DOCS / SDK & API REFERENCE
Request, verify, and deliver.
Use hosted signers to derive keys, authorize exact requests and retrieve verifiable signatures.
Hosted API v2
Base URL: https://pairkey.com/api/v2. POST JSON with X-PairKey-Client: 1. The header is request hygiene, not signing authority. Use HTTPS outside loopback. Never send a wallet private key to the API.
| Endpoint | Purpose |
|---|---|
| GET /config | Deployment, signer identities, configured example program, supported sources and limits. |
| POST /namespaces | {identity, namespace_id}: public metadata and two attestations; no registration transaction. |
| POST /keys | {registration, chain, path}: derived public key and suite certificate. |
| POST /approvals | {signing_request, identity}: exact request, current rules, payload and authorization bytes. |
| POST /jobs | {signing_request, approval: {request, proof}}: privately authenticate at both signers and submit. |
| GET /jobs/:id | Read working, complete or failed/uncertain status. |
| POST /jobs/:id/recover | Read existing public results; cannot start another signing attempt. |
| POST /verify | Fresh signature verification for chain, key, message and signature. |
| POST /rpc | Bounded devnet RPC for explicit wallet transactions and chain reads. |
signing_request is {key: {registration, chain, path}, request_id, message}. Byte arrays use JSON integer arrays. Chain names are solana, evm, zcash, near and bitcoin. Use the typed SDK instead of hand-encoding cryptographic envelopes.
Browser and Rust SDKs
Download the browser SDK, keep its three modules together, and provide @solana/web3.js v1. The client recomputes canonical request bytes, checks namespace attestations and wallet signatures, and validates complete policy/commit instruction bundles before a wallet transaction.
PairKey.onboard → key → prepare → recovery → approve → submit → wait is the account flow. Program on-chain mode adds commitProgram before submission. Passkey/OIDC sources use ephemeralIdentity. See the source guides for account, program, passkey and Google examples.
The Rust hosted client exposes offchain_namespace, offchain_key, prepare_offchain and typed submission/recovery. hosted::offchain::verify_result verifies the chain signature, key context and both signer receipts against the original approval. Browser verification of Ed25519 and request context is not a full independent multisuite verifier. Use the offline Rust example for that.
Exact approval and versions
The approval binds identity, namespace registration digest, request ID, destination/path, payload hash and length, policy revision/digest, issued time and expiry. The wallet signs a domain-separated ASCII digest message. Default validity is five minutes. Never sign bytes returned by a server without checking them against the original intended request.
The HTTP API is v2; the approval structure has its own version 1, and multisuite protocol envelopes have version 2. These are separate version fields. Existing v1 routes and legacy derivation encodings remain compatible.
Bound passkey and OIDC identities
A binding includes the identity digest, namespace digest, ephemeral Ed25519 public key and a validity interval of at most ten minutes. Create it before preparing the request so it covers the request lifetime. The ephemeral key signs the exact approval; sessions are not enabled.
const namespace = await pairkey.onboard(undefined, { identity });
const key = await pairkey.key(namespace, 'solana', [0]);
const ephemeral = await ephemeralIdentity(namespace);
// Passkey: assertion challenge = ephemeral.challenge.
// OIDC: login nonce = ephemeral.oidcNonce; use the configured issuer/audience.
const prepared = await pairkey.prepare(namespace, undefined, {
chain: 'solana', path: [0], message: 'My exact message'
});
const recovery = await pairkey.recovery(prepared); // save public metadata first
const submission = await ephemeral.approve(prepared, key, { assertion });
// For OIDC use { idToken } instead of { assertion }.
const { job_id } = await pairkey.submit(submission);
const result = await pairkey.wait(job_id, { prepared, key });Passkey identity includes the credential’s compressed P-256 public key, RP ID and origin. Each signer checks origin, RP ID, presence, verification and the binding challenge. OIDC identity includes issuer, audience and subject; each signer verifies the configured issuer’s RS256 token, identity/time claims and nonce using an operator-pinned HTTPS JWKS endpoint. Authentication proofs are private inputs, not public receipt fields.
Policy and program endpoints
| POST endpoint | Purpose |
|---|---|
| /policies | Read rules for a registration. |
| /policies/prepare | Prepare the exact next rules revision for an identity. |
| /policies/instructions | Construct unsigned owner-authorized native verification and gateway instructions. |
| /programs/example | Resolve example program/owner/mode into its source and policy address. |
| /programs/example/policy/read | Read application policy, or null if absent. |
| /programs/example/policy | Construct an explicit owner-signed application policy edit. |
| /programs/example/commit | Construct the exact on-chain approval transaction instruction. |
Policy endpoints construct instructions; they do not spend the hosted wallet’s funds. Wallets explicitly send policy edits and program commits. Read Policy & rules for field meanings, identity proofs and costs. Canonical types are in sdk/src/hosted/offchain.rs and sdk/src/hosted/offchain/management.rs.
Recovery without another signature
Save the public prepared request, key, deployment and deterministic job ID before submission. Retrying the exact authenticated submission returns its retained job, but a page reload should use public-only POST /jobs/:id/recover and poll the same ID. Recovery does not need a JWT, assertion, caller signature, new approval or fresh randomness.
Interrupted jobs become uncertain. Recovery succeeds only when both workers’ saved public receipts establish the original result. If no job reached the service, recovery reports an unknown job. Do not silently create a replacement request; uncertainty is not proof that no signature exists. A saved result remains verifiable after approval expiry.
Interpret errors at the right stage
Before submission, malformed bytes, expired authentication or changed rules stop the request. After submission begins, a lost response or failure can leave an uncertain outcome. Hosted errors include an error string; job failures also carry uncertainty. Do not label an HTTP failure as a refusal by both signers.
Rate limits return HTTP 429. Respect backoff and retain the exact request/job ID. The service does not automatically create another signing attempt. Policy revision conflicts require reading current rules and deliberately preparing a new edit.
Durability and limits
Hosted jobs retain public request metadata, proof commitments and completed results, with at most 1,024 retained hosted jobs and no automatic eviction. Raw messages are limited to 256 UTF-8 bytes; the API has bounded body sizes, a shared 120-call/minute limit and a signing/enrollment gate. Save verified transcripts in your application. Public metadata is not confidential storage.
Worker and coordinator request claims survive ordinary restarts. File locking and atomic snapshots do not establish distributed rollback protection. Never reset roots, journals or incident latches to retry an uncertain job.
Internal coordinator and legacy APIs
Internal worker routes are not public hosted APIs. The coordinator’s off-chain path privately distributes authentication, reserves durable request claims and recovers only original receipts. The hosted service adds its own durable public job journal.
The older /api/v1 hosted API keeps registration and exact COMMIT behavior for compatible clients. Registered-policy and native-transfer sessions use separate types. Do not send a legacy session as a v2 identity proof.
Legacy lower-level integration
The self-hosted multi-suite API retains /multi/keys, /jobs with a signing envelope, job polling/events/cancellation and /multi/sign. Its older retention and explicit-attempt semantics differ from v2 durable approval claims. fresh_attempt is not a recovery API for v2. See repository docs/program-jobs-and-lifecycle.md and program_jobs.rs for this compatibility path.