API Documentation

Free, public REST API for airfoil data and aerodynamic polars.

Overview

The Foil.tools API provides programmatic access to our database of >1,600 airfoils and their pre-computed aerodynamic polars across a wide range of Reynolds numbers.

Base URL

https://foil.tools/api/v1

Authentication

API key optional

Rate Limit

40/hr, 600/hr w/ key

An API key is optional. Add a free one (Dashboard → API Keys — the same keys MCP uses) as Authorization: Bearer ft_... or X-API-Key: ft_... for 600 requests/hour instead of 40. Unknown query parameters are ignored, not rejected, so check a filter's name against the tables below.

What the stored polars cover

  • NeuralFoil (large model), one curve per section × Reynolds number × Ncrit.
  • Ncrit 1, 4, 9. /polars/{name} returns all three unless you pass ncrit; /polars/search uses Ncrit 9.
  • Alpha -20° to +25° in 0.5° steps.
  • Re 20k, 50k, 100k, 200k, 300k, 500k, 750k, 1M, 1.5M, 2M, 3M, 5M, 7M, 10M.
  • Mach 0 (incompressible). The REST API has no Mach parameter; a mach query is ignored.

MCP Server

New

Foil.tools is also an Model Context Protocol server: connect Claude, Cursor, or any MCP client and your AI assistant can search airfoils, pull polars, run NeuralFoil analysis, optimize profiles, and analyze 3D wings as native tool calls.

Using Claude, ChatGPT or Grok? You don't need to paste an API key — add foil.tools as a connector and sign in to a free account. Setup guide for each assistant →The rest of this section is for wiring MCP into your own client with a key.

Endpoint

Authentication

Required: API key or OAuth

Rate Limit

600 requests/hour

Every tool call needs a credential, including the ones marked “No credits” below: a free API key from your Dashboard (API Keys tab), or a connector sign-in. Without one, initialize and tools/list answer (40 requests/hour per IP) and tools/call returns “Authorization required”. “No credits” means a tool never spends credits, not that it needs no account.

Only optimize_airfoil, analyze_wing and analyze_trim spend credits, from the same balance and monthly free allowance as the browser (analyze_trim has no free allowance). analyze_airfoil and find_airfoil never cost credits. Rates →

Tools

ToolCostDescription
search_airfoilsNo creditsSearch the 1,600+ airfoil library by name, family, thickness, camber. Thickness and camber are fractions (0.10 = 10%).
get_airfoilNo creditsGeometry summary, CST parameters, and optional Selig coordinates for one airfoil.
get_polarNo creditsStored NeuralFoil polars: a per-Re summary, or CL/CD/CM curves for an Re range, at Ncrit 9 by default (ncrit takes 1, 4 or 9).
search_by_operating_pointNo creditsRank airfoils by drag at a target (CL, Re).
find_airfoilNo creditsWeighted multi-point selector — scored on drag, Clmax, stall gentleness, pitching moment, efficiency and L/D placement; a point can be marked a constraint (checked, not scored) for a stall or load requirement. Thickness and camber limits are percent (10 = 10%). Parameters below.
analyze_airfoilNo creditsNeuralFoil 2D analysis — fresh polars for library or custom geometry. 120 runs/hour per account. Parameters below.
optimize_airfoilCreditsGradient optimization of CST parameters across weighted operating points. Constraint thickness and camber are fractions (0.10 = 10%).
analyze_wingCredits3D wing analysis (AeroBuildup or VLM), and where the wing stalls first.
analyze_trimCreditsTrim an aircraft — wing plus stabiliser about a CG on LiftingLine: trim angle, stabiliser setting, loads, static margin, thrust. Paid-only. Reconnect to see it.
searchNo creditsLibrary search in the {id, title, url} shape ChatGPT connectors require.
fetchNo creditsOne airfoil as a readable record. Pairs with search; required by ChatGPT connectors.

Units differ between tools. find_airfoil takes thickness and camber in percent (10 = 10%); search_airfoils, the optimize_airfoil constraints and the REST t_min/t_max take fractions (0.10 = 10%).

find_airfoil parameters

