§ GUIDE · QUICKSTART · 5 MINUTES

Your first verified action in five minutes.

A focused hello-world walkthrough — from pre-flight probe to a signed attestation in your inbox. cURL only, no SDK.

Audience · Anyone with an API key Time · ~5 minutes Stack · cURL + a phone

§ 01 · What you need

This walkthrough assumes you have already received your tenant's credentials. If you have not, ask your Paphwey contact for these three things — nothing else.

  • An X-API-Key with the agent:delegate scope. Production keys are prefixed sk_live_…; sandbox tenants are issued sk_test_… against the same endpoints.
  • The email of a principal you can approve on (your own phone is fine — the wallet flow is at /link-device/).
  • A terminal with curl on it.
Base URL. Every example below uses https://www.paphwey.com. There is no separate api. subdomain — the gateway serves both the public site and the API from the same canonical host.

§ 02 · Pre-flight (~30 seconds)

Before you mint anything, two probes confirm the gateway is up and your key resolves to a tenant. Skip these and you'll spend an hour debugging permission_denied errors that turned out to be wrong-key copy-paste.

1. Liveness — GET /healthz

No auth. 200 if the gateway is up.

$ curl -i https://www.paphwey.com/healthz
HTTP/1.1 200 OK
Content-Type: application/json

{"status": "live", "message": "Backend is responsive"}

2. Identify yourself — GET /api/v1/whoami

Auth required. Returns the relying party your key resolves to, the credential's scopes, and — most importantly — the policy codes you may pass as challenge_type.

$ curl https://www.paphwey.com/api/v1/whoami \
    -H "X-API-Key: sk_live_abcd.deadbeef..."

{
  "relying_party": {
    "id": "549ea1ac-b19f-4cf8-9691-f0a787582491",
    "name": "InternalDemo",
    "slug": "internal-demo",
    "status": "active",
    "is_sandbox": false,
    "is_active": true
  },
  "credential": {
    "prefix": "sk_live_abcd",
    "name": "primary",
    "scopes": ["agent:delegate", "challenge:create"],
    "expires_at": null,
    "last_used_at": "2026-04-25T08:42:00Z"
  },
  "environment": {"base_url": "https://www.paphwey.com"},
  "policies": {
    "count": 3,
    "codes": [
      "AGE_VERIFICATION_REQUIRED",
      "HIGH_VALUE_PURCHASE_REQUIRED",
      "LOGIN_ASSURANCE"
    ]
  }
}
If policies.codes is empty, your tenant has an allowed_policy_codes allowlist that filters out the global defaults — ping your Paphwey contact to widen it. Don't waste time guessing codes; the rest of the flow will fail with policy_not_found until this is resolved.

§ 03 · Mint a delegation (~30 seconds)

A delegation is a signed authority you grant to a named agent — bounded by scope, a spending ceiling, and an expiry. Creating it does not prompt the user; it only registers the authority.

Use one of the policy codes you saw in policies.codes above. We'll use HIGH_VALUE_PURCHASE_REQUIRED — a £25.00-ceiling purchase delegation valid for one week.

$ curl -X POST https://www.paphwey.com/api/v1/agent/delegations \
    -H "X-API-Key: sk_live_abcd.deadbeef..." \
    -H "Content-Type: application/json" \
    -d '{
      "principal_email": "you@example.com",
      "provider": "openai",
      "agent_id": "quickstart-agent",
      "allowed_scopes": ["purchase"],
      "allowed_challenge_types": ["HIGH_VALUE_PURCHASE_REQUIRED"],
      "max_amount_minor": 2500,
      "currency": "GBP",
      "valid_until": "2026-05-02T00:00:00Z"
    }'

{
  "delegation_id": "c5a3b2e1-7d4f-4e9a-b1c2-3d4e5f6a7b8c",
  "status": "active",
  "credential_jwt": "eyJhbGciOiJSUzI1NiIs...",
  "credential_kid": "plan3-abcd",
  "agent_did": "did:key:z6Mk...",
  "present_token": "eyJ... short-lived hint ...",
  "present_token_expires_at": "2026-04-25T09:30:00Z",
  "principal_type": "HUMAN_DELEGATED"
}

Save delegation_id — you will reference it on the next call. The other fields are useful for audit and offline verification but are not needed for the rest of this quickstart.

Picking max_amount_minor. For monetary scopes, this is your hard ceiling per action, in minor units (pence / cents). For non-monetary policies like AGE_VERIFICATION_REQUIRED, pass 0.

§ 04 · Open a challenge (~30 seconds)

Now present the delegation against an actual action. The response carries an approval_url — a single-use link your user opens on their phone.

$ DELEGATION_ID="c5a3b2e1-7d4f-4e9a-b1c2-3d4e5f6a7b8c"

$ curl -X POST \
    https://www.paphwey.com/api/v1/agent/delegations/$DELEGATION_ID/challenge \
    -H "X-API-Key: sk_live_abcd.deadbeef..." \
    -H "Content-Type: application/json" \
    -d '{
      "challenge_type": "HIGH_VALUE_PURCHASE_REQUIRED",
      "audience": "quickstart.example",
      "nonce": "first-call-nonce",
      "payload": {
        "action_context": {
          "scope": "purchase",
          "amount_minor": 1500,
          "currency": "GBP"
        },
        "minimum_assurance": 10
      }
    }'

