> ## Documentation Index
> Fetch the complete documentation index at: https://docs.peel.sh/llms.txt
> Use this file to discover all available pages before exploring further.

# How It Works

> How Peel moves from a build request to a published runtime contract.

Peel has two phases:

* **Build time** creates or updates an API under `/v1/apis`.
* **Runtime** executes one of that API's published endpoints.

Build time is allowed to be investigative. Peel can inspect pages, capture network and selector evidence, evaluate official public surfaces, draft endpoint candidates, run them, repair them, and reject weak outputs. Runtime does none of that. It only executes the contract that passed publication gates.

## Build time

You start with `POST /v1/apis`:

```bash theme={null}
curl -X POST "$PEEL_API_BASE/v1/apis" \
  -H "X-API-Key: $PEEL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://news.ycombinator.com",
    "instructions": "Get top stories with title, score, and author",
    "wait_for_completion": true,
    "timeout_ms": 120000
  }'
```

Omit `wait_for_completion` if you prefer a non-blocking create and want to poll. See the [polling pattern in the quickstart](/getting-started/quickstart#polling-when-you-dont-block).

That request can do one of two things:

* Return `matched: true` when Peel reuses an existing compatible API.
* Return `matched: false` when Peel starts a new build.

The API record then moves through the public lifecycle states (`queued` → `running` → optionally `needs_input` → `completed` or `failed`). See [Core concepts](/getting-started/concepts#a-build-moves-through-a-lifecycle) for what each state means.

When a build reaches `completed`, Peel publishes the current contract at `GET /v1/apis/{api_id}`. REST `:call`, MCP `call_endpoint`, CLI `peel call`, and the OpenAPI and MCP exports all project that same stored contract.

Failed builds are part of the contract too. A failed API should explain why no endpoint was published through `failure_reason_code`, `failure.non_publishable_reason`, and `failure.artifact` instead of returning a pretend API that happened to work once.

## What gets published

The detail record is the runtime contract. It includes:

* `id`: the stable handle you use everywhere.
* `status`: current lifecycle state.
* `source_url`: the URL the API was built from.
* `failure_reason_code` and `failure`: present when `status` is `failed`. `failure.non_publishable_reason` gives the stable reason no API was published when one is available; `failure.artifact` includes the failure class, retryability, billing disposition, suggested user action, and a `support_debug_id`.
* `auth_mode`: `public`, `mixed_public_and_managed_session`, or `managed_session_only`.
* `requires_session`: `true` if any endpoint needs a target-site session.
* `endpoints`: the array of callable operations.
* `links`: relative API paths for `self`, `health`, `revise`, `rebuild`, `respond`, `openapi_export`, `mcp_export`, and `endpoint_call_template`. Resolve them against `https://api.peel.sh` or your configured `PEEL_API_BASE`.

Each entry in `endpoints` includes the fields that matter at runtime:

* `endpoint_name`
* `method`: the compiled target transport method. Hosted callers still execute endpoints with `POST /v1/apis/{api_id}/endpoints/{endpoint_name}:call`.
* `description`
* `auth_requirement`: `public` or `session_required`.
* `input_schema`
* `output_schema`
* `endpoint_type`: usually `data`; `login` appears for compiled target-site session flows. `guided_action` is reserved for deterministic action flows and remains gated until Peel has concrete request-sequence evidence and policy approval.
* `runtime_policy`: compact policy metadata for compiled custom artifacts, including network, filesystem, environment, import, and timeout constraints when present.
* `runtime_verification`: compact build-time verification metadata, including status, field coverage, item count, observed fields, missing fields, and reason when Peel verified the endpoint before publishing it.
* optional `credit_cost`: see [Credits and limits](/reference/credits) for tier defaults.

Runtime callers should rely on the published contract instead of guessing names or payloads.

### Endpoint kinds

Peel classifies each published endpoint by the work it does:

* **`data` + `auth_requirement: "public"`**: a deterministic read against the public surface. Usually compiled from captured network traffic. It can also fall back to HTML when the site exposes no stable JSON.
* **`data` + `auth_requirement: "session_required"`**: the same shape, but behind a login. Call the API's `login` endpoint first, then pass the returned `session_id` and `encryption_key` into later calls.
* **`login`**: initiates a session. Returns `session_id` and `encryption_key` in the runtime response.
* **`guided_action`**: a deterministic multi-step flow such as a form submission or mutation. Peel keeps this gated unless the build produced replayable request-sequence evidence, narrow classification, and policy checks. For most public docs workflows, expect read-only `data` endpoints.

If a `data` endpoint returns changing field names or breaks when the site changes markup, it was likely an HTML or compiled-artifact path that needs fresh evidence. Call `POST /v1/apis/{api_id}/rebuild` to regenerate from the current site. See [API updates](/features/api-updates).

## Runtime

Runtime calls always go through a published endpoint route:

```bash theme={null}
curl -X POST "$PEEL_API_BASE/v1/apis/$API_ID/endpoints/get_top_stories:call" \
  -H "X-API-Key: $PEEL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "limit": 10
  }'
```

The call response is normalized to a top-level envelope with these fields:

* `status`
* `status_code`
* `data`
* `error`
* `error_code`
* `execution_time`

Some calls also include session-related fields such as `initiates_session`, `session_id`, or `encryption_key`. Runtime `error_code` values and build-time `failure` records are documented in [Errors](/reference/errors).

Runtime failures use the same envelope and may return non-2xx HTTP statuses that mirror `status_code` in the body. Setup errors such as invalid auth, an unknown API, an unknown endpoint, or an API that is still building are host errors and may return a smaller `{ "error": "..." }` shape instead.

## When a build pauses

`needs_input` means the build is waiting for more input. In that state:

* `GET /v1/apis/{api_id}` includes `user_input_prompt`.
* `POST /v1/apis/{api_id}/respond` sends the response payload back to the same API.

Example:

```bash theme={null}
curl -X POST "$PEEL_API_BASE/v1/apis/$API_ID/respond" \
  -H "X-API-Key: $PEEL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "response": {
      "your_field": "your_value"
    },
    "wait_for_completion": true,
    "timeout_ms": 120000
  }'
```

`respond` only works while the API is in `needs_input`. Otherwise the server returns `409`.

## Build completion: no webhooks

Peel does not currently fire webhooks when a build reaches a terminal state. To wait for a build, use one of:

* **`wait_for_completion: true`** on the create, revise, or respond calls: blocks for up to 120 seconds.
* **Polling `GET /v1/apis/{api_id}`**: see [the polling pattern in the quickstart](/getting-started/quickstart#polling-when-you-dont-block).

If webhook-based build notifications are important to your workflow, email [kaan@peel.sh](mailto:kaan@peel.sh). The surface is not available yet.

## Working with an existing API

Every API keeps the same public handle (`api.id`) across its lifetime. Once it exists, these are the operations you can perform against it:

| Operation                     | Route                                                   | Use it to                                                                                                                 |
| ----------------------------- | ------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- |
| **Execute** an endpoint       | `POST /v1/apis/{api_id}/endpoints/{endpoint_name}:call` | Run a compiled endpoint. See the [quickstart](/getting-started/quickstart#3-call-an-endpoint).                            |
| **Inspect** the contract      | `GET /v1/apis/{api_id}`                                 | Fetch the current endpoint list, schemas, auth mode, and lifecycle status.                                                |
| **Revise** the API            | `POST /v1/apis/{api_id}/revise`                         | Keep the same source URL and change the build instructions. Use when the site is right but the intent needs to change.    |
| **Rebuild** the API           | `POST /v1/apis/{api_id}/rebuild`                        | Keep the same instructions and refresh the compiled evidence. Use when the site changed.                                  |
| **Respond** to a paused build | `POST /v1/apis/{api_id}/respond`                        | Unblock a build stuck in `needs_input`.                                                                                   |
| **Check** health              | `GET /v1/apis/{api_id}/health`                          | Build-stage diagnostics until a binding is published, then per-binding counters, quality score, and `recommended_action`. |
| **Export** as OpenAPI         | `GET /v1/apis/{api_id}/exports/openapi`                 | Download an OpenAPI 3.1 spec of every published endpoint. See [Exporting an API](/reference/exports).                     |
| **Export** as MCP             | `GET /v1/apis/{api_id}/exports/mcp`                     | Download an MCP tool manifest for self-hosted agent runtimes. See [Exporting an API](/reference/exports).                 |

The full decision tree for `revise` vs `rebuild` vs `respond`, including when to inspect health first, is in [API updates](/features/api-updates).
