agentic-endpoints

MCP serverAI & models

Pay-per-call tools for autonomous agents, settled in USDC on Base via x402.

Available today. Use it from your connected AI after setup.

Connect ahel once, and every AI you use reads what you have installed.

Then ask your AI: use the meetings search tool from agentic-endpoints

From the project's README

As published by olie-kiss/agentic-endpoints in README.md.

x402-powered micro-SaaS utilities for autonomous AI agents. Pay-per-call with USDC micropayments on Base — no API keys, no accounts, no subscriptions.

Live at https://ai.oliverkiss.com

Endpoints

RouteMethodPriceDescription
/once-keyPOST$0.001Claim an action exactly once, and replay its recorded result
/once-key/completePOSTFreeRecord the outcome of a claimed action
/once-key/releasePOSTFreeSurrender a claim whose work failed
/scrapePOST$0.005Web scraping and text extraction
/pdf-parsePOST$0.01PDF text extraction from a URL
/compressPOST$0.005Token compression / context reduction for LLMs
/x402/verifyPOST$0.003Check a stranger's x402 endpoint before paying it
/meetings/importPOST$0.004Import a meeting transcript, private or searchable
/meetings/searchPOST$0.006Full-text search across your meetings
/meetings/summarizePOST$0.030Answer a question from your meetings, with citations
/meetings/getPOST$0.002Read one meeting in full
/meetings/listPOST$0.001List meetings (metadata only)
/meetings/deletePOST$0.001Delete a meeting and its index entry
/vault/storePOST$0.02Store a client-encrypted item
/vault/retrievePOST$0.02Retrieve a client-encrypted item
/vault/deletePOST$0.005Delete an item
/vault/existsPOST$0.001Check whether an item exists
/vault/listPOST$0.001List the keys in a namespace (metadata only)
/vault/rotate-tokenPOSTFreeReplace a namespace token that may have leaked
/credits/buyPOST$5.00Buy $6.00 of prepaid credit (20% bonus)
/credits/buy-25POST$25.00Buy $32.50 of prepaid credit (30% bonus)
/credits/balancePOSTFreeCheck a credit balance
/revenueGETFreeOn-chain USDC received, read from Base
/mcpPOSTFree to listRemote MCP server; each tool costs its route's price
/GETFreeService discovery (JSON) or landing page (HTML)
/healthGETFreeHealth check
/statusGETFreeUptime, error rate and latency, derived from recorded behaviour
/statsGETFreeDemand funnel: challenged, paid, free, by route
/llms.txtGETFreeProse description for a model given only a URL
/openapi.jsonGETFreeOpenAPI 3.1 description

GET / content-negotiates: send Accept: application/json for the machine-readable endpoint catalogue, anything else gets the HTML landing page.

How It Works

  1. An agent requests a paid endpoint
  2. The server replies HTTP 402 Payment Required with the price, network and receiving address
  3. The agent signs a USDC transfer on Base and retries with an X-PAYMENT header
  4. The facilitator verifies and settles the payment, then the handler runs

Try it — this returns a real 402 challenge, not an error:

curl -i -X POST https://ai.oliverkiss.com/once-key \
  -H 'Content-Type: application/json' \
  -d '{"namespace":"demo","action_key":"abc123"}'

MCP Server

Every paid endpoint is also exposed as an MCP tool over Streamable HTTP at https://ai.oliverkiss.com/mcp, implementing revision 2026-07-28 (stateless: no initialize handshake, no session header) with a fallback for clients still sending the 2025-06-18 handshake.

{
  "mcpServers": {
    "agentic-endpoints": { "type": "http", "url": "https://ai.oliverkiss.com/mcp" }
  }
}

tools/list is free so clients can discover the catalogue. tools/call re-enters the corresponding paid route in-process, so it passes the same x402 gate, body cap and validation as a direct HTTP call. Without a valid X-PAYMENT header the tool returns isError: true and a machine-readable payment demand (price, payTo, asset, network) rather than performing work.

Two Ways To Pay

Per-call x402 caps revenue at whatever a buyer will tolerate signing: $1,000 at $0.005 a call is 200,000 signatures. Prepaid credits sell the same work once, in an amount worth the transaction, and let callers whose wallets cannot sign per request use the service at all.