{
  "challenge_id": "a277c8e4-1234-4abc-b9d0-fedcba012345",
  "status": "AWAITING_APPROVAL",
  "approval_url": "https://www.paphwey.com/link-device/?challenge_id=a277c8e4-...&approval_id=ap_8b2...",
  "poll_url": "https://www.paphwey.com/api/v1/orchestration/challenges/a277c8e4-.../",
  "expires_at": "2026-04-25T09:35:00Z"
}
Two URLs, two jobs. approval_url is the user-facing link — open it on a phone or render a QR. The Paphwey link-device flow lives on the same host, so there is no extra wallet domain to whitelist. poll_url is your machine-facing endpoint — poll it (1-second floor) until status flips.

Save the approval_url and poll_url for the next two steps.

§ 05 · Approve on the phone (~60 seconds)

Open approval_url on your phone — or paste it into a second browser tab if that's quicker for the first run-through. You'll see the Paphwey link-device page asking the principal to approve the action.

Meanwhile, your backend polls poll_url. The status flow is:

  • AWAITING_APPROVAL — initial state, waiting for the user.
  • APPROVED — terminal happy-path. Response carries the attestation_token.
  • DENIED — the user explicitly declined.
  • EXPIRED — TTL elapsed before approval.
$ POLL_URL="https://www.paphwey.com/api/v1/orchestration/challenges/a277c8e4-.../"

$ curl $POLL_URL -H "X-API-Key: sk_live_abcd.deadbeef..."

# Before approval:
{"challenge_id": "a277...", "status": "AWAITING_APPROVAL", ...}

# After you tap "Approve" on the phone:
{
  "challenge_id": "a277...",
  "status": "APPROVED",
  "attestation_token": "eyJhbGciOiJSUzI1NiIs..."
}
For dev only. No callback_url in the request above — Paphwey can't reach your laptop. Polling is the correct shape for development. In production, set callback_url on the challenge and have Paphwey POST the outcome envelope to a public endpoint.

§ 06 · Verify the outcome (~30 seconds)

The attestation_token you got back is a JWS — never trust its claims without re-verifying against the gateway. The /api/v1/agent/outcomes/verify endpoint does the verification for you and returns a clean, structured envelope.

$ TOKEN="eyJhbGciOiJSUzI1NiIs..."

$ curl -X POST https://www.paphwey.com/api/v1/agent/outcomes/verify \
    -H "X-API-Key: sk_live_abcd.deadbeef..." \
    -H "Content-Type: application/json" \
    -d "{
      \"attestation_token\": \"$TOKEN\",
      \"audience\": \"quickstart.example\",
      \"expected_delegation_id\": \"$DELEGATION_ID\",
      \"expected_nonce\": \"first-call-nonce\"
    }"

{
  "valid": true,
  "claims": {
    "sub": "principal-uuid",
    "challenge_id": "a277...",
    "nonce": "first-call-nonce"
  },
  "delegation": {
    "id": "c5a3...",
    "agent_did": "did:key:...",
    "allowed_scopes": ["purchase"],
    "status": "active"
  },
  "actor_chain": {
    "principal_id": "...",
    "agent": {"provider": "openai", "agent_id": "quickstart-agent"}
  },
  "receipt_id": "rcpt-...",
  "kyc_reference": ""
}
The valid field is your gate. If it's true, the principal approved the action under the delegation, and the audience / nonce / delegation match what you expected. If it's false, reject the action — the body carries an error_code describing why.

That's the complete loop. Probe → Mint → Approve → Verify. Everything else in the Paphwey API is composition over these four shapes.

§ 07 · Where it goes wrong

The five errors first-time integrators hit, and what each one really means.

error_codeWhat it actually meansFix
authentication_failed
401
Bad or missing X-API-Key. The header is wrong, the key was rotated, or you copied the truncated prefix instead of the full prefix.secret. Re-run GET /api/v1/whoami with the same key — if that 401s, the key itself is bad.
permission_denied
403
The credential authenticates but lacks the agent:delegate scope. Check credential.scopes in the whoami response. Ask your account manager to add the missing scope.
policy_not_found
400
The challenge_type string you passed has no matching active policy on the tenant. Check policies.codes in whoami. The exact string must appear there. Codes are case-sensitive.
policy_requirement_not_met
400
The policy is found, but the request doesn't satisfy it — usually a missing scope on the delegation, or a minimum_assurance below the policy floor. Compare your delegation's allowed_scopes with what the policy requires. Bump minimum_assurance if the response details mention assurance.
validation_error
400
Field is missing, malformed, or fails an expected-vs-actual check (e.g. audience mismatch on verify). The response's details object lists the offending field. Most often: forgot audience on verify, or passed amount_minor as a decimal instead of integer minor units.

Every error response carries a correlation_id — quote it if you ask Paphwey to debug a specific failure. Don't pattern- match on the human-readable message; key off error_code.

§ 08 · Where to go next

You've completed the loop. Here's where to go for the production wiring.

Full REST guide

Webhooks, key-bound delegations, revocation, error envelopes, pre-ship checklist.

Open ↗

Python SDK

Typed sync + async clients, FastAPI / Django / Celery patterns.

Open ↗

Web SDK

Server-side TypeScript for Node, Next.js, Bun, Edge.

Open ↗

MCP server

Drop the paphwey-mcp tool set into Claude Desktop or any MCP client.

Open ↗

Swagger UI

Live, machine-readable contract for every endpoint.

Open ↗

Agent API reference

One-page text reference for the triad — shapes, errors, rate limits.

/api/docs/

Stuck?

Quote your correlation_id — we'll find it.