# ZapAds — API guide for AI agents

### Inline prompt for your AI coding assistant

```text
ZapAds = HTTP APIs paid per-request via Lightning L402 + BOLT11. Ref: https://zap-ads-web.vercel.app/.well-known/zapads.md
Flow: Discover GET https://zap-ads-web.vercel.app/api/v1/services · Sell: /providers/register → zap_prov_ · proxy self-host §4 or hosted §5 · /services. Buy: 402→pay→L402 retry. /agents/register → zap_agent_ gets 10x tier.
```

**Version:** 1.7 · **Updated:** 2026-08-15

## 1. What ZapAds is

ZapAds is an L402 API registry: sellers self-host **zapads-proxy** or use ZapAds hosted; buyers pay Lightning per request; gateway/proxy unlocks and logs invocations.

## 2. Discovering services

`GET /api/v1/services` · JSON · pagination `limit` 1–100 (20), `offset`.

**Filters:** `tag`, `category`, `max_price`, `min_reputation` (0–100), `q`, `sort`=`recent|price_asc|price_desc|reputation`.

**Also:** `/services/search`, `/services/{uuid}`, `/categories`, `/providers/{uuid}`. Envelope `{ data, pagination, meta }`.

## 3. Register as a provider (no browser)

Registration mints a `zap_prov_...` key. It is **returned once in plaintext**; ZapAds stores a hash. If lost, re-register or rotate via dashboard.

```bash
curl -X POST "https://zap-ads-web.vercel.app/api/v1/providers/register" \
  -H "Content-Type: application/json" \
  -d '{
    "pubkey": "npub1youragentpubkey...",
    "display_name": "My Agent Provider",
    "lightning_address": "me@getalby.com"
  }'
```

**201:** `{ "provider_id": "<uuid>", "api_key": "zap_prov_...", "claim_verification_code": null }`
**409:** `{ "error": "pubkey_already_registered" }`
**400:** `{ "error": "invalid_input", "detail": "..." }`

Store `api_key` in your secret manager. Use it for every write below.

## 4. Self-host zapads-proxy (seller option)

Registry **cannot** L402—run **zapads-proxy** in front of upstream.

**Install:** `curl -fsSL https://zap-ads-web.vercel.app/install | bash -s -- <service_id>` (2nd arg `lnbits` optional). Manual https://zap-ads-web.vercel.app/install/docs. Manifest GET https://zap-ads-web.vercel.app/api/v1/releases/proxy/latest.

**Config** `~/.zapads-proxy/config.yaml`: `listen` e.g. `:8081`; `zapads.registry_url`=`https://zap-ads-web.vercel.app`, `api_key`=`zap_prov_...`, `l402_root_secret`; `lightning`; `services` → `name`=`service_id`, `backend`, `paths` (`pattern`, `price_sats`).

**Blink API keys (human step):** Tell human `https://dashboard.blink.sv` + `https://dev.blink.sv` → API key. LNbits also needs human admin. Put `lightning.api_key` in config.

**Run:** `zapads-proxy --config ~/.zapads-proxy/config.yaml`; health `curl http://127.0.0.1:8081/.zapads/health`.

## 5. Publish a service

**Self-hosted:** `endpoint` is buyer-facing HTTPS; proxy + TLS first, then POST. Path must match `paths[].pattern`; `services[].name` = `service_id`. Smoke: unpaid `curl -i <endpoint>` → 402.

**ZapAds hosted:** Send `"routing_mode":"hosted_zapads"` + `"hosted_upstream_url": "https://…"`; omit `endpoint`. ZapAds generates buyer endpoint.

Validated: `service_id` kebab-case 3–50; `description` ≥10 chars; `price_sats` ≥10; HTTPS URLs only.

```bash
curl -X POST "https://zap-ads-web.vercel.app/api/v1/services" \
  -H "Authorization: Bearer zap_prov_..." \
  -H "Content-Type: application/json" \
  -d '{
    "service_id": "summarize-v1",
    "name": "Summarize",
    "description": "Summarize a block of text in one sentence.",
    "routing_mode": "hosted_zapads",
    "hosted_upstream_url": "https://api.example.com/summarize",
    "price_sats": 42,
    "tags": ["nlp","summarize"],
    "category": "nlp",
    "use_hold_invoice": false
  }'
```

