Skip to main content
JWTs issued by the have expiry times, but a user can also revoke a PINT before its expiry. When a PINT is revoked, all JWTs issued against it are invalidated. As a verifier, you should check revocation status to avoid accepting credentials that the user has withdrawn.

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 PINT they’ve been presented with. No specific scope is required.

Request

Extract the PINT ID from the JWT’s pint_uri claim and call the status endpoint:
curl "https://sis.sumvin.com/v0/sis/pint/sr%3Aus%3Apint%3Aabc123/status" \
  -H "Authorization: Bearer <your-api-key>"
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().

Response — Valid

{
  "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

{
  "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

{
  "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" }
  }
}
FieldTypeDescription
idstringThe PINT’s SRI
validbooleantrue if the PINT is active and not expired; false if revoked or expired
reasonstring or nullnull when valid; "revoked" or "expired" when invalid
revoked_atinteger (epoch milliseconds) or nullTimestamp of when the PINT was revoked. null when the PINT is valid or has only expired without explicit revocation
_links.selfobjectLink back to this status endpoint
_links.pintobjectLink to the full PINT resource
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.

Error Responses

StatusError CodeDescription
401Missing or invalid API key.
404PINT-404-001No PINT exists for the given SRI. Response follows RFC 7807; see detail for the decoded SRI that failed lookup.

Implementation Examples

TypeScript

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

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 PINT 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 PINT is revoked or expired, it doesn’t come back. You can cache negative results for the duration of the JWT’s remaining lifetime.
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 PINT 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 PINTs that were revoked before their natural expiry.