---
title: "Mandate verification"
description: "@codespar/sdk/mandate: decode a mandate presentation token and check its Ed25519 signatures offline, with no API call."
---

<VersionBadge pkg="@codespar/sdk" />

`@codespar/sdk/mandate` is a separate entry point of the same package. It answers one question, and it answers it without the network: **is this mandate token signed by the agent and the issuer it claims?**

```ts twoslash
import { verifyMandateToken } from "@codespar/sdk/mandate";

const result = verifyMandateToken(process.env.MANDATE_TOKEN!, {
  agentPublicKey: process.env.AGENT_PUBLIC_KEY, // raw 32-byte Ed25519 key, hex
});

if (result.verified) {
  console.log(result.mandate.id, result.mandate.amount, result.agentDid);
}
```

<Callout type="info">
**Why offline matters.** The bank, the auditor and the counterparty can check a mandate without asking CodeSpar anything: the public keys come from the agent's `did:web` document. The org HMAC that also travels in the token is **not** offline-verifiable, because checking it needs the org secret; the Ed25519 pair is the part a third party can verify. The same check from a terminal is `codespar mandate verify`.
</Callout>

## `verifyMandateToken`

<SdkMethod name="verifyMandateToken" signature="verifyMandateToken(token: string, opts?: VerifyMandateOptions): MandateVerification" kind="read" />

Offline-verifies a V3 presentation token against the public keys you supply.

| Parameter | Type | Required | Description |
|---|---|---|---|
| `token` | `string` | yes | The presentation token: base64url JSON of the signed fields plus the signature envelope. |
| `opts.agentPublicKey` | `string \| Uint8Array` | no | Raw 32-byte Ed25519 agent key, hex or bytes. |
| `opts.issuerPublicKey` | `string \| Uint8Array` | no | Raw 32-byte Ed25519 issuer key, hex or bytes. |

Returns `MandateVerification`: `verified` (true only if at least one carried signature verified and none failed), the decoded `mandate`, the `agentDid` and `kid` when present, and one `SignatureCheck` for the agent and one for the issuer. Each check reports `verified`, `failed`, `skipped` (carried, but you passed no key for it) or `absent`.

It throws only when the token cannot be decoded, so a malformed token and a well-formed unverified one are different outcomes.

```ts twoslash
import { verifyMandateToken } from "@codespar/sdk/mandate";
const token = "eyJ0";
// ---cut---
const v = verifyMandateToken(token, {
  agentPublicKey: "7f3c…",
  issuerPublicKey: "a91b…",
});

v.agent.status; // "verified" | "failed" | "skipped" | "absent"
v.issuer.status;
v.mandate.expires_at; // UNIX seconds
```

## `decodeMandateToken`

<SdkMethod name="decodeMandateToken" signature="decodeMandateToken(token: string): MandateDecodeResult" kind="read" />

Splits the envelope from the signed fields and verifies nothing. Returns `{ ok: true, token }` or `{ ok: false, error }`, where `error` is `"invalid_payload"` or `"mandate_format_unsupported"`. Use it when you want to read a mandate you are not checking, for example to show scope and ceiling in a UI.

## `reconstructSigningString`

<SdkMethod name="reconstructSigningString" signature="reconstructSigningString(fields: Record<string, unknown>): string" kind="read" />

Rebuilds the canonical string the signatures cover: V3 is 14 fields and 13 `:` separators, V2 is the first 12. Absent optionals render empty so the separator count never changes, `purposes` is sorted lexicographically and comma-joined with escaping (`\` first, then `,`), and colons inside `agent_kid` are emitted verbatim because the string is serialized one way and never re-split.

You need this only to sign or to debug a signature; `verifyMandateToken` calls it for you.

## `verifyEd25519`

<SdkMethod name="verifyEd25519" signature="verifyEd25519(signingString: string, signatureB64url: string, pub: Buffer): boolean" kind="read" />

One raw signature check. Returns `false` on malformed input instead of throwing.

## `agentDidFromKid`

<SdkMethod name="agentDidFromKid" signature="agentDidFromKid(kid: string): string" kind="read" />

Strips the `#<fragment>` from a key id (`did:web:acme.example#1`) and gives back the bare agent DID, which is where you fetch the public keys.

## Types

`MandateFields` is the signed field set: `format_version`, `id`, `agent_id`, `type` (`payment`, `subscription` or `delegation`), `amount` as a decimal string, `currency`, `purposes`, `expires_at` in UNIX seconds, the optional `max_amount`, `parent_id` and `denomination`, `secret_version`, and the two V3-only fields `principal_kyc_ref` and `agent_kid`.

`DecodedMandateToken` adds the envelope: the org HMAC `signature`, and for V3 the `agent_sig`, `issuer_sig` and `kid`.

## The same check elsewhere

<NextStepsGrid items={[
  { label: "CONCEPT", title: "Mandates", description: "What a mandate authorizes, how it is granted, paused and revoked, and which routes check it.", href: "/docs/concepts/mandates" },
  { label: "CLI", title: "Mandates and governance", description: "codespar mandate verify: the same offline check from a terminal, plus create, issue and verification-status.", href: "/docs/cli/mandates-and-governance" },
  { label: "SDK", title: "Python", description: "codespar.mandate: verify_mandate_token and decode_mandate_token, the same two entry points.", href: "/docs/api/sdk/python" },
]} />
