Compare and decide
Airfoil study
Save candidates, then run them under one set of conditions.
An airfoil study is three calls. Create the study, add candidates, then run it. The run spends 4 compute credits. Each candidate needs at least 8 coordinates. A study holds at most 12 candidates. Multi-element configurationKind is rejected.
Work this page from top to bottom. Read Before you call this API, then Request syntax, then the full Step by step walkthrough and the Quick checklist. Fill each payload field from the parameters table, send one of the language samples, and match the HTTP status to the accepted body or to a rejected body. Sample numbers show field names only — they are not measured aerodynamic results. If a field or status is unclear, use the contact links at the end and include the status and the error text.
How to use this documentation page
- Skim the intro so you know what meter this analysis spends and what geometry it needs.
- Complete the step-by-step walkthrough once with the dummy shapes, then again with your live key and real inputs.
- Copy a language sample, change only the values you must change, and keep JSON key names identical.
- On every response, read status first. Only then parse coefficients, gates, or file content.
- When automation returns 202, open Jobs and usage and poll until the job finishes.
Create the study
POST https://henfoil.hendoi.com/api/v1/studies
Before you call this API
- You have an active HENDOI account and a live HENFOIL API key (
hfk_…). - You know which plan meters this call spends (automation
apiRequests, and often polars, optimize, or compute credits). - You have either a stored
airfoilId, a NACA code, or coordinate / .dat text ready when the endpoint needs geometry. - You will treat every numeric sample on this page as a shape guide, not as measured flight data.
Request syntax
Every call has the same four parts. Fill them in this order.
- Method.
POST. - URL.
https://henfoil.hendoi.com/api/v1/studies. The host is the production API. Do not change it. - Header.
Authorization: Bearer hfk_demo_4c91e2ab8d07. Replace the dummy key with the key from Settings, then API, then Generate now. You can send the raw key inX-Henfoil-Keyinstead, with no Bearer prefix. - Body. One JSON object, with header
Content-Type: application/json.
Step by step
Follow this guide in order. Each step says what to type, where it goes, and what to check next. After the last step, compare your HTTP status with the accepted body and the rejected bodies further down.
- Before you start. Sign in at https://hendoi.com/login, open HENFOIL, then Settings → API → Generate now. Copy the full
hfk_…key once. Confirm your plan still has usage remaining for automation calls. - Confirm the endpoint. You will send
POSTtohttps://henfoil.hendoi.com/api/v1/studies. Keep the production host. Do not point samples at localhost in a shipped integration. - Attach the key. Prefer
Authorization: Bearer hfk_…. Or send the raw key inX-Henfoil-Keywith no Bearer prefix. Never put the key in the JSON body or in a query string. - Build one JSON object. Add header
Content-Type: application/json. Numbers stay numbers (no quotes). Strings stay quoted. Omit optional keys when you want the server default. - Optionally set
name. Example:Trainer sections. Defaults to Airfoil study. Omit the key to keep the server default. - Optionally set
conditions.re. Example:500000. Shared Reynolds number. Omit the key to keep the server default. - Optionally set
conditions.mach. Example:0. Shared Mach number. Omit the key to keep the server default. - Optionally set
conditions.targetCl. Example:0.5. Lift used when the study compares at equal lift. Omit the key to keep the server default. - Optionally set
configurationKind. Example:independent-candidates. multi-element is rejected. Omit the key to keep the server default. - Send a name and the shared conditions.
- Copy id from the response. That id is the path on the next two calls.
- Send the request. Use any of the six language samples below. Expect HTTP
200when the call matches this page's accepted example (or HTTP 202 withjobIdwhen a long solve is queued). - Read the JSON by name. On success, start with
id,name,candidates. Ignore sample coefficient magnitudes — they are placeholders for field names only. - Handle failure before success fields. If status is 400, 401, or 403, read
error(andmeteron 403). Do not parse lift or drag from an error body. - Verify before the next call. Confirm status, remaining automation budget (
X-Henfoil-Api-Remainingor GET /api/v1/usage), and that honesty / gate fields (when present) match what you expect for a screening result — not a certification claim. - If you are stuck. Copy the HTTP status and the full
errorstring, then use the contact block at the bottom of this page (WhatsApp or support@hendoi.com).
Quick checklist
- □ Method is
POSTand URL path is exact. - □ Live API key is in the Authorization (or X-Henfoil-Key) header.
- □ JSON body uses the field names in the parameters table; types match (number vs string).
- □ Client branches on HTTP status before reading aerodynamic fields.
- □ Error path logs
error/meterfor support.
Parameters
Required means the server needs that field. Optional means a default is already in place. One of these means you send exactly one of the listed geometry fields.
| Field | Required | Example | Meaning |
|---|---|---|---|
| name | No | Trainer sections | Defaults to Airfoil study. |
| conditions.re | No | 500000 | Shared Reynolds number. |
| conditions.mach | No | 0 | Shared Mach number. |
| conditions.targetCl | No | 0.5 | Lift used when the study compares at equal lift. |
| configurationKind | No | independent-candidates | multi-element is rejected. |
How to fill the payload
Treat the payload as a form. Each name is a JSON key. Send numbers as numbers, without quotes. Send text in quotes. Omit an optional key when you want the server default. Send exactly one geometry source when the table says the field is one of a set.
name— Optional. Example:Trainer sections. Defaults to Airfoil study.conditions.re— Optional. Example:500000. Shared Reynolds number.conditions.mach— Optional. Example:0. Shared Mach number.conditions.targetCl— Optional. Example:0.5. Lift used when the study compares at equal lift.configurationKind— Optional. Example:independent-candidates. multi-element is rejected.
The example object below is the body to send. It uses name, conditions. Copy it, replace the dummy values with yours, and send it with Content-Type: application/json.
Example request
The key hfk_demo_4c91e2ab8d07 is a dummy. It is not a live key, and the server rejects it. Replace it before you send the call. The other values are dummy parameters in the same shape the server reads, so you can see where each number goes.
{
"name": "Trainer sections",
"conditions": {
"re": 500000,
"mach": 0,
"targetCl": 0.5
}
}
If the request is accepted
HTTP 200 means the key was accepted and this call finished. Read the status in your client first, then parse the JSON. The numbers below are placeholders so you can see the field names. A live response replaces them. Do not treat this sample as a measured result.
{
"id": "9c2e1f55-2222-4333-8444-666677778888",
"name": "Trainer sections",
"candidates": []
}
Read the body by name:
id— text, such as an id, a name, or a message.name— text, such as an id, a name, or a message.candidates— a list.
If the request is not accepted
A rejected call is still JSON. Read the HTTP status, then the error string. Do not look for lift or drag fields in these bodies. Fix the named problem and send the same call again.
HTTP 400, missing or unusable input. The message changes with the field. The shape does not:
{
"error": "Provide coordinates, text, or airfoilId."
}
HTTP 401, missing, unknown, or deactivated key. No automation call is spent:
{
"error": "This API key is deactivated or was not recognized. Only an active key can call the API."
}
HTTP 403, a meter is used up. meter tells you which one. apiRequests is the automation budget. apiKeyAllocation is the calls assigned to this key:
{
"error": "API usage limit reached.",
"code": "QUOTA_EXCEEDED",
"plan": "student",
"upgradeTo": "professional",
"pricingPath": "/pricing",
"meter": "apiRequests",
"used": 500,
"limit": 500,
"remaining": 0
}
When something goes wrong
- HTTP 400 Multi-element configurations are not supported in the Airfoil Study workspace.
- HTTP 401: the key is missing, unknown, or deactivated. No call is spent. Generate a new key and send that value.
- HTTP 403 with
codeQUOTA_EXCEEDED: readmeter.apiRequestsis the automation budget.apiKeyAllocationis this key's assigned calls. The other meter names are the platform limit named in the message. - HTTP 400: read
error. It names the missing field or the geometry problem. Fix that field and send the call again. - HTTP 202 with
jobId: the solve is queued. Poll Jobs and usage until status is COMPLETED, COMPLETED_WITH_WARNINGS, FAILED, or CANCELLED. Then GET the job withoutpartial=1.
How each language returns a bad response
- cURL. The body prints first. The last line from
-wis the HTTP status. 400 still prints a JSON body. - Node.js.
fetchdoes not throw on 400, 401, or 403. Branch onres.status, then readdata.error. - C#.
GetAsyncandPostAsyncdo not throw on those statuses. ReadStatusCode, then the body string. - Python.
urlopenraisesurllib.error.HTTPError. The status ise.code. The JSON ise.read().decode(). - PHP.
curl_execreturns the body for a 400. The status isCURLINFO_HTTP_CODE. A 400 means the server answered. - Java.
sendreturns the status and the body together. A 400 does not throw.
import json
from urllib.error import HTTPError
from urllib.request import urlopen
# req is the Request built in the Python sample below.
try:
with urlopen(req) as res:
print(res.status)
print(json.load(res))
except HTTPError as e:
print(e.code)
print(json.loads(e.read().decode()))
Useful tips
- Replace the dummy key before the first real call. A 401 on the sample key is expected.
- Check the HTTP status before you read any coefficient. An error body has
error, not a polar. - Read
X-Henfoil-Api-Remainingbefore a loop.X-Henfoil-Api-Warningappears when fewer than one fifth of the calls remain. - Copy a sample with the Copy button, then change only the values you intend to change. Leave the key names as written.
- If the
errortext does not match the body you sent, contact us with the status code and that text. The link is at the bottom of this page.
Samples
The six programs below send the same method, URL, header, and payload. The paragraph above each program says which line is the payload and which line is the response.
Send it in cURL
The payload is the JSON in single quotes after -d. Edit the values there. Keep Content-Type: application/json. Replace the dummy key. -w prints the HTTP status after the body.
curl -s -X POST \
"https://henfoil.hendoi.com/api/v1/studies" \
-H "Authorization: Bearer hfk_demo_4c91e2ab8d07" \
-H "Content-Type: application/json" \
-d '{"name":"Trainer sections","conditions":{"re":500000,"mach":0,"targetCl":0.5}}' \
-w "\n%{http_code}\n"
Send it in Node.js
The object inside JSON.stringify is the payload. Change a property there; do not build the JSON by hand. fetch does not throw on 400 or 403. Read res.status, then data.error when the status is not 200.
const res = await fetch("https://henfoil.hendoi.com/api/v1/studies", {
method: "POST",
headers: {
Authorization: "Bearer hfk_demo_4c91e2ab8d07",
"Content-Type": "application/json"
},
body: JSON.stringify({"name": "Trainer sections", "conditions": {"re": 500000, "mach": 0, "targetCl": 0.5}})
});
const data = await res.json();
console.log(res.status, data);
Send it in C#
The string passed to StringContent is the JSON payload. Edit the values inside that string and keep the escaped quotes. StatusCode is the HTTP code. The following string is the raw response, success or error.
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
using var client = new HttpClient();
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", "hfk_demo_4c91e2ab8d07");
var content = new StringContent(
"{\"name\": \"Trainer sections\", \"conditions\": {\"re\": 500000, \"mach\": 0, \"targetCl\": 0.5}}", Encoding.UTF8, "application/json");
var response = await client.PostAsync("https://henfoil.hendoi.com/api/v1/studies", content);
Console.WriteLine((int)response.StatusCode);
Console.WriteLine(await response.Content.ReadAsStringAsync());
Send it in Python
payload is the JSON text, then encoded to bytes. Edit the text inside the quotes. On 200, res.status and json.load show the accepted body. On 400, 401, or 403, urlopen raises HTTPError and the JSON is in e.read().
import json
from urllib.request import Request, urlopen
payload = "{\"name\": \"Trainer sections\", \"conditions\": {\"re\": 500000, \"mach\": 0, \"targetCl\": 0.5}}".encode()
req = Request(
"https://henfoil.hendoi.com/api/v1/studies",
data=payload,
headers={
"Authorization": "Bearer hfk_demo_4c91e2ab8d07",
"Content-Type": "application/json",
},
method="POST",
)
with urlopen(req) as res:
print(res.status)
print(json.load(res))
Send it in PHP
The array is the payload. json_encode turns it into JSON. Change a value in the array. CURLINFO_HTTP_CODE is the status. $body is the response text, whether the status is 200 or 400.
$payload = json_encode([
"name" => "Trainer sections",
"conditions" => [
"re" => 500000,
"mach" => 0,
"targetCl" => 0.5
]
]);
$ch = curl_init("https://henfoil.hendoi.com/api/v1/studies");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => $payload,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
"Authorization: Bearer hfk_demo_4c91e2ab8d07",
"Content-Type: application/json",
],
]);
$body = curl_exec($ch);
echo curl_getinfo($ch, CURLINFO_HTTP_CODE), "\n", $body;
curl_close($ch);
Send it in Java
The body string is the JSON payload. Edit the values in that string. statusCode() is the HTTP code and body() is the response text. A rejected call still returns both.
var client = java.net.http.HttpClient.newHttpClient();
var body = "{\"name\": \"Trainer sections\", \"conditions\": {\"re\": 500000, \"mach\": 0, \"targetCl\": 0.5}}";
var request = java.net.http.HttpRequest.newBuilder()
.uri(java.net.URI.create("https://henfoil.hendoi.com/api/v1/studies"))
.header("Authorization", "Bearer hfk_demo_4c91e2ab8d07")
.header("Content-Type", "application/json")
.method("POST", java.net.http.HttpRequest.BodyPublishers.ofString(body))
.build();
var response = client.send(request, java.net.http.HttpResponse.BodyHandlers.ofString());
System.out.println(response.statusCode());
System.out.println(response.body());
Add a candidate
POST https://henfoil.hendoi.com/api/v1/studies/9c2e1f55-2222-4333-8444-666677778888/candidates
Before you call this API
- You have an active HENDOI account and a live HENFOIL API key (
hfk_…). - You know which plan meters this call spends (automation
apiRequests, and often polars, optimize, or compute credits). - You have either a stored
airfoilId, a NACA code, or coordinate / .dat text ready when the endpoint needs geometry. - You will treat every numeric sample on this page as a shape guide, not as measured flight data.
Request syntax
Every call has the same four parts. Fill them in this order.
- Method.
POST. - URL.
https://henfoil.hendoi.com/api/v1/studies/9c2e1f55-2222-4333-8444-666677778888/candidates. The host is the production API. Do not change it. - Header.
Authorization: Bearer hfk_demo_4c91e2ab8d07. Replace the dummy key with the key from Settings, then API, then Generate now. You can send the raw key inX-Henfoil-Keyinstead, with no Bearer prefix. - Body. One JSON object, with header
Content-Type: application/json.
Step by step
Follow this guide in order. Each step says what to type, where it goes, and what to check next. After the last step, compare your HTTP status with the accepted body and the rejected bodies further down.
- Before you start. Sign in at https://hendoi.com/login, open HENFOIL, then Settings → API → Generate now. Copy the full
hfk_…key once. Confirm your plan still has usage remaining for automation calls. - Confirm the endpoint. You will send
POSTtohttps://henfoil.hendoi.com/api/v1/studies/9c2e1f55-2222-4333-8444-666677778888/candidates. Keep the production host. Do not point samples at localhost in a shipped integration. - Attach the key. Prefer
Authorization: Bearer hfk_…. Or send the raw key inX-Henfoil-Keywith no Bearer prefix. Never put the key in the JSON body or in a query string. - Build one JSON object. Add header
Content-Type: application/json. Numbers stay numbers (no quotes). Strings stay quoted. Omit optional keys when you want the server default. - Set
candidates. Required (Yes). Example value:array. Each object needs coordinates with at least 8 points, and a displayName. - Optionally set
displayName. Example:NACA 2412. Name stored on the candidate. Omit the key to keep the server default. - Replace the id in the path with the id from create.
- Send candidates, or a single coordinates array on the body.
- Repeat until the shortlist is complete. The cap is 12.
- Send the request. Use any of the six language samples below. Expect HTTP
200when the call matches this page's accepted example (or HTTP 202 withjobIdwhen a long solve is queued). - Read the JSON by name. On success, start with
id,candidates. Ignore sample coefficient magnitudes — they are placeholders for field names only. - Handle failure before success fields. If status is 400, 401, or 403, read
error(andmeteron 403). Do not parse lift or drag from an error body. - Verify before the next call. Confirm status, remaining automation budget (
X-Henfoil-Api-Remainingor GET /api/v1/usage), and that honesty / gate fields (when present) match what you expect for a screening result — not a certification claim. - If you are stuck. Copy the HTTP status and the full
errorstring, then use the contact block at the bottom of this page (WhatsApp or support@hendoi.com).
Quick checklist
- □ Method is
POSTand URL path is exact. - □ Live API key is in the Authorization (or X-Henfoil-Key) header.
- □ JSON body uses the field names in the parameters table; types match (number vs string).
- □ Client branches on HTTP status before reading aerodynamic fields.
- □ Error path logs
error/meterfor support.
Parameters
Required means the server needs that field. Optional means a default is already in place. One of these means you send exactly one of the listed geometry fields.
| Field | Required | Example | Meaning |
|---|---|---|---|
| candidates | Yes | array | Each object needs coordinates with at least 8 points, and a displayName. |
| displayName | No | NACA 2412 | Name stored on the candidate. |
How to fill the payload
Treat the payload as a form. Each name is a JSON key. Send numbers as numbers, without quotes. Send text in quotes. Omit an optional key when you want the server default. Send exactly one geometry source when the table says the field is one of a set.
candidates— Required. Example:array. Each object needs coordinates with at least 8 points, and a displayName.displayName— Optional. Example:NACA 2412. Name stored on the candidate.
The example object below is the body to send. It uses candidates. Copy it, replace the dummy values with yours, and send it with Content-Type: application/json.
Example request
The key hfk_demo_4c91e2ab8d07 is a dummy. It is not a live key, and the server rejects it. Replace it before you send the call. The other values are dummy parameters in the same shape the server reads, so you can see where each number goes.
{
"candidates": [
{
"displayName": "NACA 2412",
"coordinates": [
{
"x": 1.0,
"y": 0.0
},
{
"x": 0.75,
"y": 0.05
},
{
"x": 0.4,
"y": 0.08
},
{
"x": 0.1,
"y": 0.03
},
{
"x": 0.0,
"y": 0.0
},
{
"x": 0.1,
"y": -0.02
},
{
"x": 0.4,
"y": -0.04
},
{
"x": 0.75,
"y": -0.03
}
]
}
]
}
If the request is accepted
HTTP 200 means the key was accepted and this call finished. Read the status in your client first, then parse the JSON. The numbers below are placeholders so you can see the field names. A live response replaces them. Do not treat this sample as a measured result.
{
"id": "9c2e1f55-2222-4333-8444-666677778888",
"candidates": [
{
"displayName": "NACA 2412"
}
]
}
Read the body by name:
id— text, such as an id, a name, or a message.candidates— a list of objects. Each object includesdisplayName.
If the request is not accepted
A rejected call is still JSON. Read the HTTP status, then the error string. Do not look for lift or drag fields in these bodies. Fix the named problem and send the same call again.
HTTP 400, missing or unusable input. The message changes with the field. The shape does not:
{
"error": "Provide coordinates, text, or airfoilId."
}
HTTP 401, missing, unknown, or deactivated key. No automation call is spent:
{
"error": "This API key is deactivated or was not recognized. Only an active key can call the API."
}
HTTP 403, a meter is used up. meter tells you which one. apiRequests is the automation budget. apiKeyAllocation is the calls assigned to this key:
{
"error": "API usage limit reached.",
"code": "QUOTA_EXCEEDED",
"plan": "student",
"upgradeTo": "professional",
"pricingPath": "/pricing",
"meter": "apiRequests",
"used": 500,
"limit": 500,
"remaining": 0
}
When something goes wrong
- HTTP 400 candidates[] required, or Each candidate needs at least 8 coordinates, or Study candidate cap is 12.
- HTTP 401: the key is missing, unknown, or deactivated. No call is spent. Generate a new key and send that value.
- HTTP 403 with
codeQUOTA_EXCEEDED: readmeter.apiRequestsis the automation budget.apiKeyAllocationis this key's assigned calls. The other meter names are the platform limit named in the message. - HTTP 400: read
error. It names the missing field or the geometry problem. Fix that field and send the call again. - HTTP 202 with
jobId: the solve is queued. Poll Jobs and usage until status is COMPLETED, COMPLETED_WITH_WARNINGS, FAILED, or CANCELLED. Then GET the job withoutpartial=1.
How each language returns a bad response
- cURL. The body prints first. The last line from
-wis the HTTP status. 400 still prints a JSON body. - Node.js.
fetchdoes not throw on 400, 401, or 403. Branch onres.status, then readdata.error. - C#.
GetAsyncandPostAsyncdo not throw on those statuses. ReadStatusCode, then the body string. - Python.
urlopenraisesurllib.error.HTTPError. The status ise.code. The JSON ise.read().decode(). - PHP.
curl_execreturns the body for a 400. The status isCURLINFO_HTTP_CODE. A 400 means the server answered. - Java.
sendreturns the status and the body together. A 400 does not throw.
import json
from urllib.error import HTTPError
from urllib.request import urlopen
# req is the Request built in the Python sample below.
try:
with urlopen(req) as res:
print(res.status)
print(json.load(res))
except HTTPError as e:
print(e.code)
print(json.loads(e.read().decode()))
Useful tips
- Replace the dummy key before the first real call. A 401 on the sample key is expected.
- Check the HTTP status before you read any coefficient. An error body has
error, not a polar. - Read
X-Henfoil-Api-Remainingbefore a loop.X-Henfoil-Api-Warningappears when fewer than one fifth of the calls remain. - Copy a sample with the Copy button, then change only the values you intend to change. Leave the key names as written.
- If the
errortext does not match the body you sent, contact us with the status code and that text. The link is at the bottom of this page.
Samples
The six programs below send the same method, URL, header, and payload. The paragraph above each program says which line is the payload and which line is the response.
Send it in cURL
The payload is the JSON in single quotes after -d. Edit the values there. Keep Content-Type: application/json. Replace the dummy key. -w prints the HTTP status after the body.
curl -s -X POST \
"https://henfoil.hendoi.com/api/v1/studies/9c2e1f55-2222-4333-8444-666677778888/candidates" \
-H "Authorization: Bearer hfk_demo_4c91e2ab8d07" \
-H "Content-Type: application/json" \
-d '{"candidates":[{"displayName":"NACA 2412","coordinates":[{"x":1.0,"y":0.0},{"x":0.75,"y":0.05},{"x":0.4,"y":0.08},{"x":0.1,"y":0.03},{"x":0.0,"y":0.0},{"x":0.1,"y":-0.02},{"x":0.4,"y":-0.04},{"x":0.75,"y":-0.03}]}]}' \
-w "\n%{http_code}\n"
Send it in Node.js
The object inside JSON.stringify is the payload. Change a property there; do not build the JSON by hand. fetch does not throw on 400 or 403. Read res.status, then data.error when the status is not 200.
const res = await fetch("https://henfoil.hendoi.com/api/v1/studies/9c2e1f55-2222-4333-8444-666677778888/candidates", {
method: "POST",
headers: {
Authorization: "Bearer hfk_demo_4c91e2ab8d07",
"Content-Type": "application/json"
},
body: JSON.stringify({"candidates": [{"displayName": "NACA 2412", "coordinates": [{"x": 1.0, "y": 0.0}, {"x": 0.75, "y": 0.05}, {"x": 0.4, "y": 0.08}, {"x": 0.1, "y": 0.03}, {"x": 0.0, "y": 0.0}, {"x": 0.1, "y": -0.02}, {"x": 0.4, "y": -0.04}, {"x": 0.75, "y": -0.03}]}]})
});
const data = await res.json();
console.log(res.status, data);
Send it in C#
The string passed to StringContent is the JSON payload. Edit the values inside that string and keep the escaped quotes. StatusCode is the HTTP code. The following string is the raw response, success or error.
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
using var client = new HttpClient();
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", "hfk_demo_4c91e2ab8d07");
var content = new StringContent(
"{\"candidates\": [{\"displayName\": \"NACA 2412\", \"coordinates\": [{\"x\": 1.0, \"y\": 0.0}, {\"x\": 0.75, \"y\": 0.05}, {\"x\": 0.4, \"y\": 0.08}, {\"x\": 0.1, \"y\": 0.03}, {\"x\": 0.0, \"y\": 0.0}, {\"x\": 0.1, \"y\": -0.02}, {\"x\": 0.4, \"y\": -0.04}, {\"x\": 0.75, \"y\": -0.03}]}]}", Encoding.UTF8, "application/json");
var response = await client.PostAsync("https://henfoil.hendoi.com/api/v1/studies/9c2e1f55-2222-4333-8444-666677778888/candidates", content);
Console.WriteLine((int)response.StatusCode);
Console.WriteLine(await response.Content.ReadAsStringAsync());
Send it in Python
payload is the JSON text, then encoded to bytes. Edit the text inside the quotes. On 200, res.status and json.load show the accepted body. On 400, 401, or 403, urlopen raises HTTPError and the JSON is in e.read().
import json
from urllib.request import Request, urlopen
payload = "{\"candidates\": [{\"displayName\": \"NACA 2412\", \"coordinates\": [{\"x\": 1.0, \"y\": 0.0}, {\"x\": 0.75, \"y\": 0.05}, {\"x\": 0.4, \"y\": 0.08}, {\"x\": 0.1, \"y\": 0.03}, {\"x\": 0.0, \"y\": 0.0}, {\"x\": 0.1, \"y\": -0.02}, {\"x\": 0.4, \"y\": -0.04}, {\"x\": 0.75, \"y\": -0.03}]}]}".encode()
req = Request(
"https://henfoil.hendoi.com/api/v1/studies/9c2e1f55-2222-4333-8444-666677778888/candidates",
data=payload,
headers={
"Authorization": "Bearer hfk_demo_4c91e2ab8d07",
"Content-Type": "application/json",
},
method="POST",
)
with urlopen(req) as res:
print(res.status)
print(json.load(res))
Send it in PHP
The array is the payload. json_encode turns it into JSON. Change a value in the array. CURLINFO_HTTP_CODE is the status. $body is the response text, whether the status is 200 or 400.
$payload = json_encode([
"candidates" => [
[
"displayName" => "NACA 2412",
"coordinates" => [
[
"x" => 1.0,
"y" => 0.0
],
[
"x" => 0.75,
"y" => 0.05
],
[
"x" => 0.4,
"y" => 0.08
],
[
"x" => 0.1,
"y" => 0.03
],
[
"x" => 0.0,
"y" => 0.0
],
[
"x" => 0.1,
"y" => -0.02
],
[
"x" => 0.4,
"y" => -0.04
],
[
"x" => 0.75,
"y" => -0.03
]
]
]
]
]);
$ch = curl_init("https://henfoil.hendoi.com/api/v1/studies/9c2e1f55-2222-4333-8444-666677778888/candidates");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => $payload,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
"Authorization: Bearer hfk_demo_4c91e2ab8d07",
"Content-Type: application/json",
],
]);
$body = curl_exec($ch);
echo curl_getinfo($ch, CURLINFO_HTTP_CODE), "\n", $body;
curl_close($ch);
Send it in Java
The body string is the JSON payload. Edit the values in that string. statusCode() is the HTTP code and body() is the response text. A rejected call still returns both.
var client = java.net.http.HttpClient.newHttpClient();
var body = "{\"candidates\": [{\"displayName\": \"NACA 2412\", \"coordinates\": [{\"x\": 1.0, \"y\": 0.0}, {\"x\": 0.75, \"y\": 0.05}, {\"x\": 0.4, \"y\": 0.08}, {\"x\": 0.1, \"y\": 0.03}, {\"x\": 0.0, \"y\": 0.0}, {\"x\": 0.1, \"y\": -0.02}, {\"x\": 0.4, \"y\": -0.04}, {\"x\": 0.75, \"y\": -0.03}]}]}";
var request = java.net.http.HttpRequest.newBuilder()
.uri(java.net.URI.create("https://henfoil.hendoi.com/api/v1/studies/9c2e1f55-2222-4333-8444-666677778888/candidates"))
.header("Authorization", "Bearer hfk_demo_4c91e2ab8d07")
.header("Content-Type", "application/json")
.method("POST", java.net.http.HttpRequest.BodyPublishers.ofString(body))
.build();
var response = client.send(request, java.net.http.HttpResponse.BodyHandlers.ofString());
System.out.println(response.statusCode());
System.out.println(response.body());
Run the study
POST https://henfoil.hendoi.com/api/v1/studies/9c2e1f55-2222-4333-8444-666677778888/run
Before you call this API
- You have an active HENDOI account and a live HENFOIL API key (
hfk_…). - You know which plan meters this call spends (automation
apiRequests, and often polars, optimize, or compute credits). - You have either a stored
airfoilId, a NACA code, or coordinate / .dat text ready when the endpoint needs geometry. - You will treat every numeric sample on this page as a shape guide, not as measured flight data.
Request syntax
Every call has the same four parts. Fill them in this order.
- Method.
POST. - URL.
https://henfoil.hendoi.com/api/v1/studies/9c2e1f55-2222-4333-8444-666677778888/run. The host is the production API. Do not change it. - Header.
Authorization: Bearer hfk_demo_4c91e2ab8d07. Replace the dummy key with the key from Settings, then API, then Generate now. You can send the raw key inX-Henfoil-Keyinstead, with no Bearer prefix. - Body. One JSON object, with header
Content-Type: application/json.
Step by step
Follow this guide in order. Each step says what to type, where it goes, and what to check next. After the last step, compare your HTTP status with the accepted body and the rejected bodies further down.
- Before you start. Sign in at https://hendoi.com/login, open HENFOIL, then Settings → API → Generate now. Copy the full
hfk_…key once. Confirm your plan still has usage remaining for automation calls. - Confirm the endpoint. You will send
POSTtohttps://henfoil.hendoi.com/api/v1/studies/9c2e1f55-2222-4333-8444-666677778888/run. Keep the production host. Do not point samples at localhost in a shipped integration. - Attach the key. Prefer
Authorization: Bearer hfk_…. Or send the raw key inX-Henfoil-Keywith no Bearer prefix. Never put the key in the JSON body or in a query string. - Build one JSON object. Add header
Content-Type: application/json. Numbers stay numbers (no quotes). Strings stay quoted. Omit optional keys when you want the server default. - Optionally set
async. Example:true. Queues the run when a worker is available. Omit the key to keep the server default. - Send async true when you want a job id.
- The server copies the saved candidates and conditions into the run.
- Poll the job, then read the compare pack.
- Send the request. Use any of the six language samples below. Expect HTTP
200when the call matches this page's accepted example (or HTTP 202 withjobIdwhen a long solve is queued). - Read the JSON by name. On success, start with
jobId,status. Ignore sample coefficient magnitudes — they are placeholders for field names only. - Handle failure before success fields. If status is 400, 401, or 403, read
error(andmeteron 403). Do not parse lift or drag from an error body. - Verify before the next call. Confirm status, remaining automation budget (
X-Henfoil-Api-Remainingor GET /api/v1/usage), and that honesty / gate fields (when present) match what you expect for a screening result — not a certification claim. - If you are stuck. Copy the HTTP status and the full
errorstring, then use the contact block at the bottom of this page (WhatsApp or support@hendoi.com).
Quick checklist
- □ Method is
POSTand URL path is exact. - □ Live API key is in the Authorization (or X-Henfoil-Key) header.
- □ JSON body uses the field names in the parameters table; types match (number vs string).
- □ Client branches on HTTP status before reading aerodynamic fields.
- □ Error path logs
error/meterfor support.
Parameters
Required means the server needs that field. Optional means a default is already in place. One of these means you send exactly one of the listed geometry fields.
| Field | Required | Example | Meaning |
|---|---|---|---|
| async | No | true | Queues the run when a worker is available. |
How to fill the payload
Treat the payload as a form. Each name is a JSON key. Send numbers as numbers, without quotes. Send text in quotes. Omit an optional key when you want the server default. Send exactly one geometry source when the table says the field is one of a set.
async— Optional. Example:true. Queues the run when a worker is available.
The example object below is the body to send. It uses async. Copy it, replace the dummy values with yours, and send it with Content-Type: application/json.
Example request
The key hfk_demo_4c91e2ab8d07 is a dummy. It is not a live key, and the server rejects it. Replace it before you send the call. The other values are dummy parameters in the same shape the server reads, so you can see where each number goes.
{
"async": true
}
If the request is accepted
HTTP 200 means the key was accepted and this call finished. Read the status in your client first, then parse the JSON. The numbers below are placeholders so you can see the field names. A live response replaces them. Do not treat this sample as a measured result.
{
"jobId": "8b1d0e44-1111-4222-8333-555566667777",
"status": "QUEUED"
}
Read the body by name:
jobId— text, such as an id, a name, or a message.status— text, such as an id, a name, or a message.
If the request is not accepted
A rejected call is still JSON. Read the HTTP status, then the error string. Do not look for lift or drag fields in these bodies. Fix the named problem and send the same call again.
HTTP 400, missing or unusable input. The message changes with the field. The shape does not:
{
"error": "Provide coordinates, text, or airfoilId."
}
HTTP 401, missing, unknown, or deactivated key. No automation call is spent:
{
"error": "This API key is deactivated or was not recognized. Only an active key can call the API."
}
HTTP 403, a meter is used up. meter tells you which one. apiRequests is the automation budget. apiKeyAllocation is the calls assigned to this key:
{
"error": "API usage limit reached.",
"code": "QUOTA_EXCEEDED",
"plan": "student",
"upgradeTo": "professional",
"pricingPath": "/pricing",
"meter": "apiRequests",
"used": 500,
"limit": 500,
"remaining": 0
}
When something goes wrong
- HTTP 404 when the study id is not yours.
- HTTP 401: the key is missing, unknown, or deactivated. No call is spent. Generate a new key and send that value.
- HTTP 403 with
codeQUOTA_EXCEEDED: readmeter.apiRequestsis the automation budget.apiKeyAllocationis this key's assigned calls. The other meter names are the platform limit named in the message. - HTTP 400: read
error. It names the missing field or the geometry problem. Fix that field and send the call again. - HTTP 202 with
jobId: the solve is queued. Poll Jobs and usage until status is COMPLETED, COMPLETED_WITH_WARNINGS, FAILED, or CANCELLED. Then GET the job withoutpartial=1.
How each language returns a bad response
- cURL. The body prints first. The last line from
-wis the HTTP status. 400 still prints a JSON body. - Node.js.
fetchdoes not throw on 400, 401, or 403. Branch onres.status, then readdata.error. - C#.
GetAsyncandPostAsyncdo not throw on those statuses. ReadStatusCode, then the body string. - Python.
urlopenraisesurllib.error.HTTPError. The status ise.code. The JSON ise.read().decode(). - PHP.
curl_execreturns the body for a 400. The status isCURLINFO_HTTP_CODE. A 400 means the server answered. - Java.
sendreturns the status and the body together. A 400 does not throw.
import json
from urllib.error import HTTPError
from urllib.request import urlopen
# req is the Request built in the Python sample below.
try:
with urlopen(req) as res:
print(res.status)
print(json.load(res))
except HTTPError as e:
print(e.code)
print(json.loads(e.read().decode()))
Useful tips
- Replace the dummy key before the first real call. A 401 on the sample key is expected.
- Check the HTTP status before you read any coefficient. An error body has
error, not a polar. - Read
X-Henfoil-Api-Remainingbefore a loop.X-Henfoil-Api-Warningappears when fewer than one fifth of the calls remain. - Copy a sample with the Copy button, then change only the values you intend to change. Leave the key names as written.
- If the
errortext does not match the body you sent, contact us with the status code and that text. The link is at the bottom of this page.
Samples
The six programs below send the same method, URL, header, and payload. The paragraph above each program says which line is the payload and which line is the response.
Send it in cURL
The payload is the JSON in single quotes after -d. Edit the values there. Keep Content-Type: application/json. Replace the dummy key. -w prints the HTTP status after the body.
curl -s -X POST \
"https://henfoil.hendoi.com/api/v1/studies/9c2e1f55-2222-4333-8444-666677778888/run" \
-H "Authorization: Bearer hfk_demo_4c91e2ab8d07" \
-H "Content-Type: application/json" \
-d '{"async":true}' \
-w "\n%{http_code}\n"
Send it in Node.js
The object inside JSON.stringify is the payload. Change a property there; do not build the JSON by hand. fetch does not throw on 400 or 403. Read res.status, then data.error when the status is not 200.
const res = await fetch("https://henfoil.hendoi.com/api/v1/studies/9c2e1f55-2222-4333-8444-666677778888/run", {
method: "POST",
headers: {
Authorization: "Bearer hfk_demo_4c91e2ab8d07",
"Content-Type": "application/json"
},
body: JSON.stringify({"async": true})
});
const data = await res.json();
console.log(res.status, data);
Send it in C#
The string passed to StringContent is the JSON payload. Edit the values inside that string and keep the escaped quotes. StatusCode is the HTTP code. The following string is the raw response, success or error.
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
using var client = new HttpClient();
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", "hfk_demo_4c91e2ab8d07");
var content = new StringContent(
"{\"async\": true}", Encoding.UTF8, "application/json");
var response = await client.PostAsync("https://henfoil.hendoi.com/api/v1/studies/9c2e1f55-2222-4333-8444-666677778888/run", content);
Console.WriteLine((int)response.StatusCode);
Console.WriteLine(await response.Content.ReadAsStringAsync());
Send it in Python
payload is the JSON text, then encoded to bytes. Edit the text inside the quotes. On 200, res.status and json.load show the accepted body. On 400, 401, or 403, urlopen raises HTTPError and the JSON is in e.read().
import json
from urllib.request import Request, urlopen
payload = "{\"async\": true}".encode()
req = Request(
"https://henfoil.hendoi.com/api/v1/studies/9c2e1f55-2222-4333-8444-666677778888/run",
data=payload,
headers={
"Authorization": "Bearer hfk_demo_4c91e2ab8d07",
"Content-Type": "application/json",
},
method="POST",
)
with urlopen(req) as res:
print(res.status)
print(json.load(res))
Send it in PHP
The array is the payload. json_encode turns it into JSON. Change a value in the array. CURLINFO_HTTP_CODE is the status. $body is the response text, whether the status is 200 or 400.
$payload = json_encode([
"async" => true
]);
$ch = curl_init("https://henfoil.hendoi.com/api/v1/studies/9c2e1f55-2222-4333-8444-666677778888/run");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => $payload,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
"Authorization: Bearer hfk_demo_4c91e2ab8d07",
"Content-Type: application/json",
],
]);
$body = curl_exec($ch);
echo curl_getinfo($ch, CURLINFO_HTTP_CODE), "\n", $body;
curl_close($ch);
Send it in Java
The body string is the JSON payload. Edit the values in that string. statusCode() is the HTTP code and body() is the response text. A rejected call still returns both.
var client = java.net.http.HttpClient.newHttpClient();
var body = "{\"async\": true}";
var request = java.net.http.HttpRequest.newBuilder()
.uri(java.net.URI.create("https://henfoil.hendoi.com/api/v1/studies/9c2e1f55-2222-4333-8444-666677778888/run"))
.header("Authorization", "Bearer hfk_demo_4c91e2ab8d07")
.header("Content-Type", "application/json")
.method("POST", java.net.http.HttpRequest.BodyPublishers.ofString(body))
.build();
var response = client.send(request, java.net.http.HttpResponse.BodyHandlers.ofString());
System.out.println(response.statusCode());
System.out.println(response.body());
Related pages
If you are unsure, contact us
You do not have to guess the payload. Call or WhatsApp +91-9677261485. The message already names this page. Or email support@hendoi.com. Paste the HTTP status and the error text from the response so we can answer from the same request you sent.
Contact us if you are stuck
Call or WhatsApp +91-9677261485. The message already names this page. Or email support@hendoi.com. Paste the HTTP status and the error text from the response.