Robot integration

Device tokens (Tier 1)

HS256 JWTs with per-token revocation. The starting point for any fleet.

Device tokens are the recommended auth for any new robot integration. They cost nothing to provision, they fit in a few kilobytes of robot config, and any compromised token can be revoked instantly without rotating the platform secret.

Token shape

A standard JWT signed with HS256:

header + payload (decoded)json
{
  "alg": "HS256",
  "typ": "JWT"
}
.
{
  "iss": "waveform-nav-pack",
  "sub": "robot:<uuid>",
  "customer_id": "<uuid>",
  "tier": "robot:tier1",
  "fleet_id": "depot-north",
  "scopes": ["nav_pack:read"],
  "iat": 1700000000,
  "nbf": 1700000000,
  "exp": 1731536000,
  "jti": "<uuid>"
}

Key fields:

  • sub β€” namespaced as robot:<uuid> so it can’t collide with human user ids.
  • customer_id β€” gets compared to robot_devices.owner_customer_id on every verify. Mismatch = 403.
  • scopes β€” closed enum: nav_pack:read and nav_pack:audit:read today.
  • jti β€” the revocation identifier. Lets us kill one token without rotating the HMAC key.
  • exp β€” default TTL is 30 days; min 1 minute, max 180 days.

Issuing a token

Admin-only endpoint. Returns the JWT exactly once.

http
POST /v1/robots/<robot_id>/tokens
Authorization: Bearer <admin-cognito-jwt>
Content-Type: application/json

{
  "scopes": ["nav_pack:read"],
  "ttl_seconds": 2592000
}

Response:

json
{
  "status": "ok",
  "data": {
    "jti": "...",
    "token": "eyJhbGciOiJIUzI1...",
    "issued_at": "2026-05-28T00:00:00Z",
    "expires_at": "2026-06-27T00:00:00Z",
    "scopes": ["nav_pack:read"]
  }
}

Cache the token on the robot at this moment β€” the server never returns the raw JWT again.

Using a token

Send it as a bearer token on any nav-pack read endpoint:

http
GET /v1/areas/<area_id>/nav-pack
Authorization: Bearer eyJhbGciOiJIUzI1...

The server verifies the signature, checks exp + nbf with 30 s leeway, confirms jti isn’t in the revocation set, and scopes the request to the token’s customer_id. ACL on every downstream query gets it for free.

Revoking a token

If a robot is lost / stolen, revoke its tokens individually:

http
POST /v1/robots/tokens/<jti>/revoke
Authorization: Bearer <admin-cognito-jwt>
Content-Type: application/json

{ "reason": "device reported stolen" }

The endpoint updates robot_device_tokens.revoked_at and pushes the jti into the in-process revocation deny-list for the API instance handling the request. Sibling instances pick up the revocation via a periodic refresh from the DB (default cadence: 5 min).

Cascade revocation
Retiring a robot via DELETE /v1/robots/<id> automatically revokes every active token issued to that robot, in one atomic transaction. You don’t need to revoke each jti separately.

Rotating the HMAC secret

The HMAC secret lives in AWS Secrets Manager at waveform/<env>/robot-token-hmac-key. To rotate:

  1. Stage the new secret as a separate version of the same secret. Production verifiers accept both during a TTL window.
  2. Re-issue tokens for every active robot, signed by the new version.
  3. After the issued-with-old-key tokens have all expired (max 30 days at default TTL), retire the old version.

Rotation does not require downtime; the verifier handles multi-version checking.

Threat model

Read the design doc for the full table; the short version:

  • Token theft from one robot β€” per-token jti revocation handles it.
  • Token replayed across customers β€” the server checks customer_id matches the robot’s row.
  • HMAC secret leaked β€” rotate via the dual- validate window above.
  • Robot impersonates another β€” jti + sub are unique; impersonation requires forging the HMAC.
Docs β€” WaveForm β€” WaveForm