> ## Documentation Index
> Fetch the complete documentation index at: https://docs.sumvin.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Revocation Checking

> How to check if a Stamped Mandate (and its JWTs) has been revoked

JWTs issued by the <Tooltip headline="SIS" tip="Sumvin Identity Service — the B2B API that exchanges signed PINTs for JWTs." cta="Glossary →" href="/glossary">SIS</Tooltip> have expiry times, but a user can also **revoke** a <Tooltip headline="Stamped Mandate" tip="A signed authorisation a user grants for specific scoped actions — delivered on the wire as a PINT." cta="Glossary →" href="/glossary">Stamped Mandate</Tooltip> before its expiry. When a mandate is revoked, all JWTs issued against it are invalidated immediately — not just at their next expiry.

Sumvin enforces this on its own endpoints: every API request that presents a Stamped Mandate JWT is checked against the mandate's live status. Once the mandate is revoked, the very next request presenting one of its tokens is refused with `401 Unauthorized` and error code `PINT-401-006` — there is no propagation delay. As a **verifier** running your own gate, you should also check revocation status so you can reject withdrawn credentials before you act on them, rather than relying solely on the calls you make to Sumvin.

<Snippet file="product-term-disambiguation.mdx" />

A Stamped Mandate is delivered over the API as a `PINT` (header `x-sumvin-pint-token`), so the status endpoint, the `pint_uri` claim, and the `PINT-4xx` error codes below all use the `PINT` identifier on the wire.

## Endpoint

```
GET /v0/sis/pint/{pint_id}/status
```

**Authentication:** API key via `Authorization: Bearer` header. This endpoint is deliberately permissive — any authenticated third-party caller with a valid SIS API key can check the status of any Stamped Mandate they've been presented with. No specific scope is required.

## Request

Extract the mandate's identifier from the JWT's `pint_uri` claim and call the status endpoint:

```bash theme={null}
curl "https://sis.sumvin.com/v0/sis/pint/sr%3Aus%3Apint%3Aabc123/status" \
  -H "Authorization: Bearer <your-api-key>"
```

<Warning>
  The SRI contains colons that must be URL-encoded as `%3A` in the path. Most HTTP libraries handle this automatically with `encodeURIComponent()` or `urllib.parse.quote()`.
</Warning>

## Response — Valid

```json theme={null}
{
  "id": "sr:us:pint:abc123",
  "valid": true,
  "reason": null,
  "revoked_at": null,
  "_links": {
    "self": { "href": "/v0/sis/pint/sr%3Aus%3Apint%3Aabc123/status" },
    "pint": { "href": "/v0/sis/pint/sr%3Aus%3Apint%3Aabc123" }
  }
}
```

## Response — Revoked

```json theme={null}
{
  "id": "sr:us:pint:abc123",
  "valid": false,
  "reason": "revoked",
  "revoked_at": 1739443800000,
  "_links": {
    "self": { "href": "/v0/sis/pint/sr%3Aus%3Apint%3Aabc123/status" },
    "pint": { "href": "/v0/sis/pint/sr%3Aus%3Apint%3Aabc123" }
  }
}
```

## Response — Expired

```json theme={null}
{
  "id": "sr:us:pint:abc123",
  "valid": false,
  "reason": "expired",
  "revoked_at": null,
  "_links": {
    "self": { "href": "/v0/sis/pint/sr%3Aus%3Apint%3Aabc123/status" },
    "pint": { "href": "/v0/sis/pint/sr%3Aus%3Apint%3Aabc123" }
  }
}
```

| Field         | Type                                 | Description                                                                                                                 |
| ------------- | ------------------------------------ | --------------------------------------------------------------------------------------------------------------------------- |
| `id`          | string                               | The Stamped Mandate's SRI                                                                                                   |
| `valid`       | boolean                              | `true` if the mandate is active and not expired; `false` if revoked or expired                                              |
| `reason`      | string or null                       | `null` when valid; `"revoked"` or `"expired"` when invalid                                                                  |
| `revoked_at`  | integer (epoch milliseconds) or null | Timestamp of when the mandate was revoked. `null` when the mandate is valid or has only expired without explicit revocation |
| `_links.self` | object                               | Link back to this status endpoint                                                                                           |
| `_links.pint` | object                               | Link to the full Stamped Mandate resource                                                                                   |

