OpenAPI Specification
The complete Platform API contract as an OpenAPI 3.1 document, for developers building Applications against Substratal Apps. Browse it in the Interactive Reference, download it, or generate a client from it.
- At a glance
- Get the document
- What the document covers
- Authentication in the spec
- Reading an operation
- Generate a client
- Validate your integration
- Versioning and change policy
At a glance
| Format | OpenAPI 3.1.0, YAML |
| Document | openapi.yaml |
| Interactive view | Interactive Reference (Swagger UI) |
| Base URL | https://api.substratalapps.com/v1 |
| Authentication | Authorization: Bearer <token>: a user access token or an API Key (bearerAuth) |
| Content type | application/json; charset=utf-8 |
| Operations | 118, every one with a stable operationId |
| Schemas | 109 reusable components under components.schemas |
The prose pages under API Reference are authoritative. openapi.yaml is kept in sync with them by hand, and the implementation’s contract tests validate every response against it, so a drift between the two fails CI rather than reaching a client. See Conventions → Machine-readable.
Get the document
curl -O https://compositecode.github.io/substratalapps.com/openapi.yaml
Every published version of this site keeps its own copy of openapi.yaml, so you can pin a client to the spec it was generated from. See Versions.
What the document covers
Operations are grouped by tag, and each tag matches an API Reference page that explains the rules behind it.
| Tag | Operations | Prose reference |
|---|---|---|
| Meta | 1 | Conventions → Health check |
| Auth | 21 | Auth |
| Users | 10 | Users |
| Profiles | 4 | Profiles |
| Settings | 4 | Settings |
| Applications | 4 | Applications |
| Entitlements | 6 | Entitlements |
| Roles & Permissions | 10 | Roles & Permissions |
| Organizations | 11 | Organizations |
| Tenancy | 6 | Tenancy |
| Billing | 7 | Billing |
| Audit | 2 | Audit |
| Webhooks | 9 | Webhooks |
| Notifications | 17 | Push Notifications |
| API Keys | 6 | API Keys |
Notifications are specified, not built yet. The 17 operations under the Notifications tag are the target contract for Push Notifications. Until the reference implementation builds them, each one returns 501 not_implemented, so build clients against the shapes, not against a live server.
Some endpoints live outside /v1 and aren’t in the document’s paths, because they follow external standards instead:
GET /.well-known/jwks.jsonandGET /.well-known/openid-configuration: signing keys and discovery, for verifying app tokens with any standard JWT library. See Auth → Signing keys./mcp: the MCP Server, generated from this document’soperationIds.
Authentication in the spec
The document declares one security scheme, bearerAuth (HTTP bearer), and applies it globally. The few operations that need no credential, such as GET /health, signup, and login, override it with security: [].
The same header carries both credential types:
| Credential | Looks like | Used by | Get one from |
|---|---|---|---|
| User access token | A JWT | A person, or a client acting for them | POST /auth/login, /auth/signup, /auth/mfa/verify, or /auth/token/refresh |
| API Key | satk_live_… or satk_test_… |
Your backend, service to service | API Keys |
Some operations accept only one of the two. For example, every Billing operation returns 400 user_token_required when called with an API Key. The operation’s description says so, and its prose page lists the permission it requires.
A satk_test_… key works against the same base URL but only ever sees test-mode records. There’s no separate sandbox host. See Conventions → Authentication.
Reading an operation
Each operation in the document follows the same conventions, so you only need to learn them once:
operationIdis stable and namespaced (entitlements.grant,users.get). Generated clients use it for method names, and the MCP server uses it for tool names. Renaming one counts as a breaking change.- Errors all share one shape (
{"error": {"code", "message", "details"}}, with the request id in theX-Request-Idheader) through the shared responsesBadRequest,Unauthorized,Forbidden,NotFound,Conflict,Unprocessable,TooManyRequests, andServiceUnavailable. Branch onerror.code, never onmessage. See Conventions → Errors. - Pagination uses the shared
LimitandCursorparameters (limit1–100, default 25). See Conventions → Pagination. - Idempotency. Operations that create something take the
Idempotency-Keyheader. Some require it, and their descriptions say which. See Conventions → Idempotency. - Concurrency. Single-resource responses carry an
ETagheader. Send it back asIf-MatchonPATCHto avoid overwriting someone else’s change. See Conventions → Concurrency. - IDs are typed by prefix (
usr_,ent_,org_, …). Most are a prefix plus a ULID. Application and Role ids are derived (app_<slug>,role_<slug>_<name>). See Conventions → IDs. mecan stand in for your own User id in any/users/{id}path. See Conventions → Addressing yourself.
Vendor extensions
| Extension | Values | Meaning |
|---|---|---|
x-substratal-destructive |
always | conditional |
The operation destroys or irreversibly changes data. It’s rejected for API Keys created with restrict_destructive: true, and the MCP server marks it destructiveHint. See API Keys → Agent keys. |
x-substratal-destructive-when |
a description of the request | With conditional, only requests matching this are destructive. The rest of the operation stays callable with a restricted key. |
x-substratal-phase |
mvp, phase2, phase3 |
The Roadmap phase the operation ships in. |
Code generators ignore these extensions, so they’re safe to leave in.
Generate a client
Any OpenAPI 3.1 tool works. A few common ones:
# TypeScript types, with no runtime dependency
npx openapi-typescript https://compositecode.github.io/substratalapps.com/openapi.yaml -o substratal.d.ts
# Go: types and a client (this is what the reference implementation uses for its server side)
go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen@latest \
-generate types,client -package substratal openapi.yaml > substratal.gen.go
# Any of 50+ languages
npx @openapitools/openapi-generator-cli generate -i openapi.yaml -g python -o ./substratal-python
To explore by hand, import openapi.yaml into Postman, Insomnia, Bruno, or any other tool that reads OpenAPI. The Interactive Reference also has “Try it out” built in, against a local devserver.
Validate your integration
- Lint the document, or your copy of it. Run
npx @redocly/cli lint openapi.yamlornpx @stoplight/spectral-cli lint openapi.yaml. - Check your mocks against it.
npx @stoplight/prism-cli mock openapi.yamlserves a mock API from the document, so you can build a client before you have credentials. - Run the reference implementation locally. The repository’s devserver serves every route at
http://localhost:8080/v1with no AWS account. See the repository’s README → Local development. The Interactive Reference can send requests to it once its origin is allowed. See the steps at the top of the Interactive Reference.
Versioning and change policy
The /v1 prefix in the base URL is the API version. info.version in the document (currently a 1.0.0-draft pre-release) is the version of the document itself.
- Additive changes (new optional fields, new operations, new enum values) ship without a new prefix. Write clients to ignore fields they don’t know, and to handle enum values they don’t recognize.
- Breaking changes get a new prefix and a deprecation window for the old one. See Non-Functional Requirements → Versioning.
- Every change is recorded in the Changelog.