Open Platform API
The Open Platform is AuraBoot's machine-to-machine surface. A third-party system — an ERP sync job, a warehouse robot fleet, a partner portal — gets its own identity, calls published APIs with a short-lived token, receives webhooks, and can inject events back into AuraBoot automations.
It is separate from the logged-in REST API in REST API: human sessions never appear here, and machine identities never impersonate employees.
The one-minute mental model
base_url + client_id + client_secret
│
├─ POST /oauth2/token (OAuth 2.0 client_credentials)
▼
short-lived opaque access_token (default 1 hour; stored hashed)
│
├─ Authorization: Bearer <access_token>
▼
/api/open/v1/** (tenant is derived from the token)Three invariants to internalize before writing code:
- The tenant comes from the token. There is no
X-Tenant-Idheader and no tenant query parameter. Clients cannot switch tenants; every token is bound to one installation of your application in one tenant + environment. - Only published capabilities exist. Internal controllers, DSL models, and commands are not exposed automatically. If a route is not in the OpenAPI registry, it does not exist — probing returns
404. - Your secret is not an API key. The
client_secretis only used at the token endpoint. Business APIs accept the short-lived Bearer token and nothing else.
Step 1 — Administrator setup
An administrator (platform role sys.connector.update) creates the application in Settings → API & Open Platform (/settings/api-docs):
- Create the application. The creator automatically becomes its first
owner. - Install it into a tenant/environment (
development,staging, orproduction) and grant scopes — e.g.assets.read,assets.manage,inventory.stockins.manage. Scopes are grant-limited: a token can never exceed the scopes granted to the installation. - Create a credential. The
client_id/client_secretpair is shown exactly once. Store the secret in your secret manager; only a hash is kept server-side.
Application collaboration: owner manages members, installations, scopes, credentials, replays, and the application lifecycle; maintainer manages runtime concerns but not members; viewer has read-only access to operations and audit; non-members see nothing. Every membership change is audited, and the last owner cannot be removed or demoted.
Step 2 — Exchange credentials for a token
export AURABOOT_BASE_URL='https://your-tenant.example.com'
export AURABOOT_CLIENT_ID='ab_client_...'
export AURABOOT_CLIENT_SECRET='from your secret manager only'
ACCESS_TOKEN="$(
curl -fsS "$AURABOOT_BASE_URL/oauth2/token" \
-H 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode grant_type=client_credentials \
--data-urlencode client_id="$AURABOOT_CLIENT_ID" \
--data-urlencode client_secret="$AURABOOT_CLIENT_SECRET" \
| jq -er .access_token
)"Successful response:
{
"access_token": "…",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "assets.read assets.manage"
}You can request a subset of the granted scopes with the optional scope parameter; requesting an ungranted scope fails with invalid_scope.
Cache the token and reuse it until roughly 30 seconds before expires_in. Do not fetch a token per request — that endpoint is rate-limited and audited. When a business call returns 401 invalid_token, refresh exactly once and retry once; if it still fails, stop and investigate the installation or credential state.
Step 3 — Call the API
Every business call is base_url + access_token:
curl -fsS "$AURABOOT_BASE_URL/api/open/v1/whoami" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "X-Request-Id: $(uuidgen)"Send an X-Request-Id on every call. It is echoed back and can be searched in the tenant's call audit and webhook delivery logs — it is the correlation key for support requests.
Reading resources
Published resources (for example assets, inventory.stock-ins) support list and get:
# List — keyset pagination via an opaque cursor
curl -fsS "$AURABOOT_BASE_URL/api/open/v1/resources/assets?limit=50" \
-H "Authorization: Bearer $ACCESS_TOKEN"
# Page 2 — pass nextCursor back verbatim; it is signed and expires in 24h
curl -fsS "$AURABOOT_BASE_URL/api/open/v1/resources/assets?limit=50&cursor=$NEXT_CURSOR" \
-H "Authorization: Bearer $ACCESS_TOKEN"
# Get one record — note the ETag response header
curl -fsSI "$AURABOOT_BASE_URL/api/open/v1/resources/assets/$RECORD_PID" \
-H "Authorization: Bearer $ACCESS_TOKEN"- Records are addressed by stable public PIDs, never database IDs.
nextCursoris an opaque, signed token. Tampering with it, using it across resources, or using an expired one fails the request. Do not parse it; store and return it as-is.
Executing commands
Published commands (for example assets.assign) require both a strong If-Match ETag from a recent GET and an Idempotency-Key you generate:
ETAG=$(curl -fsSI "$AURABOOT_BASE_URL/api/open/v1/resources/assets/$RECORD_PID" \
-H "Authorization: Bearer $ACCESS_TOKEN" | sed -n 's/^ETag: //p')
curl -fsS -X POST "$AURABOOT_BASE_URL/api/open/v1/commands/assets.assign:execute" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "If-Match: $ETAG" \
-H "Idempotency-Key: $(uuidgen)" \
-H 'Content-Type: application/json' \
-d '{"targetPid":"'$RECORD_PID'","input":{"assignee":"alice"}}'- A stale or missing
If-Matchreturns412 precondition_failed. Re-GET, merge your intent, and retry with a new idempotency key — there is no last-write-wins. - Replaying the same
Idempotency-Keywith the same body returns the original result (idempotentReplay: true), never a duplicate side effect. The same key with a different body returns a conflict. - A
412is never retried automatically by the SDK.
Error model
Errors use a stable envelope: code, message, requestId, details. Branch on code, never on message text.
| HTTP | code | Meaning — caller action |
|---|---|---|
| 400 | invalid_scope | Fix the token request scope; do not retry as-is |
| 400 | invalid_request | Malformed path, body, or headers; fix before retrying |
| 401 | invalid_client | Wrong credential pair; rotate or fix — never blind-retry |
| 401 | invalid_token | Token expired or revoked; refresh once, then investigate |
| 403 | insufficient_scope | Ask the administrator to grant the scope; no client-side workaround |
| 404 | open_api_capability_not_found | Route not published; do not probe internal paths |
| 412 | precondition_failed | ETag mismatch; re-GET and retry with a new idempotency key |
| 429 | rate_limit_exceeded | Back off exponentially with jitter; reduce concurrency |
Receiving webhooks
When installed events occur (for example assets.assignment.changed), AuraBoot delivers a signed POST to each registered webhook endpoint. Delivery is at-least-once; deduplicate on the event id.
Verify the signature over the raw request body before parsing it:
import { createHmac, timingSafeEqual } from 'node:crypto';
const timestamp = request.headers.get('X-Webhook-Timestamp');
const received = request.headers.get('X-Webhook-Signature');
const expected = `sha256=${createHmac('sha256', webhookSecret)
.update(`${timestamp}.${rawBody}`, 'utf8').digest('hex')}`;
const fresh = Math.abs(Date.now() / 1000 - Number(timestamp)) <= 300;
const valid = fresh && received?.length === expected.length
&& timingSafeEqual(Buffer.from(received), Buffer.from(expected));- Check the timestamp window first (5 minutes), compare signatures in constant time, then parse.
- Deduplicate using
X-Webhook-Delivery/ the eventid— retries reuse the same identifiers. - Failed deliveries retry with exponential backoff, land in a dead-letter queue, and can be replayed from the admin console. Webhook secrets are shown once and support overlapping rotation.
Injecting external events
A third-party system can push events into a tenant's automations through the installation-scoped event source:
curl -fsS -X POST "$AURABOOT_BASE_URL/api/open/v1/event-sources/$SOURCE_CODE/events" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Idempotency-Key: $STABLE_KEY" \
-H 'Content-Type: application/json' \
-d '{"type":"line.stopped","schemaVersion":1,"occurredAt":"2026-09-27T08:00:00Z","data":{}}'The endpoint authenticates, validates the schema, deduplicates, and enqueues — it never runs long automations inside the HTTP request. Register an automation with an external-event trigger to consume these events.
Credential rotation
Rotate by creating a new credential with a grace window (5 minutes to 7 days). During the window both credential pairs can mint tokens; after it expires the old one is rejected. Tokens issued before rotation live out their short TTL — for incident-level revocation use credential revoke or installation disable, which immediately invalidate active tokens.
TypeScript SDK
The SDK wraps the full flow — token caching, single refresh on 401, idempotency keys and If-Match preserved across write retries, no automatic retry after 412, and typed errors exposing status / code / requestId:
import { OpenPlatformClient } from "@auraboot/open-platform-sdk";
// Option A: client credentials (token cached and refreshed for you)
const client = new OpenPlatformClient({
baseUrl: process.env.AURABOOT_BASE_URL!,
clientId: process.env.AURABOOT_CLIENT_ID!,
clientSecret: process.env.AURABOOT_CLIENT_SECRET!, // secret manager, never bundled
scope: "assets.read assets.manage",
});
const page = await client.listResources("assets", 50);
const { resource, etag } = await client.getAssetVersioned(page.items[0]!.pid);
await client.assignAsset(resource.pid, "alice", crypto.randomUUID(), etag);Distribution status (2026-09): the SDK is not yet published to the npm registry. Until then, consume it from the monorepo source at packages/open-platform-sdk or from a packed tarball — do not npm install @auraboot/open-platform-sdk; it will not resolve. This note will be updated when the package ships.
Security rules
- TLS only. Never put tokens or secrets in URLs, query strings, logs, or browser/local storage.
- Never ship
client_secretin a frontend, mobile app bundle, or shared artifact — use a backend token exchange instead. - Treat the
client_secretas an OAuth credential only; it is not a business API key. - Rotate credentials on a schedule (90-day baseline) and use revoke/disable for incident response.
- Store
webhookSecretlike a password; rotate it the same way.