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

# Quickstart

> Detect the providers behind a business URL and pull structured data in under 5 minutes.

## Overview

Vine turns a business URL into structured data through two endpoints:

1. **Detect** (`POST /api/v1/detect`) — **synchronous**. Send a URL, get back the providers Vine detected and the proposed connections behind them in \~25 seconds.
2. **Extract** (`POST /api/v1/extract`) — **async**. Send a URL, Vine detects providers and pulls data from each, and you poll until the job reaches a terminal state.

Every request is authenticated with a Bearer API key. `detect` returns its result directly; `extract` returns a job you poll with `GET /api/v1/extractions/{id}`.

<Steps>
  <Step title="Get an API key">
    Mint a key with the key-management endpoint. The `plaintext` is returned **once** — store it immediately; it is never recoverable.

    ```bash cURL theme={"theme":{"light":"github-light","dark":"vitesse-dark"}}
    curl -X POST https://vine.getcourtyard.ai/api/v1/keys \
      -H "Content-Type: application/json" \
      -d '{ "name": "my-first-key", "env": "test" }'
    ```

    ```json Response theme={"theme":{"light":"github-light","dark":"vitesse-dark"}}
    {
      "key": {
        "id": "clz…",
        "name": "my-first-key",
        "keyPrefix": "vine_test_Ab",
        "plaintext": "vine_test_xxxxxxxxxxxxxxxxxxxxxxxxxxxx",
        "scopes": ["detect", "extract", "extractions:read"],
        "rateTier": "internal",
        "createdAt": "2026-06-25T18:00:00.000Z"
      }
    }
    ```

    Keys look like `vine_test_…` (sandbox, the default) or `vine_live_…` (production). Attach yours as a Bearer token on every request:

    ```http theme={"theme":{"light":"github-light","dark":"vitesse-dark"}}
    Authorization: Bearer vine_test_xxxxxxxxxxxxxxxx
    ```

    <Tip>
      Keep keys server-side. The default scopes — `detect`, `extract`, `extractions:read` — cover the whole flow below.
    </Tip>
  </Step>

  <Step title="Detect providers (synchronous)">
    Send the business URL. `detect` blocks until the classifier finishes (\~25s) and returns the result directly — no polling.

    <CodeGroup>
      ```bash cURL theme={"theme":{"light":"github-light","dark":"vitesse-dark"}}
      curl -X POST https://vine.getcourtyard.ai/api/v1/detect \
        -H "Authorization: Bearer $VINE_API_KEY" \
        -H "Content-Type: application/json" \
        -d '{ "url": "https://www.mindbodyonline.com/explore/locations/thesharpbarber" }'
      ```

      ```typescript Node.js theme={"theme":{"light":"github-light","dark":"vitesse-dark"}}
      const res = await fetch('https://vine.getcourtyard.ai/api/v1/detect', {
        method: 'POST',
        headers: {
          'Authorization': `Bearer ${process.env.VINE_API_KEY}`,
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({
          url: 'https://www.mindbodyonline.com/explore/locations/thesharpbarber',
        }),
      });

      const { providers, connections } = await res.json();
      ```
    </CodeGroup>

    A `200 OK` returns the detected providers and the connections you can feed into an extraction:

    ```json theme={"theme":{"light":"github-light","dark":"vitesse-dark"}}
    {
      "schemaVersion": "extraction.v1",
      "url": "https://www.mindbodyonline.com/explore/locations/thesharpbarber",
      "providers": ["mindbody"],
      "connections": [
        {
          "provider": "mindbody",
          "actionType": "book",
          "actionUrl": "https://widgets.mindbodyonline.com/...",
          "providerParams": { "siteId": "-99" }
        }
      ],
      "durationMs": 24650
    }
    ```
  </Step>

  <Step title="Extract provider data (async)">
    To pull live data (schedules, menus, practitioners, availability), submit an extraction. An `Idempotency-Key` is optional — supply one to make a submit safely retryable; omit it and each call creates a new job.

    <CodeGroup>
      ```bash cURL theme={"theme":{"light":"github-light","dark":"vitesse-dark"}}
      curl -X POST https://vine.getcourtyard.ai/api/v1/extract \
        -H "Authorization: Bearer $VINE_API_KEY" \
        -H "Content-Type: application/json" \
        -H "Idempotency-Key: my-key-001" \
        -d '{ "url": "https://www.mindbodyonline.com/explore/locations/thesharpbarber" }'
      ```

      ```typescript Node.js theme={"theme":{"light":"github-light","dark":"vitesse-dark"}}
      const res = await fetch('https://vine.getcourtyard.ai/api/v1/extract', {
        method: 'POST',
        headers: {
          'Authorization': `Bearer ${process.env.VINE_API_KEY}`,
          'Content-Type': 'application/json',
          'Idempotency-Key': 'my-key-001',
        },
        body: JSON.stringify({
          url: 'https://www.mindbodyonline.com/explore/locations/thesharpbarber',
        }),
      });

      const { id, pollUrl, retryAfterSeconds } = await res.json();
      // id                → "ext_01JX…"
      // pollUrl           → full URL you GET to check status
      // retryAfterSeconds → suggested initial delay before the first poll
      ```
    </CodeGroup>

    The API responds with `202 Accepted` and a job envelope:

    ```json theme={"theme":{"light":"github-light","dark":"vitesse-dark"}}
    {
      "schemaVersion": "extraction.v1",
      "id": "ext_01JX4MQTCPB9NZ7KXHVF3A2R8",
      "kind": "extract",
      "status": "queued",
      "pollUrl": "https://vine.getcourtyard.ai/api/v1/extractions/ext_01JX4MQTCPB9NZ7KXHVF3A2R8",
      "createdAt": "2026-06-25T18:00:00.000Z",
      "retryAfterSeconds": 4
    }
    ```
  </Step>

  <Step title="Poll until terminal">
    Poll `pollUrl` (or `GET /api/v1/extractions/{id}`) until `status` is no longer `queued` or `running`. An extraction is readable only by the key that created it.

    <CodeGroup>
      ```bash cURL theme={"theme":{"light":"github-light","dark":"vitesse-dark"}}
      curl https://vine.getcourtyard.ai/api/v1/extractions/ext_01JX4MQTCPB9NZ7KXHVF3A2R8 \
        -H "Authorization: Bearer $VINE_API_KEY"
      ```

      ```typescript Node.js theme={"theme":{"light":"github-light","dark":"vitesse-dark"}}
      async function poll(id: string, intervalMs = 4000, maxAttempts = 60) {
        for (let i = 0; i < maxAttempts; i++) {
          await new Promise(r => setTimeout(r, intervalMs));

          const res = await fetch(
            `https://vine.getcourtyard.ai/api/v1/extractions/${id}`,
            { headers: { Authorization: `Bearer ${process.env.VINE_API_KEY}` } },
          );
          const job = await res.json();

          if (job.status === 'succeeded') return job;
          if (job.status === 'failed' || job.status === 'timed_out') {
            throw new Error(job.failure?.message ?? job.status);
          }
        }
        throw new Error('Polling timed out');
      }

      const job = await poll('ext_01JX4MQTCPB9NZ7KXHVF3A2R8');
      ```
    </CodeGroup>

    A succeeded job carries the extracted data in `result`:

    ```json theme={"theme":{"light":"github-light","dark":"vitesse-dark"}}
    {
      "schemaVersion": "extraction.v1",
      "id": "ext_01JX4MQTCPB9NZ7KXHVF3A2R8",
      "kind": "extract",
      "status": "succeeded",
      "attempt": 1,
      "createdAt": "2026-06-25T18:00:00.000Z",
      "startedAt": "2026-06-25T18:00:01.000Z",
      "finishedAt": "2026-06-25T18:00:39.000Z",
      "durationMs": 38120,
      "result": {
        "providers": ["mindbody"],
        "offerings": [
          { "name": "Classic Cut", "price": "$35", "durationMins": 30 },
          { "name": "Hot Towel Shave", "price": "$28", "durationMins": 30 }
        ]
      }
    }
    ```
  </Step>
</Steps>

## Handling errors

Every error follows the same envelope:

```json theme={"theme":{"light":"github-light","dark":"vitesse-dark"}}
{
  "error": {
    "code": "rate_limited",
    "message": "Too many concurrent extractions. Retry shortly."
  }
}
```

| HTTP  | Meaning                                                          | Retryable                |
| ----- | ---------------------------------------------------------------- | ------------------------ |
| `401` | Missing, invalid, or revoked key                                 | No — check your key      |
| `409` | Idempotency conflict — same `Idempotency-Key`, different payload | No — use a new key       |
| `422` | Validation failed — fix the request body                         | No                       |
| `429` | Rate limited or at capacity                                      | Yes — back off and retry |
| `404` | Extraction not found, or not owned by this key (poll)            | No                       |

When an extraction ends `failed`, check `failure.retryable`:

```typescript theme={"theme":{"light":"github-light","dark":"vitesse-dark"}}
if (job.status === 'failed') {
  if (job.failure?.retryable) {
    // Safe to resubmit (with a fresh Idempotency-Key)
  } else {
    // Permanent error — log and skip
  }
}
```

## Next steps

<CardGroup cols={2}>
  <Card title="Detect" icon="radar" href="/docs/api-reference/detect">
    Synchronous provider detection — request schema and response fields.
  </Card>

  <Card title="Extract" icon="database" href="/docs/api-reference/extract">
    Async extraction — submit a URL, get a job to poll.
  </Card>

  <Card title="Poll an extraction" icon="rotate" href="/docs/api-reference/get-extraction">
    Polling semantics and terminal states.
  </Card>

  <Card title="API keys" icon="key" href="/docs/api-reference/keys-create">
    Create, list, and revoke keys.
  </Card>
</CardGroup>
