# API reference

This document describes the free discovery API. Paid provider routes are documented per listing and return their own x402 challenge. The OpenAPI 3.1 document at `/openapi.json` is the canonical machine-readable contract for discovery and every paid x402 route. Paid operations include `x-payment-info` and a 402 response so x402scan can discover them.

Base URL: `https://apistead.qd.je`

Canonical reference: `https://apistead.qd.je/api`.

All discovery endpoints are free, unauthenticated and CORS-enabled. They never return a payment challenge.

Paid API calls also require no account, API key or signup. The only requirement is the x402 payment in USDC on Base.

## Marketplace status

APIStead Marketplace is not accepting new listing submissions yet. All live APIs are first-party APIs built and operated by APIStead Labs.

The APIs are live and payable. The marketplace catalogue and provider submission flow are not live yet.

## Free discovery vs paid routes

| Surface | Example | Returns | Payment |
|---|---|---|---|
| MCP | `/mcp` | Free discovery tools | None |
| Catalogue | `/agent/search`, `/agent/list`, `/agent/get`, `/agent/routes` | Listing metadata | None |
| Listing record | `/api/apis/{slug}`, `/api/apis/{slug}/md` | JSON or Markdown metadata | None |
| Docs and manifests | `/api`, `/mcp-docs`, `/skill`, `/openapi.json`, `/llms.txt` | Documentation and discovery | None |
| Paid API | `/api/v1/...`, `/api/v2/...` | Product JSON | x402 402 |