Both paths run side by side and neither is privileged:

# Per call, unchanged
curl -X POST https://ai.oliverkiss.com/compress -H "X-PAYMENT: ..." -d '{"text":"..."}'

# Or prepay once, then no signatures
curl -X POST https://ai.oliverkiss.com/credits/buy -H "X-PAYMENT: ..."   # -> credit_token
curl -X POST https://ai.oliverkiss.com/compress \
  -H "X-Credit-Token: ae_..." -d '{"text":"..."}'

Omitting X-Credit-Token produces exactly the 402 challenge it always did, so the Bazaar listing and every existing integration are unaffected.

Credits are integer micro-dollars, never floats: $0.001 has no exact binary representation, and a ledger that drifts is worse than no ledger. Each account is its own Durable Object addressed by the hash of its token, so the balance check and its debit are atomic and one account cannot queue behind another. Calls are debited before the work and refunded if it 5xxs, because an outage must not bill a customer for nothing.

The token is shown once and is not recoverable — only its hash is stored.

Discovery

An endpoint nobody can find earns nothing, so the service is registered wherever agents actually look. Every catalog below was chosen because it verifies ownership by domain or wallet rather than by a financial account — the Coinbase CDP Bazaar is skipped for exactly that reason.

CatalogStatusHow
PayAI BazaarNot listed on mainnet — mechanism proven on testnetListing 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
x402-list.comSubmitted, pending reviewPOST /api/v1/submit; free because the service is on a custom domain
Official MCP RegistryPublished — 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
npmPublished — agentic-endpointscd 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
SmitheryPublished — kiss-olie/agentic-endpoints, quality 98/100npx -y @smithery/cli@latest mcp publish "https://ai.oliverkiss.com/mcp" -n kiss-olie/agentic-endpointsno auth needed, and it updates in place. The scan finds all 17 tools (re-publish after adding one, or it keeps reporting the old count). 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
GlamaNot listed; claim file pre-placedglama.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

Aggregators such as PulseMCP ingest from the official registry, so publishing there covers several directories at once. The repository is public, so directories that crawl source repos can now see it too.

Machine-readable descriptions are generated from the same pricing table that gates payment, so they cannot drift from what is actually charged: /llms.txt for a model handed a bare URL, /openapi.json for tooling, plus /robots.txt and /sitemap.xml. Tests assert the prices agree across all of them.

Revenue Monitoring

The service could demand payment for months with no way to tell whether a payment ever arrived — including the failure mode where payments verify but never settle. A cron trigger sweeps Base every 5 minutes for USDC Transfer logs into the receiving address and folds them into a running ledger in KV.

Revenue is read from the chain, not from our own logs or the facilitator's word, so it cannot be inflated by a bug on either side. GET /revenue publishes the ledger for free — it costs nothing and gives a prospective caller evidence the service actually transacts.

Set ALERT_WEBHOOK_URL to a Discord webhook to be notified when money lands:

npx wrangler secret put ALERT_WEBHOOK_URL

The first scan starts the watermark at the current chain head rather than genesis; scanning millions of blocks through a public RPC node would fail repeatedly and never establish a watermark at all. The watermark advances only on a successful scan, so a transient RPC failure is retried on the next tick with nothing missed.

Buyer signals

Revenue monitoring only sees money that arrived. The service takes thousands of requests a day and, so far, no revenue — and reviewing the callers, almost all of it is liveness probes, trust scanners and directory crawlers, which are indistinguishable from a customer in an access log. The gap that leaves is a genuine buyer being refused for a fixable reason and leaving without ever appearing as a distinguishable line.

src/lib/tripwire.ts emits one structured line per notable request, and the same signals are persisted (see below), so the durable answer is:

curl -s https://ai.oliverkiss.com/stats | jq .buyer_signals

To watch them arrive live, or to search the raw log lines:

npx wrangler tail --format json | grep BUYER_SIGNAL

Searching Workers Logs after the fact needs a full-text needle, not a $metadata.message filter: the line is a JSON blob, and the message filter does not match inside it — it silently returns zero rather than erroring, which reads exactly like "no buyer has ever appeared".

