Call a capability

The gateway is the only endpoint an integrator needs to know about to spend money through Lipafy. Every capability — whether it’s a food-order API, an airtime top-up service, or a private internal endpoint — is invoked the same way:

ANY-METHOD https://api.lipafy.xyz/gateway/<slug>/<sub-path>
Authorization: Bearer <lipafy-credential>
Content-Type: <whatever the upstream expects>
<body>

That’s the entire contract. Lipafy authenticates you, holds the price from your wallet, forwards your request to the upstream provider, and returns the upstream’s response augmented with payment headers.

Five-second example

$curl -X POST https://api.lipafy.xyz/gateway/eats.local-lunch.order/orders \
> -H "Authorization: Bearer lip_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
> -H "Content-Type: application/json" \
> -d '{"items":["chapati","beans"],"delivery_to":"Wilson"}'

You — the integrator — never see the upstream’s API key. The provider — Local Lunch — never sees your wallet. Lipafy is the trust boundary.

What happens behind the scenes

1

Authenticate

The gateway accepts three credential types, in priority order:

  1. API keyAuthorization: Bearer lip_live_... (most agent-to-server use)
  2. OAuth 2.1 access tokenAuthorization: Bearer <jwt> (third-party connected apps)
  3. Dashboard session cookieCookie: lipafy_session=... (only when calling from the dashboard frontend)

See Authentication for which to use when.

2

Resolve capability + check status

Lipafy looks up the slug. If it’s draft or disabled, the request fails with 422 capability_disabled. If the slug doesn’t exist at all → 404 capability_not_found.

3

Run spending controls

The grant attached to your credential (API key grants, OAuth grants, or your account_default grant for direct sign-in) carries spending controls:

  • Daily cap
  • Per-transaction cap
  • Allow / block lists on slugs
  • Escalation threshold (requires human approval above N KES)

Any cap exceeded → 402 with a code: daily_limit_exceeded, transaction_limit_exceeded, capability_blocked, capability_not_allowed. See Grants and controls.

4

Maybe pause for human approval

If the call is above your escalation threshold, the gateway responds with:

1HTTP/1.1 402 Payment Required
2Content-Type: application/json
3
4{
5 "error": "approval_required",
6 "message": "Human approval required before this call can proceed",
7 "details": {
8 "approval_id": "uuid",
9 "amount": "5000",
10 "currency_code": "KES"
11 }
12}

Your client should pause, present the pending approval to the human (via your UI, the Lipafy dashboard at /approvals, or an SSE listener on /v1/notifications), and retry the same request once the approval is granted. The approval_id is consumed atomically on the next call — exactly-once. See Approvals.

5

Hold price from wallet

Lipafy debits the price (e.g. 500 KES) from your wallet’s available balance and writes it to held_balance. If available < price402 insufficient_balance. The hold is reversible right up until step 7.

6

Forward to upstream

Lipafy issues the upstream call:

  • Method: same as yours.
  • Path: everything after /gateway/<slug>/ is appended to the capability’s endpoint_url.
  • Headers: yours pass through, except Authorization, X-Payment-Authorization, Cookie, and hop-by-hop headers are stripped. The capability’s configured auth (bearer / api_key / basic / none) is injected.
  • Body: byte-for-byte.
7

Settle the hold

  • Upstream returned 2xx → confirm the hold (your wallet’s held_balance → debit, provider’s wallet credit minus revenue share), write a signed receipt to capability_requests.
  • Upstream returned 4xx or 5xx → release the hold (held → available, no charge), no receipt.

Either way, the upstream’s response — status code, headers, body — is what you receive, augmented with payment headers.

What you get back

1HTTP/1.1 200 OK
2Content-Type: application/json
3X-Payment-Reference: cr_8f3a...
4X-Payment-Amount: 500
5X-Payment-Currency: KES
6X-Payment-Receipt: https://api.lipafy.xyz/v1/receipts/cr_8f3a...
7
8{ ...the upstream's response body, unchanged... }
  • X-Payment-Reference — the capability_request row id. Stable across retries; your idempotency anchor.
  • X-Payment-Amount / X-Payment-Currency — what was actually charged. May differ from the capability’s listed price if pricing dimensions change in future.
  • X-Payment-Receipt — fetchable signed receipt for audit. See Receipts.

Error responses, at a glance

Statuserror codeWhen
401unauthenticatedNo / bad credential
401invalid_api_key / api_key_revoked / api_key_expiredAPI key problem
402approval_requiredAbove escalation threshold; user must approve
402insufficient_balanceWallet doesn’t cover the price
402daily_limit_exceeded / transaction_limit_exceededSpending cap hit
402capability_blocked / capability_not_allowedAllow/block list rejected this slug
404capability_not_foundSlug doesn’t exist
422capability_disabledSlug exists but is draft or disabled
502upstream_errorProvider returned 5xx; hold was released
429rate_limitedLipafy rate limit hit

Full catalog: Errors.

Integration patterns

Mint an API key in Settings → API keys. Use it as the Bearer token. Set spending controls on the key’s grant so an unattended script can’t drain the wallet.

1import requests
2r = requests.post(
3 "https://api.lipafy.xyz/gateway/eats.local-lunch.order/orders",
4 headers={"Authorization": f"Bearer {API_KEY}"},
5 json={"items": ["chapati"], "delivery_to": "Wilson"},
6)
7r.raise_for_status()
8print(r.headers["X-Payment-Reference"], r.json())

Use the OAuth 2.1 + PKCE flow (Connected apps via OAuth). You get an access token scoped to one user. The same Authorization: Bearer <token> header at the gateway routes the spend through that user’s wallet and grant.

Use the MCP server (Agents → MCP). The host calls the lipafy.execute_capability tool; you never write the HTTP yourself. When a call needs sign-off, the tool response asks the agent to use lipafy.request_approval, and the user decides from the dashboard.

For “buy up to N if conditions hold” flows, the agent signs an IntentMandate first (Mandates) and then calls /v1/intent-mandates/:id/fulfill instead of the gateway directly. Lipafy re-validates against the signed terms and forwards through the gateway internally.

What providers don’t have to do

There’s no SDK for the provider side. Their upstream is just an HTTPS endpoint. Lipafy:

  • Accepts whatever path / method / body the agent sends and forwards it.
  • Injects the auth (bearer / api_key / basic / none) the provider configured at registration time.
  • Treats 2xx as success (charge confirmed) and 4xx/5xx as failure (hold released).

If you’re a provider deciding whether to onboard, the only question is: is your endpoint already an HTTPS API that returns 2xx on success? If yes, you’re a 5-minute registration away from being callable. See Monetize an API.

Next

  • Spending controls — set the limits an agent can’t cross
  • Approvals — human-in-the-loop above a threshold
  • Receipts — verify what was charged, after the fact
  • Webhooks — get notified asynchronously instead of polling