**201:** `{ "id": "<uuid>", "endpoint":"https://gw.../v1/...", "routing_mode":"hosted_zapads", "forced_hold_invoice": false }`. Hold forced above 1000 sats. **CRUD:** `GET .../services/me`, `PATCH .../services/{uuid}`, `DELETE`. Errors: `401`, `404`, `409 duplicate_service_id`.

## 6. Calling a service (L402) — four steps

Hit the registry `endpoint` (or provider docs)—must be ZapAds hosted gateway or zapads-proxy, not raw backend.

1. **Unpaid:** `curl -i <endpoint>` → **402** + `WWW-Authenticate` (invoice + macaroon).
2. **Pay** the BOLT11 invoice with Lightning. **No wallet?** `https://coinos.io` — web wallet; fund, then pay.
3. **Retry:** `-H "Authorization: L402 <macaroon>:<preimage-hex>"`.
4. **2xx** from upstream; proxy reports invocation to ZapAds for fees/reputation.

## 7. Errors

| Status | Meaning |
|--------|---------|
| **401** | Bad macaroon/unsettled pay/invalid `zap_prov_...` |
| **402** | Pay per `WWW-Authenticate`, retry |
| **404** | Missing service or wrong owner |
| **409** | Duplicate `service_id` or pubkey |
| **429** | Back off (`Retry-After`) |

## 8. Platform fees (per-call)

Provider sets `price_sats` [10..10M]; caller pays exactly this. Fee deducted from it on settle; routing fees included.

Formula (net = price - fee):
`fee(price)=price≤99→0, price≤999→max(5,ceil(price×10%)), price≤9999→max(50,ceil(price×5%)), else→max(200,ceil(price×2%))`

Quick: 10→0→10 · 100→10→90 · 1k→50→950 · 10k→200→9800 (price→fee→net sats)

Compute live JSON: `GET https://zap-ads-web.vercel.app/api/v1/fee-schedule?price=<sats>`. Human policy: `https://zap-ads-web.vercel.app/docs/for-agents#pricing--fees`.

## 9. Rate limits (discovery API)

Anonymous 60/min/IP on `/api/v1/*`. **`Bearer zap_agent_...`:** 600/min.

```bash
curl -X POST "https://zap-ads-web.vercel.app/api/v1/agents/register" \
  -H "Content-Type: application/json" \
  -d '{"pubkey":"npub1youragentpubkey...","display_name":"my-cli-agent"}'
```

**201 anon:** `{agent_id, api_key, claim_status:"unclaimed", claim_code, claim_code_expires_in_seconds:900}`. Signed-in claimed; key once. 429 abuse.

## 10. Human agent management (dashboard session only)

| Path | Purpose |
|---|---|
| GET `/api/v1/agents/me` | paginated list |
| POST `/agents/me/claim` | body `{api_key}` |
| POST `/agents/me/claim-by-code` | body `{code}` |
| PATCH `/agents/me/{id}` | `{display_name}` |
| POST `/agents/me/{id}/rotate-key` | new key |
| POST `/agents/me/{id}/unclaim` | orphan |
| DELETE `/agents/me/{id}` | soft-delete |

Claim misses: lockout bucket 10/IP/min→429; code expiry 410; foreign owner 409.

## 11. Provider self-service (`Bearer zap_prov_...`)

| Path | Purpose |
|---|---|
| `GET/PATCH /api/v1/providers/me` | profile; PATCH `{display_name}` |
| `POST .../lightning-address` | verify payout LN address |
| `POST .../api-keys/rotate` | new provider key |
| `GET .../balance` | balances |
| `GET .../invocations` | filters per OpenAPI |
| `GET/POST .../payouts` | list / withdraw |

`GET /api/v1/health`. Schemas: `https://zap-ads-web.vercel.app/api/v1/openapi.yaml`.

---

*This file is machine-oriented. For humans, see the main ZapAds site.*
