Endpoints

The sandbox harness lives under /sandbox/*: mint tokens, flip kill switches, inspect what the internal-API mirror stored, and proxy read-only requests to the real API. This page is the reference for those endpoints, plus the sandbox's token decision logic.

All examples use https://api.averase.com. Every /sandbox/* endpoint accepts the optional Authorization: Bearer <ApiToken> header described in the overview; without it you're in anonymous mode and supply account fields yourself. Unhandled errors return 500 {"success": false, "errors": [{"code": 7000, "message": "Internal Server Error"}]}.

The token row

Sandbox tokens are plain rows — the jti is the credential, standing in for the signed device token a real ISO carries.

Properties

  • Name
    prefix_id
    Type
    string
    Description

    Public identifier, ltok_….

  • Name
    jti
    Type
    string
    Description

    Unique UUID. This is what device calls present; it doubles as the row's key for PATCH /sandbox/tokens/:jti.

  • Name
    account_id
    Type
    string
    Description

    Owning account, acct_….

  • Name
    account_name
    Type
    string
    Description

    Display name of the account, echoed as account in verify responses.

  • Name
    account_active
    Type
    boolean
    Description

    Stand-in for the real system's subscription state. Set it to false to simulate a lapsed subscription.

  • Name
    user_email
    Type
    string
    Description

    Email of the user the token belongs to; the sandbox also uses it as the operator on wipe reports.

  • Name
    product
    Type
    string
    Description

    engine or hotswap.

  • Name
    status
    Type
    string
    Description

    active, revoked, suspended, or disabled.

  • Name
    quota_limit
    Type
    integer
    Description

    Maximum metered check-ins; null means unlimited.

  • Name
    use_count
    Type
    integer
    Description

    Metered check-ins consumed so far. Incremented atomically by POST /internal/tokens/validate.

  • Name
    last_seen_at
    Type
    timestamp
    Description

    Heartbeat, bumped by a successful verify.

  • Name
    revoked_at
    Type
    timestamp
    Description

    When the token was revoked, if ever.

  • Name
    created_at
    Type
    timestamp
    Description

    When the token was minted.


POST/sandbox/tokens

Mint a token

Creates a token row. Every body field is optional — with a valid API key attached, account_id, account_name, and user_email default to your real account.

Optional attributes

  • Name
    account_name
    Type
    string
    Description

    Account display name for the row.

  • Name
    account_id
    Type
    string
    Description

    Account identifier the token binds to.

  • Name
    user_email
    Type
    string
    Description

    Email stamped on the token (and used as operator in wipe reports).

  • Name
    product
    Type
    string
    Description

    engine or hotswap. Defaults to engine.

  • Name
    quota_limit
    Type
    integer
    Description

    Wipe quota. Defaults to null (unlimited).

  • Name
    status
    Type
    string
    Description

    Initial status. Defaults to active.

Request

POST
/sandbox/tokens
curl https://api.averase.com/sandbox/tokens \
  -H "Content-Type: application/json" \
  -d '{
    "account_name": "Test Co",
    "account_id": "acct_demo",
    "user_email": "ops@testco.example",
    "product": "engine",
    "quota_limit": 5
  }'

Response (201)

{
  "prefix_id": "ltok_01J4QY7R5N8T2VW9XA3BC4DEFG",
  "jti": "7f3d2a1c-9b4e-4c6d-8a5f-1e2b3c4d5e6f",
  "account_id": "acct_demo",
  "account_name": "Test Co",
  "account_active": true,
  "user_email": "ops@testco.example",
  "product": "engine",
  "status": "active",
  "quota_limit": 5,
  "use_count": 0,
  "last_seen_at": null,
  "created_at": "2026-08-12T16:58:44Z"
}

GET/sandbox/tokens

List tokens

Returns all token rows, newest first. Like every sandbox listing, it is not account-scoped — you see every row in the local database.

Request

GET
/sandbox/tokens
curl https://api.averase.com/sandbox/tokens

Response (200)

[
  {
    "prefix_id": "ltok_01J4QY7R5N8T2VW9XA3BC4DEFG",
    "jti": "7f3d2a1c-9b4e-4c6d-8a5f-1e2b3c4d5e6f",
    "status": "active",
    "use_count": 1
    // …
  }
]

PATCH/sandbox/tokens/:jti

Update a token

The kill switches. Flip any combination of the four fields to steer the next verify or validate down a deny path — this is the sandbox equivalent of the ISO control column, the staff kill switch, a lapsed subscription, and quota exhaustion all in one.

Optional attributes

  • Name
    status
    Type
    string
    Description

    active, revoked, suspended, or disabled.

  • Name
    quota_limit
    Type
    integer
    Description

    New quota; null for unlimited.

  • Name
    use_count
    Type
    integer
    Description

    Set the consumed count directly — the quick way to reach quota_exceeded without burning validates.

  • Name
    account_active
    Type
    boolean
    Description

    false simulates a lapsed subscription (deny reason account_inactive).

Returns the updated row (200), or 404 for an unknown jti.

The tabs on the right walk each deny path end to end: flip the switch, then watch the device call fail. A revoked, suspended, or disabled token — or a lapsed account, or an exhausted quota — is denied at verify AND at check-in, so an unentitled device never wipes. A cross-tenant probe (right token, wrong account_id) is indistinguishable from a missing token: 404 not_found.

Deny paths

PATCH
/sandbox/tokens/:jti
curl -X PATCH https://api.averase.com/sandbox/tokens/$JTI \
  -H "Content-Type: application/json" \
  -d '{"status": "revoked"}'

curl https://api.averase.com/internal/tokens/verify \
  -H "Content-Type: application/json" \
  -d "{\"jti\": \"$JTI\"}"
# → 403
# {
#   "allow": false,
#   "status": "revoked",
#   "reason": "revoked",
#   "quota_remaining": 3
# }

GET/sandbox/assets

Inspect assets

The sandbox's stand-in for the Inventory page: every asset upserted by POST /internal/assets, one row per physical machine per account, keyed by the machine's hardware_uid (system UUID + serial). Each row carries the full fingerprint document, any operator notes, the latest test_results snapshot, and the first_seen_at / last_booted_at timestamps.

Request

GET
/sandbox/assets
curl https://api.averase.com/sandbox/assets

Response (200)

[
  {
    "prefix_id": "asset_01J4QZ8T2M9V6XW3YB5NC7DEFG",
    "account_id": "acct_demo",
    "hardware_uid": "4c4c4544-0034-5a10-8052-b9c04f4b5933/74HRKY3",
    "fingerprint": { "system": { "vendor": "Dell Inc." } },
    "notes": null,
    "test_results": null,
    "first_seen_at": "2026-08-12T17:01:12Z",
    "last_booted_at": "2026-08-12T17:01:12Z"
  }
]

GET/sandbox/wipe_results

Inspect wipe results

The stand-in for the Wipe reports page: every row persisted by POST /internal/wipe_results — the drive and wipe fields as posted, the job_id (the idempotency key: one row per job), and the certificate_status (pending, generating, complete, or failed).

Request

GET
/sandbox/wipe_results
curl https://api.averase.com/sandbox/wipe_results

Response (200)

[
  {
    "prefix_id": "wr_01J4R2M8K3P7QS5TV9WX6YZABC",
    "job_id": "0e6f4a8c-2d1b-4e5f-9a7c-8b6d5e4f3a2b",
    "drive_serial": "S64FNE2R504071",
    "drive_model": "SAMSUNG MZVL2512HDJD-00BL2",
    "drive_capacity_gb": 512,
    "wipe_method": "NIST 800-88 R2 Purge",
    "wipe_status": "completed",
    "certificate_status": "complete"
    // …
  }
]

GET/sandbox/certificates

Inspect certificates

Every certificate issued, joined with its wipe result: prefix_id, reference (the CW-… string), account_name, signed_at, tamper_valid, wipe_result_id, drive_serial, and drive_model.

Request

GET
/sandbox/certificates
curl https://api.averase.com/sandbox/certificates

Response (200)

[
  {
    "prefix_id": "cert_01J4R2N9X0WQ4T8V2Y6Z1ABCDE",
    "reference": "CW-000001",
    "account_name": "Test Co",
    "signed_at": "2026-08-12T17:41:08Z",
    "tamper_valid": true,
    "wipe_result_id": "wr_01J4R2M8K3P7QS5TV9WX6YZABC",
    "drive_serial": "S64FNE2R504071",
    "drive_model": "SAMSUNG MZVL2512HDJD-00BL2"
  }
]

GET/certificates/:id/verify

Verify a certificate

The public certificate check, exactly as in production: no authentication, addressed by the certificate's cert_… id. valid reflects the stored tamper check.

Two shapes come back: 200 with the certificate's details when it exists, or 404 {"valid": false, "error": "not_found"} when it doesn't.

Request

GET
/certificates/:id/verify
curl https://api.averase.com/certificates/cert_01J4R2N9X0WQ4T8V2Y6Z1ABCDE/verify

Response (200)

{
  "valid": true,
  "certificate_number": "CW-000001",
  "account": "Test Co",
  "drive_serial": "S64FNE2R504071",
  "drive_model": "SAMSUNG MZVL2512HDJD-00BL2",
  "drive_capacity_gb": 512,
  "wipe_method": "NIST 800-88 R2 Purge",
  "passes_completed": null,
  "completed_at": "2026-08-12T17:40:58Z",
  "signed_at": "2026-08-12T17:41:08Z"
}

POST/sandbox/live_request

Proxy a live request

A GET-only proxy to the real web application API — the backend the /live console runs on. Useful for comparing sandbox rows against real account data without leaving the sandbox.

Required attributes

  • Name
    base_url
    Type
    string
    Description

    The real web application's base URL.

  • Name
    api_key
    Type
    string
    Description

    API key to send as the bearer token.

  • Name
    path
    Type
    string
    Description

    Path to GET. Must start with /api/ — anything else is rejected.

Optional attributes

  • Name
    query
    Type
    object
    Description

    Query parameters to append.

Errors: 422 for a bad base_url or a path outside /api/…; 502 when the target is unreachable.

Request

POST
/sandbox/live_request
curl https://api.averase.com/sandbox/live_request \
  -H "Content-Type: application/json" \
  -d '{
    "base_url": "https://averase.com",
    "api_key": "'"$API_KEY"'",
    "path": "/api/v1/me"
  }'

Response (200)

{
  "status": 200,
  "ok": true,
  "body": {
    // the real API's response, passed through
  }
}

Decision logic

Every token decision in the sandbox runs the same ladder as the real system's check-in decision, in this exact order — order matters, because the staff kill switch outranks everything else:

if (status === "disabled")  return "disabled";     // order matters — staff kill outranks all
if (status === "revoked")   return "revoked";
if (status === "suspended") return "suspended";
if (!account_active)        return "account_inactive";
if (quota_limit !== null && use_count >= quota_limit) return "quota_exceeded";
return "ok";

A missing token — or a supplied account_id that doesn't match the token's — short-circuits to not_found before the ladder runs. quota_remaining is null when unlimited, otherwise max(quota_limit - use_count, 0).

ReasonHTTPReturned when
disabled403Staff kill switch — outranks every other state
revoked403Token status is revoked
suspended403Token status is suspended
account_inactive403account_active is false (lapsed subscription)
quota_exceeded403quota_limit set and use_count has reached it
not_found404Unknown jti, or account_id supplied and mismatched

Two side effects to know when testing: a successful verify bumps only last_seen_at (a heartbeat — booting is unmetered), while a successful validate atomically increments use_count and reports quota_remaining after the increment. Denials also fire the wipe_session_blocked webhook — phase verify or checkin depending on which endpoint denied — which is how you test blocked-session notifications; see Webhook testing.

Was this page helpful?