Skip to main content
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 under API Keys, then export it alongside the Peel base URL before running the examples:
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:
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 for the full command surface.

1. Build an API

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
  }'
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.
{
  "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 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:
export API_ID=2b6f7e6e-9d77-4f2a-9d8a-0b2d4f8d7c1a
CLI equivalent:
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:
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.
curl "$PEEL_API_BASE/v1/apis/$API_ID" \
  -H "X-API-Key: $PEEL_API_KEY"
{
  "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. CLI equivalent:
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.
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 }'
Runtime responses are normalized to the same top-level envelope:
{
  "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:
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:
{
  "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:
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:
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 covers peel build, peel apis, peel call, peel revise, and peel rebuild.
  • Change or refresh this API later: see API updates for revise and rebuild.
  • Use it from an agent: see MCP server to expose the same API as MCP tools.
  • Understand billing: see Credits and limits for per-operation costs and rate limits.

Troubleshooting

If a call returns an error, see Errors for the full catalog of HTTP status codes, runtime error_code values, and recovery steps.