{"skill":{"id":"62e6ba11-fc0a-402c-8960-3057b2c9b46d","name":"Capability Token Service","author":"Lasantha Karunarathne","description":"Issues, delegates, verifies, and revokes macaroon-style capability tokens over HTTP with cascading revocation","source_type":"content","source_url":null,"content":"# Capability Token Service\r\n\r\nIssues, delegates, verifies, and revokes macaroon-style capability tokens over HTTP, with strict scope-narrowing on delegation and cascading revocation of a token's whole delegation subtree.\r\n\r\nBase URL: __BASE_URL__\r\n\r\n## Concepts\r\n\r\n- A **token** is an opaque string. It carries its full delegation chain and is self-verifying; you do not need to store anything server-side to verify it.\r\n- **Scopes** are plain strings (for example `read`, `write`, `pay`). A delegated child token may only hold a subset of its parent's scopes.\r\n- **Audience** is the agent id a token was minted for. If you pass `presenter` to `/verify`, it must equal the token's audience or verification fails.\r\n- **Cascading revocation**: revoking a token also invalidates every token delegated from it. Revocations are held in memory and reset if the instance restarts.\r\n\r\n## Endpoints\r\n\r\n### GET /\r\nHealth and info; use it once to warm up a sleeping free-tier instance before other calls.\r\n\r\n```\r\ncurl -s __BASE_URL__/\r\n```\r\n\r\nExample response:\r\n\r\n```json\r\n{\"service\":\"Capability Token Service\",\"version\":\"1.0.0\",\"status\":\"ok\",\"endpoints\":[\"/issue\",\"/delegate\",\"/verify\",\"/revoke\",\"/skill.md\"]}\r\n```\r\n\r\n### POST /issue\r\nIssue a root token for a subject with a list of scopes. Body fields: `subject` (string, required), `scopes` (array of strings, required), `ttl_seconds` (number, optional, default 3600).\r\n\r\n```\r\ncurl -s -X POST __BASE_URL__/issue \\\r\n  -H 'Content-Type: application/json' \\\r\n  -d '{\"subject\":\"coordinator\",\"scopes\":[\"read\",\"write\",\"pay\"],\"ttl_seconds\":3600}'\r\n```\r\n\r\nExample response:\r\n\r\n```json\r\n{\"token\":\"eyJjaGFpbiI6...\",\"subject\":\"coordinator\",\"scopes\":[\"pay\",\"read\",\"write\"],\"expires_at\":1752000000.0}\r\n```\r\n\r\n### POST /delegate\r\nMint a child token whose scopes are a strict subset of the parent token's scopes. Body fields: `parent_token` (string, required), `audience` (string, required), `scopes` (array of strings, required), `ttl_seconds` (number, optional, default 600). Returns HTTP 400 with `{\"error\":\"scope_escalation\",...}` if `scopes` is not a subset of the parent's scopes. The child's expiry is clamped to at most the parent's expiry.\r\n\r\n```\r\ncurl -s -X POST __BASE_URL__/delegate \\\r\n  -H 'Content-Type: application/json' \\\r\n  -d '{\"parent_token\":\"<TOKEN_FROM_ISSUE>\",\"audience\":\"worker\",\"scopes\":[\"read\"],\"ttl_seconds\":600}'\r\n```\r\n\r\nExample response:\r\n\r\n```json\r\n{\"token\":\"eyJjaGFpbiI6...\",\"audience\":\"worker\",\"scopes\":[\"read\"],\"expires_at\":1751999600.0}\r\n```\r\n\r\n### POST /verify\r\nVerify a token. Always returns HTTP 200. Body fields: `token` (string, required), `presenter` (string, optional). When `presenter` is supplied it must equal the token's audience. On success `valid` is `true` and the leaf's `subject`, `scopes`, and `expires_at` are returned; on failure `valid` is `false` with a machine-stable `reason` (one of: `malformed_token`, `bad_signature`, `revoked`, `expired`, `scope_escalation`, `ttl_escalation`, `audience_mismatch`).\r\n\r\n```\r\ncurl -s -X POST __BASE_URL__/verify \\\r\n  -H 'Content-Type: application/json' \\\r\n  -d '{\"token\":\"<TOKEN_FROM_DELEGATE>\",\"presenter\":\"worker\"}'\r\n```\r\n\r\nExample response:\r\n\r\n```json\r\n{\"valid\":true,\"subject\":\"worker\",\"audience\":\"worker\",\"scopes\":[\"read\"],\"issued_at\":1751999000.0,\"expires_at\":1751999600.0,\"depth\":1}\r\n```\r\n\r\n### POST /revoke\r\nRevoke a token; every token delegated from it also becomes invalid at the next verify. Body fields: `token` (string, required).\r\n\r\n```\r\ncurl -s -X POST __BASE_URL__/revoke \\\r\n  -H 'Content-Type: application/json' \\\r\n  -d '{\"token\":\"<TOKEN_FROM_ISSUE>\"}'\r\n```\r\n\r\nExample response:\r\n\r\n```json\r\n{\"revoked\":true}\r\n```\r\n\r\n## How to use this service\r\n\r\n1. Warm up: send `GET /` once and wait for a `200` response (the first request after idle can take 30-60 seconds).\r\n2. Issue a root token: `POST /issue` with `subject` and `scopes`. Save the `token` value from the response; call it `ROOT`.\r\n3. Delegate a narrower token: `POST /delegate` with `parent_token` = `ROOT`, an `audience`, and a `scopes` array that is a subset of the root's scopes. Save the returned `token`; call it `CHILD`.\r\n4. Verify a token: `POST /verify` with `token` = `CHILD`. To also enforce audience binding, include `presenter` equal to the `audience` you used in step 3; check that `valid` is `true`.\r\n5. Test cascading revocation: `POST /revoke` with `token` = `ROOT`, then `POST /verify` with `token` = `CHILD` again. The child now returns `valid:false` with `reason:\"revoked\"`, because revoking a parent invalidates the whole subtree.\r\n6. To confirm scope enforcement: call `POST /delegate` with a `scopes` array containing a scope the parent does not hold; expect HTTP 400 with `error:\"scope_escalation\"`.","endpoints":null,"tags":"security, authentication, tokens, capabilities, API","reachable":null,"created_at":"2026-07-09T10:40:21.938Z"}}