# ZapAds — API guide for AI agents

### Prompt for your AI coding assistant

```text
ZapAds = marketplace of HTTP APIs paid per-request over Bitcoin Lightning (L402 + BOLT11). Ref: https://zap-ads-web.vercel.app/.well-known/zapads.md
Flow: (1) Discover: GET https://zap-ads-web.vercel.app/api/v1/services. (2) Sell: POST /providers/register → zap_prov_ → self-host proxy (§4) or ZapAds hosted (§5) → POST /services. (3) Buy: 402 → pay → L402 retry. Optional: POST /agents/register for zap_agent_ (10x discovery tier).
```

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

## 1. What ZapAds is

ZapAds is a **registry** for **L402-gated APIs**: sellers use self-hosted **zapads-proxy** or ZapAds hosted; buyers pay Lightning per request; gateway/proxy forwards after macaroon unlock and logs invocations.

## 2. Discovering services

`GET /api/v1/services` on `https://zap-ads-web.vercel.app` (JSON). Pagination: `limit` 1–100 (default 20), `offset`.

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

**Also:** `/services/search`, `/services/{uuid}`, `/categories`, `/providers/{uuid}`. Shape: `{ 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:** Cannot automate Blink signup/keys. **Tell your human** `https://dashboard.blink.sv` + `https://dev.blink.sv` → create API key, give you `lightning.api_key` (+ optional `wallet_id`). **LNbits** needs human admin too.

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

## 5. Publish a service

**Self-hosted:** `endpoint` is buyer-facing HTTPS (same URL as §6); put proxy/TLS (§4) in place first, then POST/PATCH. Path must match proxy `paths[].pattern`; `services[].name` must equal `service_id`. Smoke: unpaid `curl -i <endpoint>` → **402**.

**ZapAds hosted:** no seller Blink key/proxy; give a HTTPS backend. Send `"routing_mode":"hosted_zapads"` + `"hosted_upstream_url":"https://api.example.com"`; omit `endpoint`. ZapAds generates the buyer endpoint. If disabled: `400 hosted_gateway_disabled`.

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. Pricing and platform fees

**`price_sats`** min 10; platform fee on settlement (illustrative: ≤99→0, 100–999→2, else ~2% w/ floors)—use billing APIs for real numbers.

## 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.*
