OpenAPI
https://api.sailo.store/api/v1/openapi.json
OpenAPI 3.1.1, served as application/openapi+json, describing every operation,
parameter, request body, response schema and failure mode.
No key required
Deliberately, and it is the one route under /api/v1 that is open.
The document describes the shape of the API and contains no shop’s data, so a key would buy nothing — and would cost the thing the document exists for. Somebody deciding whether Sailo fits their stack needs to point Postman, an SDK generator or a model at it before they have an account. A spec behind a credential is a spec nobody evaluates.
It is also the only route here that sends Access-Control-Allow-Origin: *,
because a generator running in a browser tab is a legitimate reader of it.
Every other route sends no CORS headers at all.
Generating a client
TypeScript
npx openapi-typescript https://api.sailo.store/api/v1/openapi.json \
-o src/sailo.d.tsThen use it with any fetch wrapper — openapi-fetch pairs with it directly and
gives you typed paths and responses.
Sailo publishes no official SDK. The surface is 9 operations over one envelope, so a generated client is as good as a written one and stays current without waiting on us.
What is in it
servers— the origin that answered the request, not a hard-coded host. The document is generated per request precisely so a generated client calls the deployment you fetched the spec from.security—bearerAuthon every operation.components.schemas—Money,Address,Shop,Order,OrderItem,Product,ProductVariant,Contact,Error.x-sailo-scope—readorwriteon every operation, which is the vendor extension worth reading: it tells a generated client which calls need a write key before it makes one.- Responses per status, with every reason a status can occur folded into its description. Two 400s would silently overwrite each other under one key, so the reasons are grouped rather than listed one per response.
Nullability is 3.1, not 3.0
A nullable field is a type union:
{ "type": ["string", "null"] }not nullable: true, which was 3.0’s spelling and which 3.1 validators reject.
If your generator produces any where it should produce string | null, it is
reading the document as 3.0 — check the generator’s version rather than the
document.
The document is hand-authored rather than derived from the handlers, because there is nothing at runtime to derive it from: the routes read the query string by hand and take plain TypeScript types, so no schema exists to reflect over. What keeps it honest is a build-time test that walks the real route tree and fails if a route exists this does not describe — or if this describes one that does not exist.
Caching
public, max-age=3600, stale-while-revalidate=86400. Public, unlike everything
else under /api/v1, because there is no bearer token on this route and no shop
behind it — so a shared cache holding one copy for everybody is correct rather
than a leak.
If you are polling it in CI to detect changes, an hour is the granularity you will get. The changelog is the better signal.