SignalMeaningConfidence
payment_attemptCarried a payment authorizationhigh
credit_useAn existing customer spending prepaid credithigh
prospect_402An unrecognised caller hit a priced routelow

It keys on behaviour, not identity. A monitor never carries a payment authorization, so anything that does is trying to buy — and that holds even when the User-Agent claims to be a bot. User-Agent only suppresses known noise and grants nothing, because it is forged for free.

Note that the SDK inherits Node's User-Agent, which is exactly what an anonymous script sends, so no string positively identifies a customer. Anything unrecognised stays unclassified rather than being written off; the tests pin this, since misfiling it would suppress the one signal worth having. prospect_402 is a guess and is marked low confidence for that reason — it should never be read as a sale.

The check runs ahead of the credit and x402 gates, both of which answer without reaching application code, because a refusal is precisely the event worth recording. No header values are logged and the caller IP is dropped in favour of the country Cloudflare already derived.

Signals are also persisted in the stats Durable Object and served on /stats, because a log line is only readable while something is tailing it — a first customer arriving overnight would otherwise leave a line nobody read, which would then age out. The event ring is bounded by count rather than age: the first payment attempt could be the only one for months.

Note that buyer_signals.payment_attempt starts at 1 from a deployment verification probe on 2026-09-09 (ua: persistence-verify/1.0). It could not be removed without exposing a mutation endpoint. The first genuine buyer is entry two.

Published SLOs

An agent choosing between two paid services has no way to tell which one works. GET /status is free and answers that from recorded behaviour, not a promise:

{
  "uptime_24h": "100.00%",
  "uptime_window": "0.5h",
  "cron_ticks_24h": 6,
  "requests_48h": 88,
  "error_rate_48h": "0.00%",
  "unknown_path_requests_48h": 10,
  "latency_ms": { "p50_at_most": 25, "p95_at_most": 500, "p99_at_most": 500 }
}

Getting the number honest mattered more than getting it published. It first shipped reporting an 11.39% error rate; every one of those was a 404 on a path that never existed — my own probes and passing crawlers — plus tokens that were correctly rejected. An agent reading that would have taken its money elsewhere and been right to. So client errors (4xx) are counted separately from failures (5xx), and requests to unknown paths are excluded from the rate entirely and surfaced as a raw count instead. Otherwise any stranger could degrade our published reliability just by scanning for /wp-admin.

Latency comes from histogram buckets, so the figures are reported as p95_at_most — a bound, which is what a bucket can honestly support, rather than a precise percentile it cannot. Uptime credits only the window actually observed, so day one does not claim 24 hours from an hour of heartbeats. The heartbeat is written before the revenue scan, so an outage at a public RPC node is not reported as ours.

GET /stats publishes the demand funnel — challenged, paid, free, per route — which is the only thing that distinguishes "nobody has found us" from "agents arrive and decline to pay".

Client SDK

sdk/ is a dependency-free TypeScript client. It deliberately does not sign payments — it takes a credit token, or your own x402-aware fetch, so it never needs a private key.

npm install agentic-endpoints

Its reason to exist is exactlyOnce, which collapses the claim/complete/release protocol into one call: it handles all four claim outcomes, records the result so later callers can replay it, releases the claim if your work throws, and rethrows your error untouched.

Stack

  • Runtime: Cloudflare Workers + Durable Objects
  • Payments: x402 protocol (USDC on Base mainnet, exact scheme)
  • State: Durable Object SQLite (OnceKey and Vault)
  • Framework: Hono

Setup

Prerequisites

  • Node.js 20+
  • Cloudflare account (Workers Paid plan — $5/mo, required for Durable Objects)
  • A wallet address on Base to receive USDC
  • Wrangler CLI (use npx wrangler if not installed globally)

Install

npm install

Configure Secrets

# Required — your receiving wallet address (Base USDC)
npx wrangler secret put X402_PAY_TO

# Required — HMAC key for signing JSON receipts
npx wrangler secret put RECEIPT_SECRET