<Note>
  Timestamps default to epoch milliseconds. To receive ISO 8601 strings instead (e.g., `"2026-02-13T11:30:00Z"`), send the header `X-Timestamp-Format: iso8601`. This applies to `revoked_at` and any other `_at` fields in the response.
</Note>

## Error Responses

| Status | Error Code     | Description                                                                                                                  |
| ------ | -------------- | ---------------------------------------------------------------------------------------------------------------------------- |
| `401`  | —              | Missing or invalid API key.                                                                                                  |
| `404`  | `PINT-404-001` | No Stamped Mandate exists for the given SRI. Response follows RFC 7807; see `detail` for the decoded SRI that failed lookup. |

## Implementation Examples

### TypeScript

```typescript theme={null}
interface RevocationStatus {
  id: string;
  valid: boolean;
  reason: "revoked" | "expired" | null;
  revoked_at: number | null;
}

async function checkRevocation(pintUri: string): Promise<boolean> {
  const response = await fetch(
    `https://sis.sumvin.com/v0/sis/pint/${encodeURIComponent(pintUri)}/status`,
    { headers: { Authorization: `Bearer ${process.env.SIS_API_KEY}` } }
  );

  if (!response.ok) {
    throw new Error(`Revocation check failed: ${response.status}`);
  }

  const status: RevocationStatus = await response.json();
  return status.valid;
}

// Usage in your request handler
const pintUri = jwtPayload.pint_uri as string;
const isValid = await checkRevocation(pintUri);
if (!isValid) {
  return new Response("PINT has been revoked", { status: 403 });
}
```

### Python

```python theme={null}
import requests
from urllib.parse import quote

def check_revocation(pint_uri: str, api_key: str) -> bool:
    response = requests.get(
        f"https://sis.sumvin.com/v0/sis/pint/{quote(pint_uri, safe='')}/status",
        headers={"Authorization": f"Bearer {api_key}"},
    )
    response.raise_for_status()

    status = response.json()
    return status["valid"]

# Usage
pint_uri = jwt_payload["pint_uri"]
if not check_revocation(pint_uri, SIS_API_KEY):
    raise PermissionError("PINT has been revoked")
```

## Caching Guidance

The revocation check endpoint is designed for low latency (\< 100ms p99) and responses are already cached server-side for 30 seconds. You should still cache on your side to avoid excessive calls:

**Recommended approach:** Cache the `valid: true` response for **30 seconds**. This matches the server-side cache TTL and balances freshness (revocations propagate within 30s) with throughput.

**Do not cache indefinitely.** A Stamped Mandate that was valid at verification time may be revoked seconds later. The 30-second TTL provides a reasonable window.

**Cache `valid: false` longer.** Once a Stamped Mandate is revoked or expired, it doesn't come back. You can cache negative results for the duration of the JWT's remaining lifetime.

```typescript theme={null}
import { LRUCache } from "lru-cache";

const revocationCache = new LRUCache<string, boolean>({
  max: 10000,
  ttl: 30 * 1000, // 30 seconds for valid results
});

async function checkRevocationCached(pintUri: string): Promise<boolean> {
  const cached = revocationCache.get(pintUri);
  if (cached !== undefined) return cached;

  const isValid = await checkRevocation(pintUri);
  revocationCache.set(pintUri, isValid, {
    ttl: isValid ? 30 * 1000 : 300 * 1000, // 30s for valid, 5min for revoked
  });
  return isValid;
}
```

## When to Check

**Always check** on the first request for a given Stamped Mandate in a session. After that, your cache handles subsequent checks within the TTL.

**Skip the check** if you don't have an SIS API key. JWT signature verification and expiry checking are still valid without revocation checking — you just won't catch mandates that were revoked before their natural expiry.
