# Notlogin — full reference for agents and integrators Base URL: https://notlogin.com. Bodies are JSON. Amounts in `*Cents` are USD cents of USDC. Errors are `{ "error": "", ... }`. Short version: https://notlogin.com/llms.txt · Agent walkthrough: https://notlogin.com/auth.md Contents: 1 Model · 2 Credentials · 3 Sign in with Notlogin (OIDC) · 4 x402 payments · 5 Receipts · 6 Agent wallets, swaps and bridges · 7 Trade mandates (vendors) · 8 Vendor charges · 9 Gateway, paywalls, checkout · 10 Account API and intents · 11 MCP · 12 Discovery and keys --- ## 1. Model - A **human** signs up (email+password, magic link, passkey, Google or other configured providers), adds identity proofs and funds a **managed wallet**: one custodial key per namespace (`evm` address valid on every EVM network, `solana`, `stellar`), encrypted at rest, exportable by the human at any time. - Proof types: `email`, `sms`, `google`, `apple`, `github`, `linkedin`, `x`, `discord`, `wallet`, `worldid`, `kyc`. - The human authorizes a **vendor** and issues a **credential** (VC): scope, USDC budget, expiry, and a snapshot of the human's verified proofs, signed by Notlogin (Ed25519). Optional: wallet EIP-712 co-signature, on-chain anchor on Base. - **Agents** (sub-identities of the human, `agentId`) can also get their own wallets and a bounded swap/bridge permission. - Custody tiers the human can choose: `managed` (Notlogin holds the key; needed for agents and x402), `recoverable` (browser vault + time-locked recovery copy), `self` (browser vault only). - Spend is bounded twice: the credential budget (ledger) and the wallet's on-chain balance. Networks (`GET /api/x402/chains`, public): `base`, `base-sepolia`, `ethereum`, `polygon`, `arbitrum`, `robinhood`, `botchain`, `botchain-testnet`, `solana`, `solana-devnet`, `stellar` (payments enabled), `bsc` (balances, withdraw and swaps only; `payments:false`). Each entry: `{network, namespace, chainId, nativeSymbol, stableSymbol, explorerUrl, testnet, payments}`. --- ## 2. Credentials VC JSON is a bearer credential: whoever holds it can present it. Keep it secret. Vendor-facing (no auth; the signature is the auth): - `POST /api/credentials/verify` `{vcJson, vendorSlug}` → validity, vendor match, revocation, required proofs, budget (`remainingCents`), `credentialId`. - `POST /api/credentials/redeem` → records an issuance and decrements the budget (the SDK's `recordRedemption`). Vendor SDK (`npm i notlogin-sdk viem`): `verifyCredential(vcJson, { vendorSlug, requiredProofs, mode: 'online'|'offline' })`, `recordRedemption(...)`, middlewares `notlogin-sdk/express`, `/hono`, `/next`. Offline mode checks signature, expiry and proofs only; online also checks revocation and budget. Vendor-initiated issuance: `GET /issue?vendor=&redirect_uri=&state=` (redirect origin must be registered). The human picks scope (`read|write|admin`) and expiry (1–365 days); the browser lands on `redirect_uri#vc=&state=…` or `#error=access_denied`. Proofs are snapshotted at issuance: adding a proof later requires re-issuing. --- ## 3. Sign in with Notlogin (OIDC) Discovery: `/.well-known/openid-configuration` (also `/.well-known/oauth-authorization-server`). `client_id` = vendor slug. id_tokens and access tokens are EdDSA JWTs (JWKS: `/.well-known/jwks.json`). - Authorize: `GET /authorize?client_id=&redirect_uri=&state=&scope=openid%20profile%20email&nonce=&code_challenge=&code_challenge_method=S256`. `redirect_uri` must match the vendor's registered list exactly (loopback ports are matched port-agnostically). - Token: `POST /api/oauth/token` (JSON or form). Grants: `authorization_code` (`code`, `redirect_uri`, `code_verifier` or client secret), `refresh_token` (single-use, rotated, 30 days; reuse revokes the family), `urn:ietf:params:oauth:grant-type:token-exchange` (confidential clients only). Client auth: `client_secret_basic`, `client_secret_post`, or none + PKCE. Response: `{access_token (1h), token_type:"Bearer", expires_in, id_token, refresh_token, refresh_expires_in, scope, charge_grant_id?}`. id_token claims: `sub, aud, iss, exp, iat, nonce, email, email_verified, verified_proofs`. - Userinfo: `GET /api/oauth/userinfo` (Bearer access token) → `{sub, verified_proofs, email, email_verified, wallets:[{namespace,address,chainId,label,isPrimary}]}` (live, not a snapshot). - Scopes: `openid profile email`. Account scopes (`account:read`, `wallet:read`, `security:manage`, `intents:create`) are granted only to first-party confidential clients (section 10); for anyone else they are silently dropped. - At consent a vendor with a registered payout address may also receive a charge grant (section 8). --- ## 4. x402 payments (VC-authenticated) Auth for every route here: `vcJson` in the body (agents), or `credentialId` + the human's session (dashboard). Cross-site browser requests are refused. If the credential's vendor registered a payout address, `payTo`/`network` must equal it (403 otherwise). ### POST /api/x402/pay Body: `{vcJson, payTo, amountCents, network?, idempotencyKey?}`, or `{vcJson, paymentRequirements, idempotencyKey?}` with the vendor's 402 body (`maxAmountRequired` is converted to cents, rounded up). `network` defaults to the broker's default network (`base` on notlogin.com); any network with `payments:true` works (EVM, `solana`, `stellar`). Pass it explicitly. 200: `{ok, txHash, network, chainId, payTo, amountCents, remainingCents, settledVia:"facilitator"|"direct", receipt}`. Idempotency: per `(vendor, idempotencyKey)`; a replay returns `{ok, replayed:true, status, txHash, ...}`, including a replayed failure (use a new key to retry after a failure). Errors: 400 `missing payTo` / `amountCents must be a positive integer` / `unknown network` / `network X is disabled`; 401 `invalid notlogin signature`, `credential expired`; 403 `credential revoked`, payout-pin mismatch; 404 `unknown credential`; 402 `payment exceeds remaining budget` (`{budgetUsdcCents, spentCents, remainingCents, requestedCents}`); 502 `payment failed: …`. ### POST /api/x402/authorize (EVM only, gasless) Body: `{vcJson, payTo, amountCents, validForSec?, network?}`. 200: `{ok, xPaymentHeader, payment, network, amountCents, remainingCents}`. Send `xPaymentHeader` to the vendor as `X-PAYMENT`; its facilitator settles. The amount is reserved against the budget until reconciliation. Errors: 402 over budget, 409 no managed wallet, 400 non-EVM network. ### POST /api/x402/proxy (EVM only) Body: `{vcJson, url, method? (GET), headers?, body?, maxAmountCents?, network?}`. Fetches `url`; on a 402 it pays the x402 challenge from the budget and refetches. Streams the origin response, with `X-Notlogin-Payment: {amountCents, network, payTo, txHash, remainingCents}` when it paid. Only standard headers are forwarded; private/internal addresses are refused. Errors: 402 over `maxAmountCents` / over budget / origin rejected payment; 502 unparseable challenge or upstream failure. ### POST /api/x402/apudeal-sign (apudeal.com escrow stakes, EVM) Only for credentials issued to the `apudeal` vendor (agent grants). `{vcJson}` → `{ok, wallet}`: the managed EVM address to register as the seat's wallet. `{vcJson, sign}` → `{ok, signature, wallet, amountCents, network, remainingCents}`, where `sign` is the object apudeal's deposit/settlement call returned; post `signature` back to apudeal. What is signed is pinned: an EIP-3009 `ReceiveWithAuthorization` over USDC, `from` = the wallet, `to` = the Commerce Payments collector, valid ≤ 30 days. The stake is reserved against the budget, keyed by the authorization nonce, so asking twice re-signs without spending twice (`replayed: true`). A released stake returns the USDC, not the budget. Errors: 400 malformed/unpinned payload, 402 over budget, 403 credential not for apudeal, 409 `from` isn't this wallet or nonce already used differently. --- ## 5. Receipts Returned by `/api/x402/pay`, `/api/x402/agent-pay` and settled vendor charges: ``` { "receipt": { "schema": "notlogin/receipt/v1", "paymentId", "credentialId", "vendorSlug", "payTo", "amountCents", "network", "txHash", "reference", "settledAt" }, "signature": "", "kid": "" } ``` Verify: canonicalize `receipt` (keys sorted recursively, no whitespace), verify the Ed25519 signature over the UTF-8 bytes with the key `kid` from `/.well-known/notlogin-issuer.json` (same key that signs credentials). `settledAt` is the receipt time, not the block time. --- ## 6. Agent wallets, swaps and bridges Backend routes for an agent operator. Auth: `Authorization: Bearer ` (issued to trusted integrators, never to a browser). `userId` = the human's Notlogin id (OIDC `sub`); `agentId` matches `^[A-Za-z0-9_.:-]{1,64}$`. Each agent has its own wallets; funds leave an agent wallet only by the human's withdrawal (session-only). ### Agent permission (set by the human) At /dashboard/swap → Agents: USD budget per rolling 24h (swaps + bridges), per-trade cap, networks, max slippage, trades per 24h, may-bridge + max bridge fee, expiry. Raising any limit requires a fresh human sign-in. ### POST /api/x402/agent-swap `{userId, agentId, action, ...}`: - `status` → `{permission, wallets, balances}` - `history` → `{swaps}` (last 50) - `quote` `{network, tokenIn, tokenOut, amount | amountAtomic, slippageBps?}` → `{quote}` (expected out, min out, price impact, network fee) - `execute` (same + `clientRef`) → `{swap}`; same `clientRef` returns the stored row, never trades twice. Tokens: `usdc`, `native`, an ERC-20 address or a Solana mint. Networks: Kyber EVM chains (base, ethereum, arbitrum, polygon, optimism, bsc, robinhood) and Solana mainnet (Jupiter). ### POST /api/x402/agent-bridge `{userId, agentId, action, ...}`: `quote` / `execute` `{fromNetwork, toNetwork, fromAsset? ("usdc"|"native"), toAsset?, amount | amountAtomic, maxFeeBps? (1..300), clientRef}`; `status {id}`; `history`. Execute returns `{bridge}` with status `submitted`; poll `status` until `filled` or `refunded`. Providers: Across (EVM→EVM), Relay. Testnet ↔ mainnet bridges are refused. Refusals (both): 403 `no_permission`, `network_not_allowed`, `bridge_not_allowed`, `bridge_fee_too_high`, `slippage_too_high`; 409 `trade_too_large`, `budget_exceeded`, `daily_limit`, `in_flight`; 422 `unpriced_token`, `no_route`, `insufficient_balance`; 400 `invalid_action`. ### Other service-token routes - `POST /api/x402/agent-pay` `{userId, agentId, payTo, amountCents, network?, vendorSlug?, idempotencyKey?}` → `{txHash, receipt, ...}`. Caps: per tx, per user per 24h, recipient allowlist. - `POST /api/x402/solana-sign` `{userId, agentId, network?, requirement}` → `{xPayment, ...}`: signs (doesn't broadcast) a Solana x402 "exact" payment; capped and recipient-pinned. - `POST /api/x402/evm-sign` `{userId, agentId, network?, tx:{to,data,value?,gas?,nonce?}}` → `{signedTx, txHash, ...}`: signs (doesn't broadcast) a call to an allowlisted contract/selector. - `POST /api/x402/sign-message` `{userId, agentId, message, namespace?}` → `{address, signature, scheme}` (EIP-191 / ed25519 / SEP-53) for prefix-allowlisted login challenges. --- ## 7. Trade mandates (vendor-driven swaps) A vendor (e.g. a trading app) runs capped swaps USDC|native ↔ token inside the user's managed wallet. Output always lands in the same wallet. Consent URL (human): `/dashboard/wallet/mandate?client_id=&budget_usd=100&max_trade_usd=&slippage_bps=100&networks=solana,base&days=30&return_to=&auto_bridge=1&max_bridge_fee_bps=100`. The human can only lower the caps (max slippage 500 bps, 365 days, bridge fee 300 bps). Auth: `Authorization: Bearer ` (aud = vendor) AND `X-Client-Secret: `. Rate limits 60 swaps/min, 120 reads/min per vendor+user (429 `rate_limited`). - `GET /api/v1/trade/mandate` → `{mandate|null, wallets, totalUsdCents, spendableUsdCents?, grantUrl}` - `DELETE /api/v1/trade/mandate` → `{revoked}` - `POST /api/v1/trade/swap` `{clientRef, network, side:"buy"|"sell", token, amountUsdCents? (buy, ≥100), sellBps? (sell, 1..10000), slippageBps? (1..500), quote? ("usdc"|"native")}` → `{execution}`. Idempotent on `clientRef`; waits ~45 s, then `submitted`. With auto-bridge, a buy short of funds bridges first; 503 `bridge_pending` → retry the same `clientRef`. - `GET /api/v1/trade/executions?clientRef=&limit=` → `{executions:[{id, clientRef, status (pending|submitted|confirmed|failed), side, network, token, quote, inAtomic, outAtomic, usdCents, txHash, explorerUrl, priceUsd, error, bridge, createdAt, updatedAt}]}` Errors: 403 `no_mandate`, `mandate_revoked`, `mandate_expired`, `network_not_allowed`; 409 `over_trade_cap`, `over_budget`, `daily_limit`, `bridge_fee_cap`, `bridge_failed`, `bridge_retry`; 422 `no_route`, `insufficient_balance`, `insufficient_gas`; 502 `chain_error`, `broadcast_failed`, `unsafe_bridge`; 503 `bridge_pending`. --- ## 8. Vendor charges (card-on-file) A vendor with a registered payout address can ask for charging rights at OIDC consent (the human must hold a verified Google proof). The grant id arrives as `charge_grant_id` in the token response. Auth: the vendor's client secret (HTTP Basic `slug:secret`, body `client_id`+`client_secret`, or `Bearer :`). Never a user token. - `POST /api/x402/charge` `{grantId, amountCents, idempotencyKey (required), reference?}`. At or below the human's auto-approve threshold: settles, 200 + `receipt`. Above: 202 `pending_confirmation`; the human approves by email/dashboard within 24h. Destination is always the vendor's pinned payout address. Errors: 402 over budget, 403 revoked/expired grant, 429 daily charge limit. - `GET /api/x402/charge/` → `{chargeId, status, amountCents, network, payTo, txHash, reference, expiresAt, disputedAt, error, createdAt}`. The human can dispute a settled charge, which revokes the grant. --- ## 9. Gateway, paywalls, checkout Vendor-secret routes (escrow-based x402 provider): - `POST /api/x402/gateway/generate` `{amountCents, resourceId?, network?}` → `{x402Version, accepts, reference, paymentId}` - `POST /api/x402/gateway/verify` `{reference, payment}` → settles into escrow, `{valid, status, amountCents, txHash, payer}` - `POST /api/x402/gateway/refund` `{reference, amountCents, idempotencyKey}` → pays the buyer back - `POST /api/x402/gateway/release` `{reference, amountCents, idempotencyKey, payToUserId | payTo}` → pays a third party (refund + release share one ceiling) - WS402 drop-in aliases: `POST /api/payment/{generate,verify,refund}` (`Bearer :`). Human checkout: `/pay/?return=` — pay from the Notlogin balance (session) or an external wallet (EIP-3009 signature). Paywalls: `/watch/` returns 402 until a valid `X-PAYMENT` is sent, then streams the masked resource; embed with ``. Created by humans at /dashboard/monetize. --- ## 10. Account API and intents (first-party apps) Only first-party confidential clients (a white-label brand's own app, or listed first-party apps) hold account scopes. Auth: `Authorization: Bearer ` with the scope shown. Responses are `no-store`. - `GET /api/v1/account/profile` (`account:read`) → profile, security summary, linked wallets, recent logins - `GET /api/v1/account/proofs` (`account:read`) → `{proofs:[{type, verified, value, verifiedAt}]}` - `GET /api/v1/account/wallets[?agentId=]` (`wallet:read`) → wallets with balances, agent wallets - `GET /api/v1/account/payments?limit=` (`wallet:read`) → x402 payment history - `GET|PATCH /api/v1/account/passkeys`, `GET|DELETE /api/v1/account/sessions` (`account:read` / `security:manage`) Intents — the app proposes, the human confirms with a fresh sign-in: - `POST /api/v1/account/intents` (`intents:create`) `{type, params, returnTo?}` → 201 `{id, type, params, status:"pending", expiresAt, url}`. Open `url` (`/confirm/`) for the human. Intents expire after 10 minutes. - `GET /api/v1/account/intents/` → `{status: pending|done|cancelled, result}`; poll it. | type | params | |---|---| | `withdraw` | `network`, `to`, `amount` (decimal or `all`), `asset?`, `agentId?` | | `export_key` | `namespace` (`evm`/`solana`/`stellar`) | | `swap` | `network`, `tokenIn`, `tokenOut`, `amount`, `slippageBps?` | | `bridge` | `fromNetwork`, `toNetwork`, `amount`, `fromAsset?`, `toAsset?`, `maxFeeBps?` | | `token_launch` | `items`: 1–10 × `{pad, name, symbol, uri, description?, website?}`; pads: pump, bonk, bags, clawpump, clanker, zora, virtuals, flaunch, bagsrh, pons, jupstudio | | `fee_claim` | `pads` (pump, bonk, bags, clanker, flaunch, bagsrh, pons, jupstudio), `tokens?`, `agentId?` | | `solana_bet` | `market`, `option` (0–5), `lamports`, `label?`, `title?`, `agentId?` | | `passkey_add`, `passkey_remove {id}`, `twofactor_enable`, `twofactor_disable`, `delete_account` | — | Results: `swap`/`bridge` → `{txHash, network, amount, asset}`; `token_launch` → `{launches, launched}`; `fee_claim` → `{claims}`. --- ## 11. MCP Streamable HTTP, JSON-RPC 2.0, no auth, read-only: `claude mcp add --transport http notlogin https://notlogin.com/api/mcp`. Tools: `list_vendors {category?}`, `get_vendor {slug}`, `inspect_credential {vcJson}` (decode only), `verify_credential {vcJson, vendorSlug}`, `swap_networks`. There are no money-moving or quote tools over MCP. --- ## 12. Discovery and keys - `/.well-known/notlogin-issuer.json` — issuer, `kid`, Ed25519 JWKS (credentials + receipts) - `/.well-known/jwks.json` — OIDC signing keys - `/.well-known/openid-configuration`, `/.well-known/oauth-authorization-server` - `/.well-known/mcp.json` - `GET /api/vendors`, `GET /api/categories`, `GET /api/x402/chains` — public listings