# Inspect API Reference

## Introduction

The Inspect API creates Inspect [Source URLs](/docs/inspect/sources) and reads the requests they capture. A Source URL is a free, disposable webhook test URL, so the API lets a test or an AI agent create one, send to it, and read what arrived, with no account.

The API calls a Source URL a **Source**, so that's the word you'll see in the reference below and in its JSON.

> The Inspect API is in early beta (`0.2.0`). The contract may change.

Everything lives on the `inspect.hookdeck.com` host. Creation is proxied to Hookdeck server-side, so you only ever handle `inspect.hookdeck.com` URLs.

**Base URL:** `https://inspect.hookdeck.com`

The create response names both of a Source URL's addresses:

* **`ingest_url`** (`inspect.hookdeck.com/e/<id>`) is the Source URL itself, which captures requests.
* **`inspect_url`** (`inspect.hookdeck.com/<id>`) is its page, where you read them.

The create response also has a **`requests_url`**. A `GET` to it waits up to 25 seconds for the Source URL's next request and returns it as JSON, so there's no need to poll. If nothing arrives in time, the reply has a URL to resume waiting from the same point. Without a wait, `/<id>/requests` reads a Source URL's requests from the start, oldest first, a page at a time.

Each captured request comes back with its method, path, query string, headers, and body. Every request also has a `body_url`, which downloads its exact bytes.

The [Hookdeck REST API](/docs/api) at `api.hookdeck.com` is a separate API with its own authentication, resources, and documentation. Nothing here applies to it.

The OpenAPI description is published at [inspect.hookdeck.com/openapi.json](https://inspect.hookdeck.com/openapi.json), and a guide for AI agents at [inspect.hookdeck.com/llms.txt](https://inspect.hookdeck.com/llms.txt).

## Authentication

There is none. Every endpoint is public, and creating a Source URL requires no account.

Access is granted by knowing a Source URL's ID. Anyone with the ID can send requests to it and read the requests it captured, and opening it in a browser also lets them manage it. That's what makes a test URL shareable with a teammate.

So treat a Source URL like a key, and keep it out of anywhere public. Don't send anything through an Inspect Source URL you wouldn't want anyone holding the link to read. For authenticated access control, use an [Event Gateway](/docs/hookdeck-basics) project, which needs a Hookdeck account.

### Keep the `cli_key` secret

The create response includes a `cli_key`. It connects the [Hookdeck CLI](/docs/cli) to your Source URLs, so `hookdeck listen` can forward their requests to your machine. Anyone holding it can listen to them and forward their requests, so don't share it, log it, or paste it into a prompt.

It isn't a credential for this API, which needs none.

```bash
curl -X POST "https://inspect.hookdeck.com/"

```

## Rate limits

| Endpoint | Limit |
| --- | --- |
| `POST /` | 60 Source URLs created per minute |
| `GET /{id}`, `GET /{id}/requests`, `GET /{id}/requests/{request_id}/body` | 60 calls per minute, per client IP and Source URL |

Exceeding a limit returns `429 Too Many Requests` with a `Retry-After` header giving the seconds to wait.

Creation is limited per client IP by default. If you [replay the session cookie](#sessions), it tracks that session instead of the IP it's called from.

A call that waits for a request counts once, however long it waits. A waiting caller makes two or three calls a minute, so the read limit only stops something calling in a loop.

The `X-RateLimit-*` headers on a create reply come from Hookdeck's general API limit, which is higher. They don't count down to the creation limit above.

Ingestion to `/e/{id}` is not covered by these limits. It's subject to the platform [limits](/docs/limits).

```text
HTTP/1.1 429 Too Many Requests
Retry-After: 60

```

## Errors

Errors use conventional HTTP status codes. Most error replies have an `error` code and a `note` saying what went wrong and what to do next.

| Status | Meaning |
| --- | --- |
| `400` | An invalid `timeout` (0 to 90), `limit` (1 to 50), or `next` on `/{id}/requests`. The reply's `read_url` reads the Source URL again from the start. |
| `403` | A `POST /` sent from a web page on another site. Create Source URLs from a server, a script, or an agent. |
| `404` | No Source URL with that ID, or it was deleted. On a body download, it can also mean the Source URL has no request with that ID. |
| `405` | A method the URL doesn't take, such as a `POST` to an inspect URL. Requests to capture go to the ingest URL, which the reply includes. |
| `422` | The `type` isn't a supported Source Type, or the `name` uses characters other than letters, digits, `-` and `_`. The `data` array says which. |
| `429` | A [rate limit](#rate-limits) was exceeded. Retry after the interval in `Retry-After`. |
| `502` | Hookdeck couldn't be reached. On `POST /`, a Source URL may have been created anyway, so check before creating another. On a read, retry the same URL. |
| `503` | Too many reads right now. Retry the same URL after the interval in `Retry-After`. |

Ingestion is the exception. A request to `/e/{id}` is answered with a JSON receipt describing the captured request, not the state of this API. A method the Source URL's type doesn't accept is answered `405`, and the request is still captured, marked as rejected.

```json
{
  "code": "UNPROCESSABLE_ENTITY",
  "status": 422,
  "data": ["type must be one of [WEBHOOK, SHOPIFY, STRIPE, ...]"]
}

```

```json
{
  "error": "not_found",
  "note": "There is no Inspect Source at this URL."
}

```

## Sessions

The first create without a session returns a session cookie on `.hookdeck.com`, with `SameSite=Strict` and a fixed 90-day lifetime that doesn't slide with use.

Replaying that cookie on later creates keeps those Source URLs together, so they show up in the same list in the app. It's optional, and most callers won't need it. Save it on the first create with `-c` and send it back with `-b`.

Without it, each create stands alone. The Source URLs still work identically; they just aren't listed together.

Reading a Source URL needs no cookie, and creates no session.

### Creating from a browser

Opening [inspect.hookdeck.com](https://inspect.hookdeck.com) in a browser creates a Source URL, or opens the one that browser already has. The create happens client-side, so it only works in a browser that runs JavaScript. A non-browser client gets the page and creates nothing.

Prefer `POST /` for anything programmatic.

```bash
# Save the cookie on the first create
curl -sS -c cookies.txt -X POST "https://inspect.hookdeck.com/"

# Replay it, so both Source URLs are listed together
curl -sS -b cookies.txt -X POST "https://inspect.hookdeck.com/"

```

## API Resources

### Sources

* [Sources](/docs/inspect/api/sources.md)
  * [Sources](/docs/inspect/api/sources.md#sources)

### Ingestion

* [Ingestion](/docs/inspect/api/ingestion.md)
  * [Ingestion](/docs/inspect/api/ingestion.md#ingestion)

### Inspection

* [Inspection](/docs/inspect/api/inspection.md)
  * [Inspection](/docs/inspect/api/inspection.md#inspection)