pointsOperating points: { re, cl, weight? } scored on drag, Clmax and the rest; or { re, cl, kind: "constraint", load_N? } checked rather than scored (cl must stay within 90% of stall at that Re). 1–20 points; load_N is informational.
min_thickness / max_thicknessPercent chord — 10 means 10%.
min_camber / max_camberPercent chord.
min_cm / max_cmPitching-moment limits; min_cm is the most negative CM allowed.
criteria_weightsDecision-matrix weights, keys drag, clmax, stall, cm, efficiency, ld_alpha. Default { drag: 1 }, everything else 0, so { stall: 3 } means drag 1 and stall 3.
ncrit1, 4, 9 (default 9).

analyze_airfoil parameters

name or coordsA library name, or Selig-ordered [x, y] pairs (upper TE→LE, then lower LE→TE), at least 10.
re_valuesReynolds numbers (default [500000]). Batch several in one call.
alphasDegrees (default −5 to 15 step 0.5, at most 200). Not blended with a post-stall model — see below.
ncritAny value (default 9); not limited to the stored three.
machAccepted, but see the Mach note below.
flap / flap_modeTrailing-edge flap { deflection (degrees, down-positive, ±40), hinge_point (fraction of chord) }. Pass the clean section. "model" (default) leaves CM uncorrected; "geometry" deflects the section so CM responds.

The full JSON schema for every tool is what tools/list returns.

What analyze_airfoil does and does not model

Mach. Treat results as incompressible (Mach 0). The mach argument is accepted, but on a clean section it is not applied yet, even though each result row echoes the value you sent. A compressibility correction is being worked on; until it ships, don't use these polars above about Mach 0.3.

Post-stall. Alphas are not blended with a 360° post-stall model. Outside roughly ±25° the numbers are raw NeuralFoil extrapolation and not physical, so they can't yet fill a full −180° to +180° table such as a C81 rotor deck.

Connect — Claude Code

claude mcp add --transport http foiltools https://foil.tools/api/mcp --header "Authorization: Bearer ft_YOUR_KEY"

Connect — Claude Desktop / other clients

{
  "mcpServers": {
    "foiltools": {
      "command": "npx",
      "args": [
        "mcp-remote",
        "https://foil.tools/api/mcp",
        "--header",
        "Authorization: Bearer ft_YOUR_KEY"
      ]
    }
  }
}

Rate Limiting

All API responses include rate limit headers to help you track your usage:

X-RateLimit-Limit: 40

X-RateLimit-Remaining: 38

X-RateLimit-Reset: 1710547200 (Unix timestamp)

When the limit is exceeded, the API returns a 429 status with a JSON body containing the reset time. Hitting the anonymous 40/hour limit? Generate a free API key from your Dashboard and send it as Authorization: Bearer ft_... to raise it to 600/hour — no purchase required.

Endpoints

GET/api/v1

API discovery endpoint. Returns available endpoints, descriptions, and rate limit info.

Example

Request

curl https://foil.tools/api/v1

Response

{
  "version": "1",
  "base_url": "https://foil.tools/api/v1",
  "endpoints": [ ... ],
  "rate_limit": {
    "requests_per_hour": 40,
    "auth": "none",
    "authenticated": { "requests_per_hour": 600, "how": "Authorization: Bearer ft_..." }
  }
}
GET/api/v1/airfoils

List airfoils. Without filters returns all 1,600+ profiles. Supports fuzzy name search and geometric filters. Add geometry=1 to include thickness and camber fields.

Parameters

NameInRequiredDescription
qqueryNoFuzzy name search, e.g. "naca 23" or "eppler".
familyqueryNoFilter by series family, e.g. "NACA 4-digit", "Eppler", "Selig".
t_minqueryNoMinimum thickness as a fraction (e.g. 0.10 = 10%).
t_maxqueryNoMaximum thickness as a fraction.
camber_minqueryNoMinimum camber as a fraction.
camber_maxqueryNoMaximum camber as a fraction.
geometryqueryNoSet to 1 to include thickness and camber values in the response.

Example

Request

curl "https://foil.tools/api/v1/airfoils?family=Eppler&t_min=0.10&t_max=0.14&geometry=1"

Response

{
  "count": 12,
  "airfoils": [
    {
      "name": "Eppler 387",
      "filename": "e387",
      "family": "Eppler",
      "source": "UIUC Database",
      "thickness": 0.0907,
      "camber": 0.0365
    },
    ...
  ]
}
GET/api/v1/airfoils/{name}

Airfoil geometry as Selig-ordered coordinate pairs plus a summary (thickness, camber, TE thickness, LE weight). Add ?format=dat or Accept: text/plain for a raw XFOIL-compatible .dat file.

