CloudAir Selection API
Read a fan catalogue from your own software — categories, series, products, performance curves and the variables that describe them. JSON over HTTPS, one key in one header.
https://selection-api.cloudair.tech/v1
Try it panels call the live API. Paste your key at the top to see your own catalogue; without a key they answer from the CloudAir demo catalogue.
Getting started
- Get a key from the company whose catalogue you integrate. Keys are issued in their CloudAir administration — with an end date and a scope: the categories, series and products you may read.
- Send it in the
X-Api-Keyheader with every request. - Start with
GET /v1— it tells you whose catalogue the key opens, until when, and what part of it you see.
curl -H "X-Api-Key: YOUR_API_KEY" https://selection-api.cloudair.tech/v1
Authentication
Every request carries the key in one header:
X-Api-Key: ca_api_3f9a…c21d
- Two kinds of keys. A server key is a secret, called from your backend (Node, PHP, Python, C#…) and refused from web pages. A browser key lists the websites it works on and is called by JavaScript in those pages — see Calling from a browser. One key is one kind; for both, ask for two keys.
- Shown once. The catalogue owner sees the key only when issuing it. A lost key is replaced with a new one; the old one stops working at once.
- Validity. Every key has an end date. Extending it keeps the same key.
- Addresses. A key may be limited to your server's IP addresses, and single addresses may be blocked. Such a key answers
IP_NOT_ALLOWEDin the Try it panels here, because they call from our server. - Daily limit. A key may have a daily number of calls; it resets at midnight.
Calling from a browser
A browser key lets JavaScript in a web page ask the API directly — a product finder on a distributor's site, a configurator page. The catalogue owner lists the websites it works on, e.g. www.example.com or *.example.com (the domain and all its subdomains).
const res = await fetch("https://selection-api.cloudair.tech/v1/series?lang=en", {
headers: { "X-Api-Key": "ca_api_…" } // a browser key — it is visible in the page, by design
});
const body = await res.json();
if (!res.ok) console.warn(body.error.code, body.error.message);
- The browser sends the page's
Originby itself; the API answers only listed websites and lets only them read the response (CORS). - A browser key does not work from a server (
WEBSITE_REQUIRED) and a server key does not work from a page (BROWSER_NOT_ALLOWED). - Anyone can read a key in a web page. What protects it is the website list together with a low daily limit and a narrow scope — the same model as map keys on millions of sites.
- The Try it panels here call from our server, so a browser key answers
WEBSITE_REQUIREDin them. Try it in your page instead.
Responses and errors
Every answer is JSON in UTF-8 with a real HTTP status. Success has data and usually meta:
{
"data": [ … ],
"meta": { "count": 20, "limit": 20, "offset": 0, "language": "en" }
}
Addresses are absolute. Every image and file — image, image_url, images[].url, files[].url — is a full https:// link you can download or show as it is.
A refusal has a stable code for your program and a message for people; some add fields:
{
"error": { "code": "KEY_EXPIRED", "message": "This key expired on 2027-10-24.", "fields": { "valid_to": "2027-10-24" } }
}
| HTTP | code | meaning |
|---|---|---|
| 401 | NO_KEY | No X-Api-Key header. |
| 401 | BAD_KEY | The key does not exist — mistyped, or replaced in the meantime. |
| 403 | SUSPENDED | The catalogue owner suspended the key. |
| 403 | NOT_YET | The key starts working on a later date (fields.valid_from). |
| 403 | BROWSER_NOT_ALLOWED | A server key was used from a web page. Use a browser key, or call from your server. |
| 403 | WEBSITE_REQUIRED | A browser key was used without a web page (no Origin) — it works only in a browser. |
| 403 | WEBSITE_NOT_ALLOWED | A browser key was used on a website it does not list (fields.website). |
| 403 | IP_BLOCKED | Your address is blocked for this key (fields.ip). |
| 403 | IP_NOT_ALLOWED | The key works only from listed addresses (fields.ip is yours). |
| 403 | LANGUAGE_NOT_INCLUDED | The key covers other languages (fields.languages). |
| 403 | NO_MODULE | The catalogue no longer has the Selection API enabled. |
| 404 | NOT_FOUND | No such address, or the item is outside the key's scope. |
| 405 | BAD_METHOD | The API only reads — use GET. |
| 410 | KEY_EXPIRED | The key expired (fields.valid_to). Ask the catalogue owner to extend it — the key stays the same. |
| 422 | BAD_LANGUAGE | The language is not a code like en. |
| 429 | DAILY_LIMIT | The daily limit is used up. Retry-After gives the seconds until midnight. |
| 500 | SERVER_ERROR | Our side. Retry later; if it repeats, tell the catalogue owner. |
Language
Names and descriptions come in the language you ask for with ?lang= — en, pl, de, fr and the other catalogue languages. Where a text is missing in that language you get the English one, never an empty field by accident.
- Without
?lang=answers are in English — or in the first language of a key limited to other languages. - A key limited to some languages answers
LANGUAGE_NOT_INCLUDEDfor the rest, so a missing translation never looks like a missing text. meta.languagesays which language you got. Language codes are the CloudAir ones:cz,dk,ua,gr,jp,cn,zn.- The language picker at the top applies to every Try it panel on this page.
Paging
Lists take limit (1–1000, default 100) and offset. Ask for the next page while meta.count equals limit. For everything at once there is Whole catalogue.
What a key sees
The catalogue owner decides what each key may read: the whole catalogue, or chosen categories, series and single products, up to an access level and in chosen languages. Chosen categories narrow the series; chosen series and single products add up.
Anything outside the scope simply does not exist for the key — it answers 404, not 403. Prices and internal notes are never part of the API.
Dictionaries
Series and products carry keys, not texts. Two dictionaries turn them into something a person can read — fetch them once, in the language you show, and keep them:
"variables": { "powerRated": "90", "voltageRated": "230" } → /v1/variables
"tags": [ "Centrifugal", "Phase-1" ] → /v1/tags
- The whole dictionary as a filterable table: Variables.
- Variables —
powerRatedis “Rated power”, symbolP, unitW, rounded to the given step. Values come as the catalogue stores them, sometimes as text: read them as numbers. - Tags —
Phase-1is “Single-phase” in group “power supply”. Groups are what you build filters from; filter lists with?tag=. - Both take
?used=1to return only what appears in the part of the catalogue your key sees. - Filter on any variable in Products:
?v.phase=1,?v.motorSupplyType=EC,DEC,?min.powerRated=500&max.powerRated=2000. Keys are the ones from Variables; text compares without case, numbers as numbers.
Who am I
/v1The first call to make. Tells you whose catalogue the key opens, for which company, until when, with what daily limit and what part of the catalogue it sees.
Categories
/v1/categoriesAll categories the key sees, as one flat list with parent_id. Build your own tree, menu or breadcrumbs from it. series counts the series the key sees in each category, so empty branches can be skipped.
| parameter | in | type | description |
|---|---|---|---|
lang |
query | string | Language of names and descriptions, e.g. en, pl, de. Falls back to English where a text is missing. A key limited to some languages answers 403 for the others. |
Series
/v1/seriesProduct families: name, subtitle, categories, image, family-level variables and tags.
| parameter | in | type | description |
|---|---|---|---|
category |
query | integer | Only series in this category (an id from Categories). |
tag |
query | string | Tag keys from Tags, comma-separated, e.g. Centrifugal,Phase-1 — only items that have all of them. |
lang |
query | string | Language of names and descriptions, e.g. en, pl, de. Falls back to English where a text is missing. A key limited to some languages answers 403 for the others. |
limit |
query | integer | Rows per page, 1–1000. Default 100. |
offset |
query | integer | Rows to skip — with limit, pages through long lists. |
One series
/v1/series/{id}One series with its full description, and the images and files attached to the series (dimension drawings, photos, datasheets…), each with a full download url. A series outside the key's scope answers 404 — for this key it does not exist.
| parameter | in | type | description |
|---|---|---|---|
id required |
path | integer | Series id from Series or Catalog. |
lang |
query | string | Language of names and descriptions, e.g. en, pl, de. Falls back to English where a text is missing. A key limited to some languages answers 403 for the others. |
Products
/v1/productsProducts with their technical variables — the sizes and models of each series. Read variables together with Variables to get names and units.
| parameter | in | type | description |
|---|---|---|---|
serie |
query | integer | Only products of this series. |
search |
query | string | Part of the model name or catalogue number. |
tag |
query | string | Tag keys, comma-separated — only products that have all of them. A product carries the tags of its series too. |
filters |
filters | variables | Filters on any variable from Variables: v.phase=1 (equals; v.phase=1,3 — any of), min.powerRated=500, max.speedRated=1500. Combine with &. An unknown variable answers UNKNOWN_VARIABLE. |
lang |
query | string | Language of names and descriptions, e.g. en, pl, de. Falls back to English where a text is missing. A key limited to some languages answers 403 for the others. |
limit |
query | integer | Rows per page, 1–1000. Default 100. |
offset |
query | integer | Rows to skip — with limit, pages through long lists. |
One product
/v1/products/{id}Everything about one product in one response: data, description and dimensions; its serie; image_url (the product photo, or the series photo when it has none); curves with sound spectra; accessories attached to it and accessory_of — products it is attached to; images and files with full download urls. Media come from the product and from its series, marked from: "product" or "serie" — prefer the product's, fall back to the series'. Everything respects the key's scope: an accessory or file the key does not cover is simply not listed.
| parameter | in | type | description |
|---|---|---|---|
id required |
path | integer | Product id from Products or Catalog. |
lang |
query | string | Language of names and descriptions, e.g. en, pl, de. Falls back to English where a text is missing. A key limited to some languages answers 403 for the others. |
Product curves
/v1/products/{id}/curvesMeasured performance curves of a product: the measured points (volume m³/h, pressureStatic Pa, power W, speed rpm, …), the fitted polynomials the selection software calculates with, the setting the curve belongs to (Hz frequency, V voltage, VEC EC control voltage, rpm, step, angle blade angle), the measured range x_min–x_max and the sound spectra measured along it. Polynomial coefficients go in ascending powers of the airflow Q in m³/h: y = A[0] + A[1]·Q + A[2]·Q² + …, one array per quantity (pressureStatic, power, speed, current…). An empty list means the product has no curves in the catalogue.
| parameter | in | type | description |
|---|---|---|---|
id required |
path | integer | Product id. |
Variables
/v1/variablesThe dictionary for reading variables of products and series: name, symbol, unit, group, rounding step and a description. symbol and overview may contain simple HTML (<sub>, <p>, <ul>).
| parameter | in | type | description |
|---|---|---|---|
used |
query | boolean | 1 — only variables that appear in the products and series this key sees. |
group |
query | string | Only these groups, comma-separated: operation (working-point values computed by selection — volume, pressureStatic, efficiencyStatic, power…), electric, motor, size, regulation, erp, … Every variable says its kind: operation or product. |
lang |
query | string | Language of names and descriptions, e.g. en, pl, de. Falls back to English where a text is missing. A key limited to some languages answers 403 for the others. |
Select fans
/v1/select_fanFans for a working point, each with the point computed on its curves — the same calculation as the product card of the selection software: regulation, density correction, shaft power, motor efficiency, FEI and sound. Only fans — series with the selection formula fan — that the key sees. A fan fits when its computed airflow is within the tolerance of the required one and the point lies on its measured curve. Results are ranked by score — static efficiency (100), FEI (50) and power (25), each scaled between the worst and the best result — or by sort. Every result has product with its nominal variables and tags (the same as in Products), point — the computed working point with every variable of group operation (airflow, pressures, powers, efficiencies, speed, frequency, current, FEI, SFP, energy…) plus Lwa and Lpa, keyed as in Variables, regulation (type, mode, control value and unit), sound — octave-band spectra at inlet, outlet and casing, from the product's measured acoustics, or estimated by VDI 3731 from the fan's design and working point when there are no measurements (then sound.estimated is true, as in the selection software) — the stored ErP 2026 compliance and warnings such as MOTOR_OVERLOAD. meta.rejected says why the other candidates fell out.
| parameter | in | type | description |
|---|---|---|---|
volume required |
query | number | Required airflow, m³/h. |
pressure required |
query | number | Required pressure, Pa. |
pressure_type |
query | string | static (default) or total. Total needs the fan's outlet; without it the fan is computed on static pressure. |
density |
query | number | Air density, kg/m³. Or give temperature instead. Without either: the density the curves were measured at (usually 1.204). |
temperature |
query | number | Medium temperature, °C — density is computed from it with humidity (%) and altitude (m). |
humidity |
query | number | Relative humidity, %, for density. Default 0. |
altitude |
query | number | Altitude above sea level, m, for density. Default 0. |
regulation |
query | string | automatic (default) — the fan is regulated (frequency, EC voltage, speed) to hit the point; none — it runs on its rated curve; manual — at the setting given in control. A fan that cannot be regulated is computed with none. |
control |
query | number | Setting for regulation=manual, in the fan's own unit (Hz, V, rpm), clamped to its allowed range. |
motor |
query | string | Motor supply, comma-separated — the values of motorSupplyType in Variables (e.g. AC, EC, DC). Shortcut for v.motorSupplyType. |
regulation_type |
query | string | Regulation, comma-separated — the values of regulationType in Variables (inverter, EC, voltage, rotation, step…). Shortcut for v.regulationType. |
phase |
query | string | 1, 3 or 1,3 (shortcut for v.phase). |
erp |
query | string | 2026 — only fans stored as compliant with ErP 2026 (EU 2024/1834). |
daily_hours |
query | number | Energy calculator: operating hours per day (as on the product card). |
working_days |
query | number | Energy calculator: working days per year. |
energy_cost |
query | number | Energy calculator: price of 1 kWh. With the hours and days it fills energyConsumptionPerDay, energyConsumptionPerYear, dailyEnergyCost, annualEnergyCost. |
tolerance_min |
query | number | Lowest acceptable computed airflow, % of the required one. Default −10. |
tolerance_max |
query | number | Highest acceptable computed airflow, %. Default +10. |
outside |
query | boolean | 1 — also points beyond the measured part of the curve (marked OUTSIDE_MEASURED_RANGE). |
filters |
filters | variables | Filters on variables. Product variables (v.phase=3, min.volumeMax=5000) filter the catalogue; working-point variables of group operation (min.efficiencyStatic=60, max.power=800, max.Lwa=75) filter the computed point. |
serie |
query | string | Only these series, comma-separated ids. |
category |
query | integer | Only series in this category. |
tag |
query | string | Tag keys from Tags, comma-separated, e.g. Centrifugal,Phase-1 — only items that have all of them. |
sort |
query | string | score (default), efficiency, power, sound, speed, fei. |
limit |
query | integer | Results, 1–100. Default 20. |
lang |
query | string | Language of names and descriptions, e.g. en, pl, de. Falls back to English where a text is missing. A key limited to some languages answers 403 for the others. |
Whole catalogue
/v1/catalogEverything the key sees in one response: categories, series, products with variables, and the dictionary of the variables used. Meant to be fetched once a day and kept — selecting from memory is faster than asking per product.
| parameter | in | type | description |
|---|---|---|---|
brief |
query | boolean | 1 — names only, without variables: enough for a menu or a tree. |
lang |
query | string | Language of names and descriptions, e.g. en, pl, de. Falls back to English where a text is missing. A key limited to some languages answers 403 for the others. |