Endpoint
Authorization: Bearer header. The key must carry the token_exchange scope. API keys are currently provisioned on request — email dan@sumvin.com to get access.
Request
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
pint | object | Yes | The PINT payload exactly as signed |
signature | string | Yes | Hex-encoded EIP-712 signature (65 bytes: 0x[0-9a-fA-F]{130}) |
audience | string | Yes | Registered identifier of the target third-party service. Must match the caller’s registered org. |
source_chat_message_id | integer | No | Links this PINT to an originating chat message. When set, the message must belong to the wallet’s owning user. |
enforcement_mode | string | No | One of strict (default) or advisory. Governs how downstream enforcement treats scope violations. |
pint object follows the canonical EIP-712 type structure. The scopes array must contain values from the scope catalog.
The SIS automatically determines the signer type based on whether the signing wallet has a registered agent key. You do not need to specify a signer type in the request — it will appear in the JWT claims after verification.
Linking a PINT to a Chat Message
To associate a PINT with a chat session message (for audit and provenance), includesource_chat_message_id:
Processing Pipeline
When you call the token exchange, the SIS runs the following validation pipeline before issuing a JWT:- Nonce idempotency check — if a PINT already exists for this
(wallet, nonce)pair, the exchange short-circuits to the idempotent / multi-audience path (see below). - Audience validation — confirms the
audiencematches the caller’s registered organisation. - Signer determination — detects whether the wallet has a registered agent key. If yes, the SIS generates the signature through the agent signing path; otherwise the caller-supplied signature is verified.
- Signature verification — reconstructs the EIP-712 hash and verifies the signature (ECDSA for user keys, EIP-1271 for agent/Safe keys).
- Nonce advancement — advances the wallet’s server-side nonce only after the signature is confirmed, so failed attempts do not burn nonces.
- Expiry re-check — rejects if
expires_athas passed during processing. - Scope parsing and registry validation — parses each scope string, validates it against the registry, and checks parameter types and ranges.
- KYC status check — only runs if any requested scope requires KYC. Rejects with
PINT-403-002if the user’s status is notverified. - Chat message validation — if
source_chat_message_idis set, confirms the message exists and belongs to this user. - PINT persistence — stores the PINT with status
active. - JWT generation — issues an audience-scoped, SIS-signed JWT and records the issuance.
Response — 201 Created
On success, the endpoint returns the JWT and PINT resource details:Location header points to the new PINT resource: /v0/sis/pint/sr:us:pint:abc123.
Response Fields
| Field | Type | Description |
|---|---|---|
sig | string | The SIS-signed PINT JWT |
sri | string or null | Sumvin Resource Identifier for the signer (null if the wallet is not bound to a Sumvin user) |
id | string | PINT SRI (e.g. sr:us:pint:abc123) |
audience | string | Audience the JWT was issued for |
scopes | array of strings | Authorised scopes for this PINT |
expires_at | integer | Token expiry as epoch seconds |
_links | object | hypermedia links — see below |
HAL Links
| Link | Method | Description |
|---|---|---|
self | POST | This endpoint — /v0/sis/token/pint |
pint | GET | PINT resource details |
pint_status | GET | PINT status (active, revoked, expired) |
pint_tokens | GET | List JWTs issued for this PINT (user-scoped, under /v0/pint) |
revoke | DELETE | Revoke the PINT (user-scoped, under /v0/pint) |
jwks | GET | Public keys to verify the JWT signature |
Multiple Audiences
A single PINT can produce multiple JWTs — one per target service provider. This supports purchase flows that span multiple providers. To issue a JWT for a second audience, call the exchange again with the same PINT but a differentaudience:
Idempotency — 208 Already Reported
If you call the exchange with the same PINT and the same audience, the endpoint is idempotent. It returns208 Already Reported with the previously issued token:
Error Responses
All errors follow RFC 7807 Problem Details:| Status | Code | When |
|---|---|---|
| 400 | PINT-400-002 | Audience is not a registered third-party identifier, or does not match the caller’s org |
| 400 | PINT-400-003 | Scope parameter is malformed or violates its declared type / range (e.g. max=abc, provider=stripe). detail names the offending scope and param. |
| 400 | PINT-400-004 | Scope name is not in the registry (unknown MVP scope). detail echoes the offending scope string. |
| 400 | PINT-400-005 | Scope string does not conform to the grammar (wrong segment count, wrong scheme, malformed query). detail echoes the offending scope string. |
| 401 | PINT-401-001 | EIP-712 signature verification failed (ECDSA) |
| 401 | PINT-401-002 | EIP-1271 verification failed against Safe contract |
| 403 | PINT-403-002 | User has not completed KYC verification. Only fires if at least one requested scope requires KYC. |
| 409 | PINT-409-001 | Nonce has already been used for this wallet |
| 409 | PINT-409-002 | PINT has been revoked |
| 410 | PINT-410-001 | PINT expires_at timestamp has passed |
| 422 | PINT-422-001 | source_chat_message_id is invalid — the message does not exist, belongs to another user, or was supplied without an authenticated user. detail identifies the offending message ID. |
| 422 | — | Malformed request body. Returned by FastAPI schema validation as its default ValidationError payload, not as a PINT-coded Problem Details response. |
| 424 | PINT-424-001 | EIP-1271 on-chain call failed (agent key verification) |
| 429 | PINT-429-001 | Rate limit exceeded |