Do not set FACILITATOR_URL unless you mean to override the default. It must be a valid URL. If it is set to anything else, every paid endpoint returns 500 instead of a 402, and the failure is only visible in npx wrangler tail — this silently broke all payments once already.

The default facilitator is https://facilitator.xpay.sh: free, no signup, and it supports Base mainnet.

Develop and deploy

npm run dev
npm run deploy

# Tests run against the real Workers runtime via workers-pool.
# The Durable Object tests call the objects directly, since every paid
# route answers 402 without a real on-chain payment.
npm test
npm run typecheck

# Re-announce every route to the PayAI Bazaar. This SPENDS REAL USDC: a route
# is catalogued only once a payment for it settles. It refuses to start unless
# the wallet covers the whole run.
export X402_TEST_PRIVATE_KEY=0x...
node scripts/trigger-indexing.mjs

# Make one real paid call. Requires a THROWAWAY wallet holding a little USDC
# on Base; the key is read from the environment and never written anywhere.
export X402_TEST_PRIVATE_KEY=0x...
node scripts/paid-test.mjs /compress

Proving settlement without spending

Settlement is the one step that cannot be tested by inspection, and on mainnet every attempt costs real USDC. [env.testnet] deploys the same code to a workers.dev URL priced in Base Sepolia USDC, which faucet.circle.com gives away with no account.

npx wrangler deploy --env testnet
X402_TEST_PRIVATE_KEY=0x... node scripts/paid-test.mjs /once-key \
  https://agentic-endpoints-testnet.<subdomain>.workers.dev --testnet

Only the exact string eip155:84532 selects Sepolia; anything unrecognised falls back to mainnet. That asymmetry is deliberate — a Worker that wrongly demanded testnet tokens would hand out real work for money anyone can mint.

Announcing to the Bazaar

X402_TEST_PRIVATE_KEY=0x... node scripts/trigger-indexing.mjs

Pays for each route once, which is what puts it in the catalogue. $0.082 for all 14 utility routes; credit packs are excluded unless you pass --include-credits. The script checks the payer's USDC balance against that total first and refuses to start if it falls short, because a wallet that runs dry midway leaves the catalogue half-populated with no way to tell which routes made it.

API Examples

OnceKey (exactly-once execution)

A claim on its own is only half an idempotency key. The agent that loses the race needs to know what happened, or it has to either block forever or repeat the side effect anyway — which is the failure this endpoint exists to prevent. So the lifecycle is three calls, and only the first one costs money.

POST /once-key
{
  "namespace": "payment-webhooks",
  "action_key": "stripe_evt_abc123",
  "payload_sha256": "e3b0c44298fc...",
  "ttl": 86400,
  "lease_ttl": 300
}
statusMeaning
claimedYou won. Do the work, then call /once-key/complete
duplicateAlready 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
heldClaimed 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
in_progressAnother caller holds a live lease. Wait retry_after; do not do the work
conflictSame key, different payload hash. Your key derivation is wrong; never retry
POST /once-key/complete          // free
{ "namespace": "...", "action_key": "...", "namespace_token": "...",
  "result": { "charge_id": "ch_abc" } }

Every later claim of that key returns duplicate with that result.

duplicate always means completed. A claim that was started but never completed reports held instead, precisely so a caller cannot mistake work that is still in flight — or that died half way through — for work that succeeded. The SDK raises HeldError rather than returning a result of undefined.

If the work fails, POST /once-key/release (free) frees the key immediately.

lease_ttl is opt-in, deliberately. Without it a claim is held for its full ttl and nothing can ever run your side effect twice. With it, a claimant that crashes is presumed dead once the lease lapses and the next caller takes over with recovered: true. Leases on by default would have made every key claimed by the original claim-only API silently reclaimable — a duplicated charge is a far worse failure than a key that needs a retry under a fresh name.

The agentic-endpoints npm package wraps all of this in one call:

const { outcome, result } = await client.exactlyOnce(
  { namespace: "billing", actionKey: `charge:${order.id}`, leaseTtl: 300 },
  async () => stripe.charges.create({ amount: order.total }),
);

Web scraper

POST /scrape
{ "url": "https://example.com/article", "format": "text" }

PDF parser

