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:

  1. The tenant comes from the token. There is no X-Tenant-Id header and no tenant query parameter. Clients cannot switch tenants; every token is bound to one installation of your application in one tenant + environment.
  2. 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.
  3. Your secret is not an API key. The client_secret is 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):

  1. Create the application. The creator automatically becomes its first owner.
  2. Install it into a tenant/environment (development, staging, or production) 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.
  3. Create a credential. The client_id / client_secret pair 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.
  • nextCursor is 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-Match returns 412 precondition_failed. Re-GET, merge your intent, and retry with a new idempotency key — there is no last-write-wins.
  • Replaying the same Idempotency-Key with 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 412 is never retried automatically by the SDK.

Error model

Errors use a stable envelope: code, message, requestId, details. Branch on code, never on message text.

HTTPcodeMeaning — caller action
400invalid_scopeFix the token request scope; do not retry as-is
400invalid_requestMalformed path, body, or headers; fix before retrying
401invalid_clientWrong credential pair; rotate or fix — never blind-retry
401invalid_tokenToken expired or revoked; refresh once, then investigate
403insufficient_scopeAsk the administrator to grant the scope; no client-side workaround
404open_api_capability_not_foundRoute not published; do not probe internal paths
412precondition_failedETag mismatch; re-GET and retry with a new idempotency key
429rate_limit_exceededBack 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 event id — 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_secret in a frontend, mobile app bundle, or shared artifact — use a backend token exchange instead.
  • Treat the client_secret as 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 webhookSecret like a password; rotate it the same way.