Discovery never returns a payment challenge. Only `/api/v1/...` and `/api/v2/...` product routes do. Spotify v2 is documented in [Spotify v2](#spotify-v2); its routes are under `/api/v2/spotify/...`.

## Paid calls and the 402 challenge

The 402 comes from the paid `/api/v1/...` or `/api/v2/...` route itself. It is generated by the x402 server middleware on that route, not by `/mcp`, not by `/api/apis/{slug}`, and not by a marketplace.

Call the route without payment:

```bash
curl -i "https://apistead.qd.je/api/v1/spotify/artist?id=5INjqkS1o8h1imAzPqGZBb"
```

The response is:

```http
HTTP/2 402
content-type: application/json
payment-required: <base64-encoded JSON>
```

The JSON body is the same challenge:

```json
{
  "x402Version": 2,
  "error": "Payment required",
  "resource": {
    "url": "https://apistead.qd.je/api/v1/spotify/artist?id=5INjqkS1o8h1imAzPqGZBb",
    "description": "Spotify artist metadata and top tracks from an artist link.",
    "mimeType": "application/json"
  },
  "accepts": [
    {
      "scheme": "exact",
      "network": "eip155:8453",
      "amount": "20000",
      "asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
      "payTo": "0x2E9804240E493DD8daeBa4F341a5086b92cde235",
      "maxTimeoutSeconds": 300,
      "extra": { "name": "USD Coin", "version": "2" }
    }
  ]
}
```

| Field | Meaning |
|---|---|
| `network` | `eip155:8453` is Base mainnet. Local and staging use `eip155:84532` (Base Sepolia). |
| `asset` | USDC contract on Base: `0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913`. |
| `amount` | Atomic USDC with 6 decimals. `20000` = 0.02 USDC. |
| `payTo` | The provider wallet that receives the call payment. |
| `scheme` | `exact` means an exact-amount USDC transfer authorization. |
| `maxTimeoutSeconds` | How long the challenge remains valid. |
| `extra.name` | Must be `USD Coin` for Base USDC. |

The live 402 is the authoritative price for that exact request. The `price_usdc` in a listing record is informational. If they differ, trust the 402.

Decode the header:

```bash
curl -sS -D - -o /dev/null \
  "https://apistead.qd.je/api/v1/spotify/artist?id=5INjqkS1o8h1imAzPqGZBb" \
  | tr -d '\r' \
  | awk -F': ' 'tolower($1)=="payment-required"{print $2}' \
  | base64 -d | jq
```

### Payment flow

1. Call the paid route without payment.
2. Read the `PAYMENT-REQUIRED` header or the JSON body.
3. Check `accepts[]`: scheme, network, asset, amount, `payTo` and timeout.
4. Confirm the amount is within the agent's spending cap and the network is Base mainnet.
5. Sign the USDC payment authorization with the agent wallet.
6. Retry the same URL with `PAYMENT-SIGNATURE: <base64 payload>`.
7. APIStead verifies and settles through the facilitator. Production uses PayAI.
8. On success, the route runs the data fetch and returns the product with `PAYMENT-RESPONSE`.
9. Keep the receipt. Do not retry blindly after a 402.

The agent never sends a private key or seed phrase to APIStead. The signature is produced locally by the wallet or x402 client.

### Payment headers

| Header | Content |
|---|---|
| `PAYMENT-REQUIRED` | Base64-encoded 402 challenge JSON. |
| `PAYMENT-SIGNATURE` | Base64-encoded signed payment payload. |
| `PAYMENT-RESPONSE` | Base64-encoded settlement result: success, transaction hash, network and payer. |

Paid JSON responses also include `payment` and `verification` blocks:

```json
{
  "payment": {
    "protocol": "x402",
    "protocol_version": 2,
    "network": "eip155:8453",
    "network_name": "Base",
    "asset": "USDC",
    "price_usdc": 0.02,
    "pay_to": "0x2E9804240E493DD8daeBa4F341a5086b92cde235",
    "authoritative_source": "402_challenge"
  },
  "verification": {
    "status": "verified",
    "route_verified": true,
    "last_verified_at": "2026-11-11T19:36:52.251Z",
    "receipt_url": "https://basescan.org/tx/..."
  }
}
```

### Minimal x402 client

```ts
import { wrapFetchWithPaymentFromConfig } from "@x402/fetch";
import { ExactEvmScheme } from "@x402/evm";
import { privateKeyToAccount } from "viem/accounts";

const account = privateKeyToAccount(process.env.EVM_PRIVATE_KEY as `0x${string}`);
const paidFetch = wrapFetchWithPaymentFromConfig(fetch, {
  schemes: [{ network: "eip155:8453", client: new ExactEvmScheme(account) }],
});

const response = await paidFetch(
  "https://apistead.qd.je/api/v1/spotify/artist?id=5INjqkS1o8h1imAzPqGZBb",
);

console.log(await response.json());
```

Python clients can use `x402-httpx`. Per-route Python, JavaScript and cURL examples are in `/api/apis/{slug}/md`.

## Search the catalogue

`GET /agent/search`

| Parameter | Type | Required | Description |
|---|---|---|---|
| `q` | string | No | Free-text query |
| `category` | string | No | Category filter |
| `network` | string | No | `base` |
| `max_price` | number | No | Maximum price per call in USDC |
| `verified_only` | boolean | No | Return only verified listings |
| `limit` | integer | No | Maximum results, 1 to 100, default 50 |

Example:

```bash
curl "https://apistead.qd.je/agent/search?q=weather&limit=5"
```

Response:

```json
{
  "query": "weather",
  "filters": {
    "category": null,
    "network": null,
    "max_price_usdc": null,
    "verified_only": false
  },
  "total": 1,
  "count": 1,
  "results": [
    {
      "id": "global-weather-api",
      "slug": "global-weather-api",
      "name": "Global Weather API",
      "provider": { "slug": "apistead-labs", "name": "APIStead Labs", "verified": true },
      "category": "Weather & Geospatial",
      "price": { "min_usdc": 0.001, "max_usdc": 0.002, "unit": "per_call" },
      "networks": ["base"],
      "verification": "verified",
      "verified_routes": 2,
      "total_routes": 2,
      "payment": {
        "protocol": "x402",
        "protocol_version": 2,
        "network": "eip155:8453",
        "network_name": "Base",
        "asset": "USDC",
        "price_min_usdc": 0.001,
        "price_max_usdc": 0.002,
        "unit": "per_call",
        "pay_to": "0x2E9804240E493DD8daeBa4F341a5086b92cde235",
        "authoritative_source": "402_challenge"
      },
      "verification_detail": {
        "state": "verified",
        "verified_routes": 2,
        "total_routes": 2,
        "last_verified_at": "2026-11-11T18:08:38.605Z",
        "receipt_url": "https://basescan.org/tx/0x..."
      },
      "links": {
        "docs": "https://apistead.qd.je/api",
        "json": "https://apistead.qd.je/api/apis/global-weather-api",
        "markdown": "https://apistead.qd.je/api/apis/global-weather-api/md",
        "routes": "https://apistead.qd.je/agent/routes?id=global-weather-api",
        "receipts": "https://apistead.qd.je/agent/receipts?id=global-weather-api",
        "api_reference": "https://apistead.qd.je/api",
        "mcp_guide": "https://apistead.qd.je/mcp-docs",
        "skill": "https://apistead.qd.je/skill",
        "openapi": "https://apistead.qd.je/openapi.json"
      }
    }
  ],
  "discovery": {
    "free": true,
    "payment": "Pay the provider's own x402 route. APIStead never charges for discovery.",
    "docs": "https://apistead.qd.je/api",
    "mcp": "https://apistead.qd.je/mcp"
  },
  "generated_at": "2026-11-10T00:00:00.000Z"
}
```

## List the catalogue

`GET /agent/list`

| Parameter | Type | Required | Description |
|---|---|---|---|
| `category` | string | No | Category filter |
| `network` | string | No | `base` |
| `limit` | integer | No | Page size, default 20, maximum 100 |
| `cursor` | string | No | Pagination cursor |

Response fields: `total`, `count`, `next_cursor` and `results`.

## Get one listing

`GET /agent/get?id={slug}`

Returns the listing with routes, verification and reliability. The `id` can be a slug or a listing UUID.

## Get routes

`GET /agent/routes?id={slug}`

Returns the method, path, price, description and examples for each route. Use this response to construct the paid call.

## Get receipts

`GET /agent/receipts`

Omit `id` to list recent receipts. Pass `id={slug}` to get receipts for one listing.

Receipt fields include the listing, route, amount, network, transaction hash, block number and timestamp.

## List bundles

`GET /bundles`

Returns outcome-oriented workflows with steps, cost estimates and prompts. Bundle pages are recipes. Each route settles separately.

The HTTP `/bundles` route is not routed in API-only mode. Bundle data remains available through the MCP `list_bundles` tool.

## MCP

`POST /mcp`

Streamable HTTP MCP endpoint. It exposes eight discovery tools: `search_apis`, `list_apis`, `list_bundles`, `get_api`, `get_routes`, `get_receipt`, `check_reliability` and `submit_listing`.

`submit_listing` returns the current submission status. APIStead Marketplace is not accepting new listing submissions yet.

MCP guide: `/mcp-docs`

## Per-listing records

| Endpoint | Format |
|---|---|
| `/api/apis/{slug}` | JSON |
| `/api/apis/{slug}/md` | Markdown |

Both views read the same catalogue record. The JSON view includes normalized `routes` with `url`, `price_usdc`, `pay_to`, `example_request`, `example_response`, `verified`, `last_checked_at`, `latency_ms` and `uptime_30d`. The Markdown view includes the same route details, example responses, payment terms, verification receipt, FAQ and code examples so an agent can read one file.

The HTML listing page at `/apis/{slug}` exists in the application but is not routed in API-only mode.

### Spotify v2

`/api/v2/spotify/...` is the additive Spotify generation. The five v1 routes remain live and unchanged. V2 adds keyword search, complete artist discographies, artist playlist sections, related artists and similar albums. Every v2 route costs $0.03 USDC on Base.

```bash
curl -i "https://apistead.qd.je/api/v2/spotify/search?q=daft%20punk&type=track&limit=5"
```

| Method | Route | Returns |
|---|---|---|
| GET | `/api/v2/spotify/search?q=` | Tracks, albums, artists, playlists, podcasts, episodes, audiobooks, genres and users with per-type counts and totals |
| GET | `/api/v2/spotify/search/podcasts?q=` | Podcast search |
| GET | `/api/v2/spotify/search/episodes?q=` | Podcast episode search |
| GET | `/api/v2/spotify/search/audiobooks?q=` | Audiobook search |
| GET | `/api/v2/spotify/search/users?q=` | Public user search |
| GET | `/api/v2/spotify/playlist?url=` | Playlist metadata and every ordered track; no song limit |
| GET | `/api/v2/spotify/album?url=` | Album metadata and full track list |
| GET | `/api/v2/spotify/artist?url=` | Artist profile, world rank and top tracks |
| GET | `/api/v2/spotify/artist/top-tracks?url=` | Ranked top tracks with album context |
| GET | `/api/v2/spotify/artist/discography?url=` | Complete artist discography: all albums, singles and EPs by default |
| GET | `/api/v2/spotify/artist/playlists?url=` | Playlists owned by or associated with the artist |
| GET | `/api/v2/spotify/artist/related?url=` | Related artists |
| GET | `/api/v2/spotify/artist/discovered-on?url=` | Playlists that surface the artist |
| GET | `/api/v2/spotify/artist/featuring?url=` | Playlists featuring the artist |
| GET | `/api/v2/spotify/track?url=` | Track metadata, album context and copyright lines |
| GET | `/api/v2/spotify/track/similar-albums?url=` | Albums similar to a track |
| GET | `/api/v2/spotify/podcast?url=` | Podcast metadata |
| GET | `/api/v2/spotify/podcast/episodes?url=` | Podcast episodes with limit and offset |
| GET | `/api/v2/spotify/episode?url=` | Episode metadata |
| GET | `/api/v2/spotify/audiobook?url=` | Audiobook metadata |
| GET | `/api/v2/spotify/audiobook/chapters?url=` | Audiobook chapters with limit and offset |
| GET | `/api/v2/spotify/genres?q=` | Genre search |
| GET | `/api/v2/spotify/user?url=` | Public user profile and public playlists |

Every route accepts a Spotify URL, URI or 22-character id in `url` or `id`, with GET or POST. Search accepts `q` or `query`, `type=all|track|album|artist|playlist|podcast|episode|audiobook|genre|user`, `limit` and `offset`. Discography returns every album, single and EP by default; pass `limit` from 1 to 1000 to cap each section. The public `Appears On` section exposes up to 50 items; `appears_on_total` reports the upstream total.

Optional metadata that Spotify does not publish can be `null`, including preview URLs, follower counts, owner URLs, track counts and some release dates. Core identity fields (`id`, `uri`, `url`, and `name` or `title`) are always present. Pass `include_albums=true` on the playlist route to populate each track album object.

Playlist tracks are returned in full. `album_limit` only controls album enrichment, not the number of songs. Responses are normalized JSON. No Spotify account, login, OAuth token or API key is required from the buyer. This is an independent APIStead API and is not affiliated with or endorsed by Spotify.

V2 returns the artist's full discography and marks `complete: true` with `scope: "full_discography"` when all release sections were read. `preview_url` is a 30-second MP3 preview when Spotify publishes one. Playlist creation dates are not exposed in public playlist metadata, and per-track added dates are not exposed. They are not fabricated.

Search returns tracks, albums, artists, playlists, podcasts, episodes, audiobooks, genres and users. Dedicated search routes return only podcasts, episodes, audiobooks or users. Search tracks use `title`; album, artist and playlist search results use `name`. Track objects and discography releases include `release_date`; track objects also include album context. Artist records include `world_rank`. Artist playlist routes return playlists owned by or associated with the artist, related artists, discovered-on playlists and featuring playlists. Each artist section reports `total`, `returned` and `complete`; some sections may expose fewer items than `total`. Similar albums are based on a supplied track. Track, album and artist radio are not exposed because radio is created by the playback client, not by a stable public catalogue query. Genre labels and Spotify popularity scores are not exposed by the public source and are not returned. The other v2 shapes match v1. Agent records: [`/api/apis/spotify-api-v2`](/api/apis/spotify-api-v2) and [`/api/apis/spotify-api-v2/md`](/api/apis/spotify-api-v2/md). The v1 listing remains at [`/api/apis/spotify-api`](/api/apis/spotify-api).

Use v2 for a Spotify search API call, Spotify metadata without login, a catalogue search or a complete discography lookup.

Spotify example: `/api/apis/spotify-api/md` covers the five v1 routes, and `/api/apis/spotify-api-v2/md` covers the twenty-three v2 routes. Both include artist top tracks with album context, the `©`/`℗` copyright explanation and the playlist date limits. The v2 record also documents search, dedicated podcast/episode/audiobook/user search, artist playlists, related artists, discovered-on and featuring playlists, similar albums, podcasts, episodes, audiobooks, genres, users and complete artist discographies.

### Spotify track rights

Spotify publishes up to two copyright lines for an album. `©` is the copyright notice for the musical work: the composition and lyrics. `℗` is the phonogram notice for the sound recording: the master. They are separate rights, and one can appear without the other. The track endpoint returns each line in the order Spotify provides it, does not merge notices, and returns an empty `copyrights` array when Spotify publishes none.

## OpenAPI and LLM surfaces

| Endpoint | Purpose |
|---|---|
| `/api` | Markdown API reference (canonical) |
| `/mcp-docs` | MCP setup and tools |
| `/skill` | Agent playbook |
| `/favicon.ico` | APIStead icon |
| `/openapi.json` | OpenAPI 3.1 contract for discovery and paid x402 routes |
| `/llms.txt` | Short catalogue map |
| `/llms-full.txt` | Full catalogue (API-only mode omits bundles and articles) |
| `/.well-known/x402` | x402 discovery metadata |
| `/.well-known/x402.json` | x402 resource manifest for external catalogues |
| `/.well-known/mcp.json` | MCP manifest |
| `/.well-known/agent-card.json` | Agent card |
| `/.well-known/security.txt` | Security contact |

`/openapi.json` follows the x402scan OpenAPI discovery specification: https://x402scan.com/discovery/spec. Paid operations declare `x-payment-info` and a 402 response; free discovery operations declare `security: []`.

## Errors

Discovery endpoints return:

```json
{
  "error": "message",
  "status": 400
}
```

Paid provider routes return:

```json
{
  "error": {
    "code": "INVALID_JSON",
    "message": "Send a JSON body."
  }
}
```

Spotify v2 search returns `400 MISSING_QUERY` when `q` or `query` is absent and `400 INVALID_SEARCH_TYPE` for an unsupported `type`. Supported types are all, track, album, artist, playlist, podcast, episode, audiobook, genre and user. The dedicated podcast, episode, audiobook and user search routes use the same `q` or `query` input and return `400 MISSING_QUERY` when it is absent.

## Rate limits

Agent discovery is limited to 120 requests per minute per client. A `429` response includes `Retry-After`. Paid routes apply their own provider limits.

## Caching

Discovery responses use short shared caches. Do not treat a cached catalogue response as a price quote. The provider's 402 challenge is the authoritative price at the moment of payment.

## Related

- MCP guide: `/mcp-docs`
- Agent skill: `/skill`
- OpenAPI: `/openapi.json`
- Full catalogue: `/llms-full.txt`