POST /pdf-parse
{ "url": "https://example.com/document.pdf", "pages": [1, 2] }

Inflates FlateDecode content streams, expands PDF 1.5+ object streams, and maps character codes through each font's /ToUnicode CMap, so subset and composite fonts come back as real text rather than glyph indices. Encrypted PDFs and image-only scans return 422 rather than filler content, so callers are not billed for a result that is known to be useless.

Token compressor

POST /compress
{ "text": "Your very long text here...", "target_tokens": 500, "strategy": "extractive" }

Meeting memory (agent-queryable transcripts)

Meeting notetakers keep transcripts inside their own app, where the only reader is a human scrolling a sidebar. This puts them somewhere your agents can ask questions of them.

You choose, per meeting, whether this service can read it. The choice is required, and the wrong field for the mode is refused rather than guessed.

visibilityYou sendStored asSearchableWe can read it
private (default)ciphertextopaque bytesnono
queryabletranscriptplaintext + FTS indexyesyes

Sending plaintext as private is an error rather than a quiet indexing, and sending ciphertext as queryable is an error rather than a meeting that can never match a search. Both refusals exist because the failure they prevent is silent and only discovered long after it matters.

# First import claims the namespace and returns a one-time token.
curl -X POST https://ai.oliverkiss.com/meetings/import \
  -H "Content-Type: application/json" \
  -d '{
    "namespace": "my-meetings-4f9c2b1e8d7a",
    "title": "Pricing review",
    "occurred_at": "2026-09-01T15:00:00.000Z",
    "source": "webvtt",
    "visibility": "queryable",
    "participants": ["Alice", "Bob"],
    "transcript": "Alice: we agreed to ship the redesign before the audit."
  }'

# Then ask a question of every meeting at once.
curl -X POST https://ai.oliverkiss.com/meetings/search \
  -H "Content-Type: application/json" \
  -d '{
    "namespace": "my-meetings-4f9c2b1e8d7a",
    "namespace_token": "<from the first import>",
    "query": "pricing"
  }'

Search returns ranked excerpts plus searched_meetings and private_meetings_skipped. Read them. If searched_meetings is 0, an empty result means nothing was searched, not that the topic was never discussed — and an agent that conflates those will confidently tell a user something never happened.

Via MCP the same thing is meetings_search, meetings_import, meetings_get and meetings_list.

Asking a question instead of reading excerpts

/meetings/search hands back ranked excerpts and leaves the reasoning to you. /meetings/summarize takes a plain-language question, retrieves the relevant transcripts, and answers from them with a citation per claim:

curl -X POST https://ai.oliverkiss.com/meetings/summarize \
  -H "Content-Type: application/json" \
  -d '{
    "namespace": "my-meetings-4f9c2b1e8d7a",
    "namespace_token": "<from the first import>",
    "question": "what did we decide about pricing?"
  }'
{
  "status": "ok",
  "answer": "You agreed to defer the pricing change until Q3 [6f1c3b90-…].",
  "consulted": [
    { "meeting_id": "6f1c3b90-…", "title": "Pricing review", "chars_used": 4820, "truncated": false }
  ],
  "terms": ["decide", "pricing"],
  "searched_meetings": 12,
  "private_meetings_skipped": 3
}

Shortened here. Read the whole README on GitHub.

Tools it offers (16)

What this server listed when ahel dialed its public endpoint in Sep 2026, with no key and no account of yours. The names are the server’s own.

  • meetings_search
  • meetings_import
  • meetings_get
  • meetings_list
  • once_key_claim
  • once_key_complete
  • once_key_release
  • vault_store
  • vault_retrieve
  • vault_exists
  • vault_delete
  • vault_list
  • vault_rotate_token
  • pdf_parse
  • scrape
  • compress

Signals

Last commit
Sep 2026
Advanced
Delivery
agentic-endpoints MCP server → your ahel gateway (mcp.ahel.ai) → every connected AI client.
Catalog kind
mcp-server
Gateway key
com-oliverkiss-agentic-endpoints
Source
github.com/olie-kiss/agentic-endpoints
Hosted endpoint
https://ai.oliverkiss.com/mcp