Parameters

NameInRequiredDescription
namepathYesAirfoil key, e.g. naca2412, e387, s1223. Case-insensitive.
formatqueryNoSet to "dat" to return raw Selig .dat text instead of JSON.

Example

Request

curl https://foil.tools/api/v1/airfoils/e387

Response

{
  "name": "e387",
  "display_name": "Eppler 387",
  "family": "Eppler",
  "summary": {
    "max_thickness": 0.0907,
    "max_camber": 0.0365,
    "te_thickness": 0.0,
    "le_weight": 0.172
  },
  "coordinates": [
    { "x": 1.0, "y": 0.00126 },
    { "x": 0.9982, "y": 0.00268 },
    ...
    { "x": 1.0, "y": -0.00126 }
  ]
}
GET/api/v1/polars/{name}

Stored polar rows (CL, CD, CM vs alpha) for one airfoil. Every section is stored at Ncrit 1, 4, 9, and all three come back unless you pass ncrit. Filters cover Re, alpha, CL and Ncrit; there is no CD filter.

Parameters

NameInRequiredDescription
namepathYesAirfoil key, e.g. naca2412, e387, s1223. Case-insensitive.
re_minqueryNoMinimum Reynolds number (e.g. 200000).
re_maxqueryNoMaximum Reynolds number.
alpha_minqueryNoMinimum angle of attack in degrees.
alpha_maxqueryNoMaximum angle of attack in degrees.
cl_minqueryNoMinimum lift coefficient.
cl_maxqueryNoMaximum lift coefficient.
ncritqueryNoTransition criterion: one of 1, 4, 9. Omit it to get all three.

Example

Request

curl "https://foil.tools/api/v1/polars/naca2412?re_min=500000&re_max=500000&alpha_min=0&alpha_max=8&ncrit=9"

Response

{
  "airfoil": "naca2412",
  "source": "foil.tools",
  "units": { "Re": "dimensionless", "alpha": "degrees", "CL": "dimensionless", "CD": "dimensionless", "CM": "dimensionless" },
  "polars": [
    { "Re": 500000, "Ncrit": 9, "alpha": 0.0, "CL": 0.234, "CD": 0.00634, "CM": -0.0521 },
    { "Re": 500000, "Ncrit": 9, "alpha": 0.5, "CL": 0.289, "CD": 0.00628, "CM": -0.0522 },
    ...
  ]
}
GET/api/v1/polars/search

Rank airfoils by drag at a target operating point, using the Ncrit 9 polars. Finds the closest stored Reynolds bucket per airfoil (or interpolates between the two either side when none is within re_tol), interpolates to the exact target CL in the pre-stall region, and returns matches sorted by ascending CD. There is no thickness or camber filter: narrow by geometry with /api/v1/airfoils, or use the MCP find_airfoil tool for multi-point selection with geometry and CM limits.

Parameters

NameInRequiredDescription
clqueryYesTarget lift coefficient, e.g. 0.6.
requeryYesTarget Reynolds number, e.g. 500000.
re_tolqueryNoFractional Re tolerance for bucket matching (default 0.10 = ±10%).
max_cdqueryNoDiscard results with CD above this threshold.
alpha_maxqueryNoDiscard results where the interpolated alpha exceeds this value (degrees).
familyqueryNoFilter to a specific airfoil family, e.g. "Eppler".
limitqueryNoMaximum number of results to return (default 20, max 100).

Example

Request

curl "https://foil.tools/api/v1/polars/search?cl=0.6&re=500000&max_cd=0.012&limit=5"

Response

{
  "target": { "cl": 0.6, "re": 500000 },
  "count": 5,
  "results": [
    {
      "airfoil": "e387",
      "re_used": 500000,
      "alpha": 3.21,
      "cl": 0.6,
      "cd": 0.00681,
      "cm": -0.0831,
      "ld": 88.1
    },
    ...
  ]
}

Status Codes

CodeDescription
200Success
400A required parameter is missing or out of range (e.g. ncrit other than 1, 4 or 9)
404Airfoil not found, or no stored polars for it
429Rate limit exceeded (40/hour anonymous, 600/hour with an API key)
500Internal server error

CORS

All /api/v1/* endpoints support cross-origin requests. The API returns Access-Control-Allow-Origin: * so you can call it directly from browser-based applications.