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.

  1. At a glance
  2. Get the document
  3. What the document covers
  4. Authentication in the spec
  5. Reading an operation
    1. Vendor extensions
  6. Generate a client
  7. Validate your integration
  8. 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
API version Substratal-Version: 1 request header, required (see Conventions → Versioning)
Authentication Authorization: Bearer <token>: a user access token or an API Key (bearerAuth)
Content type application/json; charset=utf-8
Operations 123, 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://docs.substratalapps.com/versions/1.7.22/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 14 Organizations
Tenancy 6 Tenancy
Billing 7 Billing
Audit 4 Audit
Webhooks 9 Webhooks
Notifications 17 Push Notifications
API Keys 6 API Keys

Some endpoints aren’t in the document’s paths, because they follow external standards instead:

  • GET /.well-known/jwks.json and GET /.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’s operationIds.
  • /scim/v2/…: SCIM 2.0 provisioning, following RFC 7644.

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:

  • operationId is 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 the X-Request-Id header) through the shared responses BadRequest, Unauthorized, Forbidden, NotFound, Conflict, Unprocessable, TooManyRequests, and ServiceUnavailable. Branch on error.code, never on message. See Conventions → Errors.
  • Pagination uses the shared Limit and Cursor parameters (limit 1–100, default 25). See Conventions → Pagination.
  • Idempotency. Operations that create something take the Idempotency-Key header. Some require it, and their descriptions say which. See Conventions → Idempotency.
  • Concurrency. Single-resource responses carry an ETag header. Send it back as If-Match on PATCH to 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.
  • me can 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://docs.substratalapps.com/versions/1.7.22/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.yaml or npx @stoplight/spectral-cli lint openapi.yaml.
  • Check your mocks against it. npx @stoplight/prism-cli mock openapi.yaml serves 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 with 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 API version is the Substratal-Version request header (currently 1), not part of the base URL. Every versioned operation declares it as the shared ApiVersion header parameter (components.parameters.ApiVersion), so generated clients include it, and the Interactive Reference sends it for you. Most generators let you set it once as a default header instead of passing it per call. 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 within the current version. Write clients to ignore fields they don’t know, and to handle enum values they don’t recognize.
  • Breaking changes ship as the next version number, and the previous version keeps working for at least 6 months. See Non-Functional Requirements → Versioning for what counts as breaking.
  • Every change is recorded in the Changelog.

Table of contents