Help Developers
Conventions
The rules every endpoint follows. The quickstart shows them in use; this page is the reference. The full detail is in the API's Conventions.
Base URL and version
https://api.substratalapps.com
Substratal-Version: 1
Every path is at the host root, and the version goes in the header, never the URL. See API versioning.
IDs
Every ID starts with its type:
| Prefix | Type |
|---|---|
usr_ |
User |
app_ |
Application |
ent_ |
Entitlement |
role_ |
Role |
org_ |
Organization |
key_ |
API key |
evt_ |
Audit event |
whk_ |
Webhook subscription |
Treat everything after the prefix as opaque. Application IDs are app_ plus the slug, as in app_timetrack. Anywhere a path takes a user ID, me means the caller.
Errors
{
"error": {
"code": "entitlement_required",
"message": "This user has no active entitlement to app_invoicer.",
"details": { "application_id": "app_invoicer" }
}
}
Build your logic on code, which never changes. message is for people and can be reworded. Validation failures are 422 validation_failed, with every failing field listed in details.fields.
Pagination
Lists take limit (default 25, maximum 100) and cursor, and return:
{ "data": [], "page": { "next_cursor": "eyJpZCI6…", "has_more": true } }
Pass next_cursor back as cursor until has_more is false. Cursors last 24 hours, and only work with the filters they were issued for.
Safe updates: ETag and If-Match
Single-resource reads and every write return an ETag, such as W/"7". Send it back as If-Match on a PATCH or DELETE. If someone else changed the resource in between, you get 409 version_conflict instead of silently overwriting their change.
Idempotency
Send Idempotency-Key: <a UUID> on creates. The same key with the same body returns the original response instead of creating a duplicate; the same key with a different body is 409. Keys are remembered for 24 hours.
It's required when granting access to a user or an Organization, and when starting a Stripe Checkout.
Rate limits
| Scope | Limit |
|---|---|
| Default, per API key or signed-in session | 100 requests a minute |
| Creating users, granting access | 20 a minute |
effective-permissions, app tokens, OAuth token exchange |
300 a minute |
| Sign-in, sign-up, MFA verification | 10 a minute per IP address |
Every response carries RateLimit-Remaining and RateLimit-Reset. A 429 carries Retry-After: wait that many seconds before retrying.
Request IDs
Every response has an X-Request-Id. Send your own in the request to match the API's logs with yours. Every audit event records the ID of the request that caused it, so quote it when asking for support.