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

# Quickstart

> Build an API from a URL, inspect the published contract, and call one endpoint.

This page covers the shortest Peel flow:

1. Build an API.
2. Inspect its contract.
3. Call one endpoint.

By the end, you will have a callable API and know which fields on its detail record matter. The important split is simple: build time can investigate the site and verify candidate endpoints; runtime only executes the stored contract that was published.

Peel is currently private preview only, so these examples assume you already have access. Get an API key from [peel.sh/settings](https://peel.sh/settings) under **API Keys**, then export it alongside the Peel base URL before running the examples:

```bash theme={null}
export PEEL_API_KEY=YOUR_API_KEY
export PEEL_API_BASE=https://api.peel.sh
```

If you prefer the first-party CLI, the same flow is:

```bash theme={null}
peel setup
peel build https://news.ycombinator.com --instructions "Get top stories with title, score, and author"
peel apis get <api-id>
peel call <api-id> <endpoint_name> -p limit=10
```

`peel setup` signs in with an existing account, offers MCP installation, and runs a quick health check. See [CLI](/getting-started/cli) for the full command surface.

## 1. Build an API

<CodeGroup>
  ```bash cURL 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
    }'
  ```

  ```javascript Node.js theme={null}
  const res = await fetch(`${process.env.PEEL_API_BASE}/v1/apis`, {
    method: "POST",
    headers: {
      "X-API-Key": process.env.PEEL_API_KEY,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      url: "https://news.ycombinator.com",
      instructions: "Get top stories with title, score, and author",
      wait_for_completion: true,
      timeout_ms: 120000,
    }),
  });
  const { api } = await res.json();
  ```

  ```python Python theme={null}
  import os, requests

  res = requests.post(
      f"{os.environ['PEEL_API_BASE']}/v1/apis",
      headers={"X-API-Key": os.environ["PEEL_API_KEY"]},
      json={
          "url": "https://news.ycombinator.com",
          "instructions": "Get top stories with title, score, and author",
          "wait_for_completion": True,
          "timeout_ms": 120000,
      },
  )
  api = res.json()["api"]
  ```
</CodeGroup>

The response wraps the created API in an `api` object alongside build metadata. The `links` entries are relative API paths; resolve them against `https://api.peel.sh` or your configured `PEEL_API_BASE`. IDs below are abbreviated for readability.

If Peel cannot publish a trustworthy endpoint, the API moves to `failed` with `failure_reason_code` and a structured `failure.artifact`. That is expected behavior: a failed build is better than a misleading API contract.

```json theme={null}
{
  "matched": false,
  "waited": true,
  "timed_out": false,
  "api": {
    "id": "2b6f7e6e-…",
    "name": "Hacker News Top Stories",
    "url": "https://news.ycombinator.com",
    "instructions": "Get top stories with title, score, and author",
    "status": "completed",
    "endpoint_count": 1,
    "created_at": "2026-04-07T10:12:03.000Z",
    "updated_at": "2026-04-07T10:12:48.000Z",
    "completed_at": "2026-04-07T10:12:48.000Z",
    "binding_id": "bind_5f2a3c",
    "version_id": "ver_9d77a4",
    "links": {
      "self":                    "/v1/apis/2b6f7e6e-…",
      "health":                  "/v1/apis/2b6f7e6e-…/health",
      "revise":                  "/v1/apis/2b6f7e6e-…/revise",
      "rebuild":                 "/v1/apis/2b6f7e6e-…/rebuild",
      "respond":                 "/v1/apis/2b6f7e6e-…/respond",
      "openapi_export":          "/v1/apis/2b6f7e6e-…/exports/openapi",
      "mcp_export":              "/v1/apis/2b6f7e6e-…/exports/mcp",
      "endpoint_call_template":  "/v1/apis/2b6f7e6e-…/endpoints/{endpoint_name}:call"
    }
  }
}
```

`status` moves through `queued`, `running`, optionally `needs_input`, then `completed` or `failed`. See [Core concepts](/getting-started/concepts#a-build-moves-through-a-lifecycle) for the full lifecycle.

`wait_for_completion: true` blocks the request until the build reaches a terminal state, up to `timeout_ms` (max `120000`). If the build is still running at the cap, the response returns `timed_out: true` with the API record in its current state. In that case, fall back to polling.

`matched: true` means Peel reused an existing compatible API for this URL instead of starting a new build. To opt out and force a fresh compile, pass `"force_new": true` in the request body. Matched builds are free. They reuse the same `api.id`, and no credits are charged.

Save the `api.id` so the next step can reference it:

```bash theme={null}
export API_ID=2b6f7e6e-9d77-4f2a-9d8a-0b2d4f8d7c1a
```

CLI equivalent:

```bash theme={null}
peel build https://news.ycombinator.com --instructions "Get top stories with title, score, and author"
```

The command waits for completion by default and prints the resulting `api.id`.

### Polling when you don't block

If you omit `wait_for_completion` or the blocking call times out, poll the detail endpoint until `status` is terminal:

```bash theme={null}
while true; do
  STATUS=$(curl -s "$PEEL_API_BASE/v1/apis/$API_ID" \
    -H "X-API-Key: $PEEL_API_KEY" | jq -r .api.status)
  case "$STATUS" in
    completed|failed|needs_input) echo "$STATUS"; break ;;
  esac
  sleep 2
done
```

A 2-second interval works well: it stays well under the 120/min execution rate limit and is frequent enough for interactive use. Longer builds are fine to poll at 5 seconds. Avoid intervals under 1 second because they use rate-limit budget without making the build finish sooner.

## 2. Inspect the contract

Before you call anything, fetch the API detail record. It contains the endpoint names and schemas you should use.

<CodeGroup>
  ```bash cURL theme={null}
  curl "$PEEL_API_BASE/v1/apis/$API_ID" \
    -H "X-API-Key: $PEEL_API_KEY"
  ```

  ```javascript Node.js theme={null}
  const res = await fetch(`${process.env.PEEL_API_BASE}/v1/apis/${apiId}`, {
    headers: { "X-API-Key": process.env.PEEL_API_KEY },
  });
  const { api } = await res.json();
  ```

  ```python Python theme={null}
  res = requests.get(
      f"{os.environ['PEEL_API_BASE']}/v1/apis/{api_id}",
      headers={"X-API-Key": os.environ["PEEL_API_KEY"]},
  )
  api = res.json()["api"]
  ```
</CodeGroup>

```json theme={null}
{
  "api": {
    "id": "2b6f7e6e-…",
    "status": "completed",
    "url": "https://news.ycombinator.com",
    "source_url": "https://news.ycombinator.com",
    "auth_mode": "public",
    "requires_session": false,
    "binding_id": "bind_5f2a3c",
    "version_id": "ver_9d77a4",
    "endpoints": [
      {
        "endpoint_name": "get_top_stories",
        "method": "POST",
        "description": "Get top stories with title, score, and author",
        "auth_requirement": "public",
        "endpoint_type": "data",
        "runtime_policy": null,
        "runtime_verification": {
          "status": "verified",
          "field_coverage": 1,
          "item_count": 30,
          "observed_fields": ["author", "score", "title"],
          "missing_fields": [],
          "low_quality_fields": [],
          "reason": "verified"
        },
        "input_schema": {
          "limit": { "type": "number" }
        },
        "output_schema": {}
      }
    ]
  }
}
```

Three fields determine your next call:

* `status` must be `completed`.
* `endpoints[].endpoint_name` is the exact name you pass to the runtime route.
* `endpoints[].input_schema` shows the payload shape Peel expects.
* `endpoints[].runtime_policy` and `endpoints[].runtime_verification` show the deterministic runtime constraints and build-time verification evidence Peel has for that endpoint.

Two related URL fields also appear on the detail record: `url` is the URL you passed to `POST /v1/apis`, and `source_url` is the URL Peel's compiled transport actually fetches. They usually match, but they can differ if the build normalized or followed the input URL. Use `source_url` when you need to know where runtime goes.

For the full set of published fields, see [How it works](/getting-started/how-it-works#what-gets-published).

CLI equivalent:

```bash theme={null}
peel apis get "$API_ID"
```

## 3. Call an endpoint

Use the published `endpoint_name` and send the request body in the shape described by `input_schema`.

<CodeGroup>
  ```bash cURL 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 }'
  ```

  ```javascript Node.js theme={null}
  const res = await fetch(
    `${process.env.PEEL_API_BASE}/v1/apis/${apiId}/endpoints/get_top_stories:call`,
    {
      method: "POST",
      headers: {
        "X-API-Key": process.env.PEEL_API_KEY,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({ limit: 10 }),
    },
  );
  const result = await res.json();
  ```

  ```python Python theme={null}
  res = requests.post(
      f"{os.environ['PEEL_API_BASE']}/v1/apis/{api_id}/endpoints/get_top_stories:call",
      headers={"X-API-Key": os.environ["PEEL_API_KEY"]},
      json={"limit": 10},
  )
  result = res.json()
  ```
</CodeGroup>

Runtime responses are normalized to the same top-level envelope:

```json theme={null}
{
  "status": "success",
  "status_code": 200,
  "data": [
    { "title": "Example story", "score": 312, "author": "pg" }
  ],
  "error": null,
  "error_code": null,
  "execution_time": 842
}
```

If you call an endpoint name that does not exist, Peel returns `404` with an `available_endpoints` array in the body. Copy a name from there.

CLI equivalent:

```bash theme={null}
peel call "$API_ID" get_top_stories -p limit=10
```

## Handle a paused build

If `status` is `needs_input`, Peel is waiting for more information. The detail response includes `user_input_prompt` describing what it needs:

```json theme={null}
{
  "api": {
    "id": "2b6f7e6e-9d77-4f2a-9d8a-0b2d4f8d7c1a",
    "status": "needs_input",
    "user_input_prompt": {
      "fields": [
        { "name": "search_term", "description": "A product keyword to build search around" }
      ]
    }
  }
}
```

Send the requested payload through `/respond` and wait for the build to resume:

```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": { "search_term": "headphones" },
    "wait_for_completion": true,
    "timeout_ms": 120000
  }'
```

`/respond` returns `409` if the API is not currently in `needs_input`. Re-fetch the detail record after the call to confirm the new status before making runtime calls.

`needs_input` is a terminal state for the build worker. Peel does not automatically time it out. A paused API stays in `needs_input` until you call `/respond`, `/revise`, or `/rebuild`.

CLI equivalent:

```bash theme={null}
peel respond "$API_ID" --body '{"search_term":"headphones"}'
```

## Where to go next

* **List what you have built:** `GET /v1/apis?limit=20` returns your APIs and supports `limit`, `cursor`, and `status`.
* **Use the same flow from the terminal:** [CLI](/getting-started/cli) covers `peel build`, `peel apis`, `peel call`, `peel revise`, and `peel rebuild`.
* **Change or refresh this API later:** see [API updates](/features/api-updates) for `revise` and `rebuild`.
* **Use it from an agent:** see [MCP server](/features/mcp-server) to expose the same API as MCP tools.
* **Understand billing:** see [Credits and limits](/reference/credits) for per-operation costs and rate limits.

## Troubleshooting

If a call returns an error, see [Errors](/reference/errors) for the full catalog of HTTP status codes, runtime `error_code` values, and recovery steps.
