{
  "markdown": "# agentic-endpoints\n\nx402-powered micro-SaaS utilities for autonomous AI agents. Pay-per-call with USDC micropayments on Base — no API keys, no accounts, no subscriptions.\n\nLive at **https://ai.oliverkiss.com**\n\n## Endpoints\n\n| Route | Method | Price | Description |\n|-------|--------|-------|-------------|\n| `/once-key` | POST | $0.001 | Claim an action exactly once, and replay its recorded result |\n| `/once-key/complete` | POST | Free | Record the outcome of a claimed action |\n| `/once-key/release` | POST | Free | Surrender a claim whose work failed |\n| `/scrape` | POST | $0.005 | Web scraping and text extraction |\n| `/pdf-parse` | POST | $0.01 | PDF text extraction from a URL |\n| `/compress` | POST | $0.005 | Token compression / context reduction for LLMs |\n| `/meetings/import` | POST | $0.004 | Import a meeting transcript, private or searchable |\n| `/meetings/search` | POST | $0.006 | Full-text search across your meetings |\n| `/meetings/get` | POST | $0.002 | Read one meeting in full |\n| `/meetings/list` | POST | $0.001 | List meetings (metadata only) |\n| `/meetings/delete` | POST | $0.001 | Delete a meeting and its index entry |\n| `/vault/store` | POST | $0.02 | Store a client-encrypted item |\n| `/vault/retrieve` | POST | $0.02 | Retrieve a client-encrypted item |\n| `/vault/delete` | POST | $0.005 | Delete an item |\n| `/vault/exists` | POST | $0.001 | Check whether an item exists |\n| `/vault/list` | POST | $0.001 | List the keys in a namespace (metadata only) |\n| `/vault/rotate-token` | POST | Free | Replace a namespace token that may have leaked |\n| `/credits/buy` | POST | $5.00 | Buy $6.00 of prepaid credit (20% bonus) |\n| `/credits/buy-25` | POST | $25.00 | Buy $32.50 of prepaid credit (30% bonus) |\n| `/credits/balance` | POST | Free | Check a credit balance |\n| `/revenue` | GET | Free | On-chain USDC received, read from Base |\n| `/mcp` | POST | Free to list | Remote MCP server; each tool costs its route's price |\n| `/` | GET | Free | Service discovery (JSON) or landing page (HTML) |\n| `/health` | GET | Free | Health check |\n| `/status` | GET | Free | Uptime, error rate and latency, derived from recorded behaviour |\n| `/stats` | GET | Free | Demand funnel: challenged, paid, free, by route |\n| `/llms.txt` | GET | Free | Prose description for a model given only a URL |\n| `/openapi.json` | GET | Free | OpenAPI 3.1 description |\n\n`GET /` content-negotiates: send `Accept: application/json` for the machine-readable\nendpoint catalogue, anything else gets the HTML landing page.\n\n## How It Works\n\n1. An agent requests a paid endpoint\n2. The server replies `HTTP 402 Payment Required` with the price, network and receiving address\n3. The agent signs a USDC transfer on Base and retries with an `X-PAYMENT` header\n4. The facilitator verifies and settles the payment, then the handler runs\n\nTry it — this returns a real 402 challenge, not an error:\n\n```bash\ncurl -i -X POST https://ai.oliverkiss.com/once-key \\\n  -H 'Content-Type: application/json' \\\n  -d '{\"namespace\":\"demo\",\"action_key\":\"abc123\"}'\n```\n\n## MCP Server\n\nEvery paid endpoint is also exposed as an MCP tool over Streamable HTTP at\n`https://ai.oliverkiss.com/mcp`, implementing revision `2026-07-28` (stateless:\nno `initialize` handshake, no session header) with a fallback for clients still\nsending the `2025-06-18` handshake.\n\n```json\n{\n  \"mcpServers\": {\n    \"agentic-endpoints\": { \"type\": \"http\", \"url\": \"https://ai.oliverkiss.com/mcp\" }\n  }\n}\n```\n\n`tools/list` is free so clients can discover the catalogue. `tools/call`\nre-enters the corresponding paid route in-process, so it passes the same x402\ngate, body cap and validation as a direct HTTP call. Without a valid\n`X-PAYMENT` header the tool returns `isError: true` and a machine-readable\npayment demand (price, `payTo`, asset, network) rather than performing work.\n\n## Two Ways To Pay\n\nPer-call x402 caps revenue at whatever a buyer will tolerate signing: $1,000 at\n$0.005 a call is 200,000 signatures. Prepaid credits sell the same work once,\nin an amount worth the transaction, and let callers whose wallets cannot sign\nper request use the service at all.\n\nBoth paths run side by side and neither is privileged:\n\n```bash\n# Per call, unchanged\ncurl -X POST https://ai.oliverkiss.com/compress -H \"X-PAYMENT: ...\" -d '{\"text\":\"...\"}'\n\n# Or prepay once, then no signatures\ncurl -X POST https://ai.oliverkiss.com/credits/buy -H \"X-PAYMENT: ...\"   # -> credit_token\ncurl -X POST https://ai.oliverkiss.com/compress \\\n  -H \"X-Credit-Token: ae_...\" -d '{\"text\":\"...\"}'\n```\n\nOmitting `X-Credit-Token` produces exactly the 402 challenge it always did, so\nthe Bazaar listing and every existing integration are unaffected.\n\nCredits are integer micro-dollars, never floats: $0.001 has no exact binary\nrepresentation, and a ledger that drifts is worse than no ledger. Each account\nis its own Durable Object addressed by the hash of its token, so the balance\ncheck and its debit are atomic and one account cannot queue behind another.\nCalls are debited before the work and refunded if it 5xxs, because an outage\nmust not bill a customer for nothing.\n\n**The token is shown once and is not recoverable** — only its hash is stored.\n\n## Discovery\n\nAn endpoint nobody can find earns nothing, so the service is registered wherever\nagents actually look. Every catalog below was chosen because it verifies\nownership by domain or wallet rather than by a financial account — the Coinbase\nCDP Bazaar is skipped for exactly that reason.\n\n| Catalog | Status | How |\n|---|---|---|\n| PayAI Bazaar | Not listed on mainnet — **mechanism proven on testnet** | Listing requires a payment that *settles*, once per route; reaching `/verify` does nothing. Confirmed on Base Sepolia: all 9 routes appeared in `/discovery/resources` within seconds of paying. `X402_TEST_PRIVATE_KEY=0x... node scripts/trigger-indexing.mjs` costs **$0.068** for the whole catalogue |\n| x402-list.com | Submitted, pending review | `POST /api/v1/submit`; free because the service is on a custom domain |\n| Official MCP Registry | **Published — `com.oliverkiss/agentic-endpoints`, status active** | `./scripts/publish-registry.sh`. Ownership proven by an apex TXT record and an ed25519-signed timestamp, so no financial account is involved |\n| npm | **Published — [`agentic-endpoints`](https://www.npmjs.com/package/agentic-endpoints)** | `cd sdk && npm publish`. Counts as discovery, not just convenience: npm is crawled by every AI coding assistant, so the client is findable by the same models that would use the service |\n| Smithery | **Published — [`kiss-olie/agentic-endpoints`](https://smithery.ai/servers/kiss-olie/agentic-endpoints)**, quality **98/100** | `npx -y @smithery/cli@latest mcp publish \"https://ai.oliverkiss.com/mcp\" -n kiss-olie/agentic-endpoints` — **no auth needed**, and it updates in place. The scan finds all 16 tools. Note the namespace is `kiss-olie`, not the GitHub handle. Their registry API and the markdown view served to non-browser clients are both **badly stale** (still report 12 tools and the old favicon); only the browser-rendered page is current, so verify there |\n| Glama | Not listed; **claim file pre-placed** | `glama.json` at the repo root (`maintainers: [\"olie-kiss\"]`, schema `glama.ai/mcp/schemas/server.json`) claims the listing automatically if their GitHub crawler indexes us. The separate `/.well-known/glama.json` is a *different* schema (`connector.json`, an opaque `glama_claim_` token) for remote connectors and needs a listing to exist first |\n\nAggregators such as PulseMCP ingest from the official registry, so publishing\nthere covers several directories at once. The repository is public, so\ndirectories that crawl source repos can now see it too.\n\nMachine-readable descriptions are generated from the same pricing table that\ngates payment, so they cannot drift from what is actually charged: `/llms.txt`\nfor a model handed a bare URL, `/openapi.json` for tooling, plus `/robots.txt`\nand `/sitemap.xml`. Tests assert the prices agree across all of them.\n\n## Revenue Monitoring\n\nThe service could demand payment for months with no way to tell whether a\npayment ever arrived — including the failure mode where payments verify but\nnever settle. A cron trigger sweeps Base every 5 minutes for USDC `Transfer`\nlogs into the receiving address and folds them into a running ledger in KV.\n\nRevenue is read from the chain, not from our own logs or the facilitator's\nword, so it cannot be inflated by a bug on either side. `GET /revenue` publishes\nthe ledger for free — it costs nothing and gives a prospective caller evidence\nthe service actually transacts.\n\nSet `ALERT_WEBHOOK_URL` to a Discord webhook to be notified when money lands:\n\n```bash\nnpx wrangler secret put ALERT_WEBHOOK_URL\n```\n\nThe first scan starts the watermark at the current chain head rather than\ngenesis; scanning millions of blocks through a public RPC node would fail\nrepeatedly and never establish a watermark at all. The watermark advances only\non a successful scan, so a transient RPC failure is retried on the next tick\nwith nothing missed.\n\n### Buyer signals\n\nRevenue monitoring only sees money that *arrived*. The service takes thousands\nof requests a day and, so far, no revenue — and reviewing the callers, almost\nall of it is liveness probes, trust scanners and directory crawlers, which are\nindistinguishable from a customer in an access log. The gap that leaves is a\ngenuine buyer being refused for a fixable reason and leaving without ever\nappearing as a distinguishable line.\n\n`src/lib/tripwire.ts` emits one structured line per notable request:\n\n```bash\nnpx wrangler tail --format json | grep BUYER_SIGNAL\n```\n\n| Signal | Meaning | Confidence |\n|---|---|---|\n| `payment_attempt` | Carried a payment authorization | high |\n| `credit_use` | An existing customer spending prepaid credit | high |\n| `prospect_402` | An unrecognised caller hit a priced route | low |\n\nIt keys on **behaviour, not identity**. A monitor never carries a payment\nauthorization, so anything that does is trying to buy — and that holds even\nwhen the `User-Agent` claims to be a bot. `User-Agent` only suppresses known\nnoise and grants nothing, because it is forged for free.\n\nNote that the SDK inherits Node's `User-Agent`, which is exactly what an\nanonymous script sends, so **no string positively identifies a customer**.\nAnything unrecognised stays `unclassified` rather than being written off; the\ntests pin this, since misfiling it would suppress the one signal worth having.\n`prospect_402` is a guess and is marked low confidence for that reason — it\nshould never be read as a sale.\n\nThe check runs ahead of the credit and x402 gates, both of which answer without\nreaching application code, because a refusal is precisely the event worth\nrecording. No header values are logged and the caller IP is dropped in favour\nof the country Cloudflare already derived.\n\n## Published SLOs\n\nAn agent choosing between two paid services has no way to tell which one works.\n`GET /status` is free and answers that from recorded behaviour, not a promise:\n\n```json\n{\n  \"uptime_24h\": \"100.00%\",\n  \"uptime_window\": \"0.5h\",\n  \"cron_ticks_24h\": 6,\n  \"requests_48h\": 88,\n  \"error_rate_48h\": \"0.00%\",\n  \"unknown_path_requests_48h\": 10,\n  \"latency_ms\": { \"p50_at_most\": 25, \"p95_at_most\": 500, \"p99_at_most\": 500 }\n}\n```\n\nGetting the number *honest* mattered more than getting it published. It first\nshipped reporting an 11.39% error rate; every one of those was a 404 on a path\nthat never existed — my own probes and passing crawlers — plus tokens that were\ncorrectly rejected. An agent reading that would have taken its money elsewhere\nand been right to. So client errors (4xx) are counted separately from failures\n(5xx), and requests to unknown paths are excluded from the rate entirely and\nsurfaced as a raw count instead. Otherwise any stranger could degrade our\npublished reliability just by scanning for `/wp-admin`.\n\nLatency comes from histogram buckets, so the figures are reported as\n`p95_at_most` — a bound, which is what a bucket can honestly support, rather\nthan a precise percentile it cannot. Uptime credits only the window actually\nobserved, so day one does not claim 24 hours from an hour of heartbeats. The\nheartbeat is written *before* the revenue scan, so an outage at a public RPC\nnode is not reported as ours.\n\n`GET /stats` publishes the demand funnel — challenged, paid, free, per route —\nwhich is the only thing that distinguishes \"nobody has found us\" from \"agents\narrive and decline to pay\".\n\n## Client SDK\n\n[`sdk/`](./sdk) is a dependency-free TypeScript client. It deliberately does\n**not** sign payments — it takes a credit token, or your own x402-aware\n`fetch`, so it never needs a private key.\n\n```bash\nnpm install agentic-endpoints\n```\n\nIts reason to exist is `exactlyOnce`, which collapses the claim/complete/release\nprotocol into one call: it handles all four claim outcomes, records the result\nso later callers can replay it, releases the claim if your work throws, and\nrethrows your error untouched.\n\n## Stack\n\n- **Runtime**: Cloudflare Workers + Durable Objects\n- **Payments**: x402 protocol (USDC on Base mainnet, `exact` scheme)\n- **State**: Durable Object SQLite (OnceKey and Vault)\n- **Framework**: Hono\n\n## Setup\n\n### Prerequisites\n\n- Node.js 20+\n- Cloudflare account (Workers Paid plan — $5/mo, required for Durable Objects)\n- A wallet address on Base to receive USDC\n- [Wrangler CLI](https://developers.cloudflare.com/workers/wrangler/) (use `npx wrangler` if not installed globally)\n\n### Install\n\n```bash\nnpm install\n```\n\n### Configure Secrets\n\n```bash\n# Required — your receiving wallet address (Base USDC)\nnpx wrangler secret put X402_PAY_TO\n\n# Required — HMAC key for signing JSON receipts\nnpx wrangler secret put RECEIPT_SECRET\n```\n\n> **Do not set `FACILITATOR_URL` unless you mean to override the default.**\n> It must be a valid URL. If it is set to anything else, every paid endpoint\n> returns 500 instead of a 402, and the failure is only visible in\n> `npx wrangler tail` — this silently broke all payments once already.\n\nThe default facilitator is `https://facilitator.xpay.sh`: free, no signup, and it\nsupports Base mainnet.\n\n### Develop and deploy\n\n```bash\nnpm run dev\nnpm run deploy\n\n# Tests run against the real Workers runtime via workers-pool.\n# The Durable Object tests call the objects directly, since every paid\n# route answers 402 without a real on-chain payment.\nnpm test\nnpm run typecheck\n\n# Re-announce every route to the PayAI Bazaar. This SPENDS REAL USDC: a route\n# is catalogued only once a payment for it settles. It refuses to start unless\n# the wallet covers the whole run.\nexport X402_TEST_PRIVATE_KEY=0x...\nnode scripts/trigger-indexing.mjs\n\n# Make one real paid call. Requires a THROWAWAY wallet holding a little USDC\n# on Base; the key is read from the environment and never written anywhere.\nexport X402_TEST_PRIVATE_KEY=0x...\nnode scripts/paid-test.mjs /compress\n```\n\n### Proving settlement without spending\n\nSettlement is the one step that cannot be tested by inspection, and on\nmainnet every attempt costs real USDC. `[env.testnet]` deploys the same code\nto a workers.dev URL priced in Base Sepolia USDC, which\n[faucet.circle.com](https://faucet.circle.com) gives away with no account.\n\n```bash\nnpx wrangler deploy --env testnet\nX402_TEST_PRIVATE_KEY=0x... node scripts/paid-test.mjs /once-key \\\n  https://agentic-endpoints-testnet.<subdomain>.workers.dev --testnet\n```\n\nOnly the exact string `eip155:84532` selects Sepolia; anything unrecognised\nfalls back to mainnet. That asymmetry is deliberate — a Worker that wrongly\ndemanded testnet tokens would hand out real work for money anyone can mint.\n\n### Announcing to the Bazaar\n\n```bash\nX402_TEST_PRIVATE_KEY=0x... node scripts/trigger-indexing.mjs\n```\n\nPays for each route once, which is what puts it in the catalogue. $0.082 for\nall 14 utility routes; credit packs are excluded unless you pass\n`--include-credits`. The script checks the payer's USDC balance against that\ntotal first and refuses to start if it falls short, because a wallet that runs\ndry midway leaves the catalogue half-populated with no way to tell which\nroutes made it.\n\n## API Examples\n\n### OnceKey (exactly-once execution)\n\nA claim on its own is only half an idempotency key. The agent that *loses*\nthe race needs to know what happened, or it has to either block forever or\nrepeat the side effect anyway — which is the failure this endpoint exists to\nprevent. So the lifecycle is three calls, and only the first one costs money.\n\n```json\nPOST /once-key\n{\n  \"namespace\": \"payment-webhooks\",\n  \"action_key\": \"stripe_evt_abc123\",\n  \"payload_sha256\": \"e3b0c44298fc...\",\n  \"ttl\": 86400,\n  \"lease_ttl\": 300\n}\n```\n\n| `status` | Meaning |\n|---|---|\n| `claimed` | You won. Do the work, then call `/once-key/complete` |\n| `duplicate` | Already done. `result` holds the original outcome — use it. `has_result` is `false` if the original caller recorded no result, in which case `result` is null because there is nothing to replay |\n| `held` | Claimed by someone who set no lease and never finished. There is **no** result and may never be one. Do **not** do the work and do **not** treat it as done; locked until `expires_at` |\n| `in_progress` | Another caller holds a live lease. Wait `retry_after`; do **not** do the work |\n| `conflict` | Same key, different payload hash. Your key derivation is wrong; never retry |\n\n```json\nPOST /once-key/complete          // free\n{ \"namespace\": \"...\", \"action_key\": \"...\", \"namespace_token\": \"...\",\n  \"result\": { \"charge_id\": \"ch_abc\" } }\n```\n\nEvery later claim of that key returns `duplicate` **with that result**.\n\n`duplicate` always means *completed*. A claim that was started but never\ncompleted reports `held` instead, precisely so a caller cannot mistake work\nthat is still in flight — or that died half way through — for work that\nsucceeded. The SDK raises `HeldError` rather than returning a result of\n`undefined`.\n\nIf the work fails, `POST /once-key/release` (free) frees the key immediately.\n\n**`lease_ttl` is opt-in, deliberately.** Without it a claim is held for its\nfull `ttl` and nothing can ever run your side effect twice. With it, a\nclaimant that crashes is presumed dead once the lease lapses and the next\ncaller takes over with `recovered: true`. Leases on by default would have\nmade every key claimed by the original claim-only API silently reclaimable —\na duplicated charge is a far worse failure than a key that needs a retry\nunder a fresh name.\n\nThe [`agentic-endpoints` npm package](./sdk) wraps all of this in one call:\n\n```ts\nconst { outcome, result } = await client.exactlyOnce(\n  { namespace: \"billing\", actionKey: `charge:${order.id}`, leaseTtl: 300 },\n  async () => stripe.charges.create({ amount: order.total }),\n);\n```\n\n### Web scraper\n\n```json\nPOST /scrape\n{ \"url\": \"https://example.com/article\", \"format\": \"text\" }\n```\n\n### PDF parser\n\n```json\nPOST /pdf-parse\n{ \"url\": \"https://example.com/document.pdf\", \"pages\": [1, 2] }\n```\n\nInflates FlateDecode content streams, expands PDF 1.5+ object streams, and maps\ncharacter codes through each font's `/ToUnicode` CMap, so subset and composite\nfonts come back as real text rather than glyph indices. Encrypted PDFs and\nimage-only scans return `422` rather than filler content, so callers are not\nbilled for a result that is known to be useless.\n\n### Token compressor\n\n```json\nPOST /compress\n{ \"text\": \"Your very long text here...\", \"target_tokens\": 500, \"strategy\": \"extractive\" }\n```\n\n### Meeting memory (agent-queryable transcripts)\n\nMeeting notetakers keep transcripts inside their own app, where the only reader\nis a human scrolling a sidebar. This puts them somewhere your agents can ask\nquestions of them.\n\n**You choose, per meeting, whether this service can read it. The choice is\nrequired, and the wrong field for the mode is refused rather than guessed.**\n\n| `visibility` | You send | Stored as | Searchable | We can read it |\n|---|---|---|---|---|\n| `private` (default) | `ciphertext` | opaque bytes | no | no |\n| `queryable` | `transcript` | plaintext + FTS index | yes | yes |\n\nSending plaintext as `private` is an error rather than a quiet indexing, and\nsending ciphertext as `queryable` is an error rather than a meeting that can\nnever match a search. Both refusals exist because the failure they prevent is\nsilent and only discovered long after it matters.\n\n```bash\n# First import claims the namespace and returns a one-time token.\ncurl -X POST https://ai.oliverkiss.com/meetings/import \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"namespace\": \"my-meetings-4f9c2b1e8d7a\",\n    \"title\": \"Pricing review\",\n    \"occurred_at\": \"2026-09-01T15:00:00.000Z\",\n    \"source\": \"webvtt\",\n    \"visibility\": \"queryable\",\n    \"participants\": [\"Alice\", \"Bob\"],\n    \"transcript\": \"Alice: we agreed to ship the redesign before the audit.\"\n  }'\n\n# Then ask a question of every meeting at once.\ncurl -X POST https://ai.oliverkiss.com/meetings/search \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"namespace\": \"my-meetings-4f9c2b1e8d7a\",\n    \"namespace_token\": \"<from the first import>\",\n    \"query\": \"pricing\"\n  }'\n```\n\nSearch returns ranked excerpts plus `searched_meetings` and\n`private_meetings_skipped`. Read them. If `searched_meetings` is `0`, an empty\nresult means **nothing was searched**, not that the topic was never discussed —\nand an agent that conflates those will confidently tell a user something never\nhappened.\n\nVia MCP the same thing is `meetings_search`, `meetings_import`, `meetings_get`\nand `meetings_list`.\n\n### Vault\n\nStorage is free; retrieval is paid. The server only ever sees ciphertext —\n**encrypt client-side before calling.**\n\nNamespaces are caller-chosen strings, so they are claimed on first write. That\nfirst `store` returns a `namespace_token` **once**; every later operation on the\nnamespace must present it.\n\n```json\nPOST /vault/store\n{ \"namespace\": \"my-app-4f9c2b1e8d7a\", \"key\": \"secret-1\", \"ciphertext\": \"base64...\", \"ttl\": 86400 }\n\n// First write only — save this, it is not shown again\n{ \"status\": \"stored\", \"namespace_token\": \"30ab4b26...\" }\n```\n\n```json\nPOST /vault/retrieve\n{ \"namespace\": \"my-app-4f9c2b1e8d7a\", \"key\": \"secret-1\", \"namespace_token\": \"30ab4b26...\" }\n```\n\nWrites are last-write-wins unless you say otherwise, so two agents rotating\nthe same secret would clobber each other silently. Pass `if_match` with the\nitem's current `updated_at` for a compare-and-swap, or `if_absent` to create\nonly; either answers `status: \"precondition_failed\"` instead of overwriting.\n\n`POST /vault/list` ($0.001) returns the keys and their versions but never any\nciphertext — that is what the $0.02 retrieve is for.\n\n**Rotate a token you think has leaked**, with `POST /vault/rotate-token`. It\nis free: putting a price on the correct response to a suspected leak is how\nyou get callers who never rotate. It requires the *current* token, and there\nis no recovery if that is lost — any path that could restore access without\nit would be a second way in, and would serve an attacker just as readily as\nthe owner. Lose it and the namespace is gone by design; there is no account\nto reset it against.\n\n## Limits\n\n| Limit | Value |\n|-------|-------|\n| Free requests (no `X-PAYMENT`) | 60/min per IP, per Cloudflare location |\n| Vault writes | 20/min per IP |\n| Paid requests | 600/min |\n| Request body | 2 MiB |\n| Vault item | 256 KiB ciphertext |\n| Vault namespace | 1,000 items / 25 MiB |\n\nPaid requests are throttled far more loosely than anonymous ones, because a\ncaller who is paying per call already has a spend ceiling. They are not\nunlimited.\n\n### Concurrent paid calls\n\nPaid calls issued **concurrently from the same wallet** are refused a\nnoticeable fraction of the time — measured at 2 in 10 up to 8 in 15 on Base\nSepolia, independent of our rate limits, and reproducible with as few as 5 in\nflight. The refusal is an ordinary `402`, and it originates at the facilitator,\nwhich will not verify overlapping authorizations from one payer.\n\n**Nothing is charged for a refused call.** Measured directly: 10 concurrent\ncalls at $0.005, 8 settled, 2 refused, and the payer's balance moved by exactly\n$0.040. Any status at or above 400 cancels x402 settlement, and that holds here.\n\nSo a `402` on a paid request means the payment did not happen and it is safe to\nretry with a fresh signature. If you need throughput, issue paid calls\nsequentially, or retry on `402` with a short backoff. Do not treat a `402` as a\ncharge you need to reconcile.\n\nSince **0.4.0 the SDK does this for you**: `post()` replays a `402` twice by\ndefault, with jittered backoff, and `maxPaymentRetries` tunes it. Jitter is not\ndecoration — the colliding requests belong to a single payer, so a fixed delay\nwould realign them on the next attempt. Retries only happen when you supplied\nan x402-aware `fetch`; the SDK never signs payments itself, so without one a\n`402` is terminal and is surfaced immediately rather than stalling.\n\n## Security Notes\n\n- **URL-taking endpoints are SSRF-guarded** (`src/lib/url-guard.ts`): scheme\n  allowlist, private/reserved IPv4 and IPv6 ranges blocked, hostnames resolved\n  over DNS-over-HTTPS and private answers rejected, every redirect hop\n  re-validated, and response bodies bounded. It fails closed.\n  **It does not defeat DNS rebinding**, and does not claim to: the DoH probe\n  and the fetch are two independent resolutions, so a hostile low-TTL\n  nameserver can answer them differently. Pinning the fetch to the vetted\n  address is not expressible on Workers — `cf.resolveOverride` only accepts\n  hostnames inside your own zone, and an IP literal breaks TLS SNI. What\n  bounds the risk is the egress path: Workers reach the internet through\n  Cloudflare's network, which has no route to RFC1918 or loopback and exposes\n  no metadata endpoint. Do not place anything sensitive where this Worker's\n  egress can reach it on the assumption that this guard stops it.\n- **Vault and OnceKey namespaces are ownership-gated.** The first request to a\n  namespace is issued a one-time `namespace_token`; tokens are stored only as\n  SHA-256 hashes and compared in constant time.\n- **New namespaces must be unguessable** (16+ characters, mixed character\n  classes). Ownership is first-writer-wins over a global, account-less string\n  and there is deliberately no recovery path, so a short name like `invoices`\n  or `billing` could be claimed by anyone for $0.001 and would lock out the\n  rightful owner permanently. Making real namespaces unguessable means there\n  is nothing worth squatting. Use `myapp-<uuid>`. Names claimed before this\n  rule keep working.\n- **The free lifecycle endpoints do not reveal whether a namespace exists.**\n  `/once-key/complete` and `/once-key/release` return an identical 404 whether\n  the namespace was never claimed or your token is wrong, because a free\n  existence oracle is the reconnaissance step before squatting. The paid\n  `/once-key` and `/vault/*` routes answer `200` with `status: \"forbidden\"`\n  instead of `403`, so that each probe actually settles a payment — a 4xx\n  would cancel settlement and leave the payment header replayable, making the\n  oracle free after all. **Branch on `status`, not on the HTTP code.**\n- **`namespace_token` is a bearer credential with no recovery path.** Anyone\n  holding it *is* the owner. Worse than a normal leak: `/vault/rotate-token`\n  is free and needs only the current token, so whoever steals it can rotate\n  first and lock you out irreversibly. OnceKey has no rotation at all, so a\n  leaked OnceKey token is permanent. There are no accounts, no email, and no\n  support channel that can restore access — treat these tokens like a private\n  key, and store them before you make the call that returns one.\n- **The vault cannot read your values, but it does see their names.** No key\n  held here can decrypt anything, and plaintext is never received. But the\n  item key, the namespace, the `alg` label and the size are all stored in the\n  clear, so the service can tell *which* named secrets you hold and how large\n  they are. `alg` is an advisory label: nothing here can verify that what you\n  sent was in fact encrypted. Use high-entropy namespace names — ownership is\n  first-writer-wins, so a guessable namespace can be squatted (now enforced;\n  see above).\n- **Paid routes answer completed work with 200 and a `status` field**, never a\n  4xx. The x402 middleware cancels settlement above 399, so a 4xx returned\n  after the work is done gives the answer away free and leaves the payment\n  header replayable.\n- **Receipts are HMAC-signed** with `RECEIPT_SECRET`. Note that only `/once-key`\n  and the vault endpoints return a `receipt` — the stateless utilities\n  (`/pdf-parse`, `/scrape`, `/compress`) do not.\n- **The wallet is the trust anchor.** Nothing in this codebase protects the seed\n  phrase behind `X402_PAY_TO`. If it leaks, the money is gone.\n\n## Money Flow\n\n```\nAgent pays USDC on Base\n  → lands directly in your wallet (X402_PAY_TO)\n  → transfer to an exchange that supports Base USDC\n  → withdraw to your bank\n```\n\nCanadian-friendly off-ramps: Kraken, Newton, Shakepay, Coinbase. US-only\nservices such as Mercury and Sphere Pay are not an option.\n\n## Known Gaps\n\n- **No payment has settled on mainnet.** Revenue is $0.00. Settlement itself is\n  no longer unproven: on 2026-09-04 the full pipeline ran on Base Sepolia and\n  0.001 USDC moved on chain, confirmed by reading the transfer log rather than\n  trusting the facilitator. What is untested on mainnet is only that the same\n  code paths work against a chain where the money is real.\n- **Not in the PayAI Bazaar on mainnet**, though the mechanism is now proven\n  rather than assumed. Listing needs a *settled* payment per route — reaching\n  `/verify` does nothing, which is why the catalogue held 0 of 28,095 of our\n  routes. On testnet all 9 appeared within seconds. Announcing the mainnet\n  catalogue costs $0.068, not the ~$1 assumed for months.\n- **No evidence of demand.** `/stats` records the funnel precisely so that\n  \"nobody has found us\" and \"agents arrive and refuse to pay\" stop looking\n  identical. So far the answer is the first one.\n- **No A2A Agent Card, deliberately.** Crawlers request\n  `/.well-known/agent-card.json` and `/.well-known/agent.json` roughly 80 times\n  a day and get a `404`, which is the correct answer. Under A2A v1.0 a card is\n  a binding declaration, not a description: §8.3.1 requires each interface to\n  \"accurately declare its transport protocol and URL\", §8.3.2 obliges clients\n  to call it, and §5.1/§3.1 make a declared interface owe all 11 operations\n  (`SendMessage`, `GetTask`, `ListTasks`, `CancelTask`, …). This is an MCP\n  server, not an A2A agent. Publishing a card would advertise a broken agent to\n  every A2A crawler and registry — strictly worse than the `404`. Revisit only\n  alongside a real A2A binding.\n- **`/scrape`, `/pdf-parse` and `/compress` compete with free libraries.** The\n  defensible endpoints are `/once-key` and `/vault`: coordination primitives a\n  single agent cannot self-host, because they answer questions about what\n  *other* agents have done.\n- OnceKey namespace tokens cannot be rotated (vault's now can).\n- The `extractive` compression strategy is heuristic and unvalidated against real\n  agent workloads.\n\n## License\n\nMIT — see [LICENSE](./LICENSE).\n",
  "bytes": 31333,
  "sha": "403a35362a46e5f867d699dfc7cc62c79ece5a23166c6abcab892ab38ddce661",
  "repo_slug": "olie-kiss/agentic-endpoints",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_com_oliverkiss_agentic_endpoints_2972ed32/readme"
}