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

# Quickstart

> Run your first calling campaign from code: list agents, create a campaign, add contacts, start it and read the results.

This walkthrough uses an existing agent and phone number from your workspace. You need an API key with
`agents.view`, `phone_numbers.view`, `campaigns.edit` and `calls.view` (see [Authentication](/authentication)).

<Warning>Starting a campaign places real, billed calls. Use your own test numbers while you experiment.</Warning>

Set your key once:

```bash theme={null}
export PINEYARD_API_KEY="pk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
```

<Steps>
  <Step title="Find an agent and a phone number">
    <CodeGroup>
      ```bash curl theme={null}
      curl https://api.pineyard.ai/agents \
        -H "Authorization: Bearer $PINEYARD_API_KEY"

      curl https://api.pineyard.ai/phone-numbers \
        -H "Authorization: Bearer $PINEYARD_API_KEY"
      ```

      ```javascript Node theme={null}
      const API = 'https://api.pineyard.ai'
      const headers = {
        Authorization: `Bearer ${process.env.PINEYARD_API_KEY}`,
        'Content-Type': 'application/json',
      }

      const agents = await (await fetch(`${API}/agents`, { headers })).json()
      const { items: numbers } = await (await fetch(`${API}/phone-numbers`, { headers })).json()
      const agentId = agents[0].id
      const phoneNumberId = numbers[0].id
      ```

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

      API = "https://api.pineyard.ai"
      headers = {"Authorization": f"Bearer {os.environ['PINEYARD_API_KEY']}"}

      agents = requests.get(f"{API}/agents", headers=headers).json()
      numbers = requests.get(f"{API}/phone-numbers", headers=headers).json()["items"]
      agent_id = agents[0]["id"]
      phone_number_id = numbers[0]["id"]
      ```
    </CodeGroup>

    Note the agent's `variables`: these are the per-contact fields its script uses (for example `first_name`).
    Agent responses never include the prompt text.
  </Step>

  <Step title="Create a campaign">
    New campaigns start as drafts.

    <CodeGroup>
      ```bash curl theme={null}
      curl -X POST https://api.pineyard.ai/campaigns \
        -H "Authorization: Bearer $PINEYARD_API_KEY" \
        -H "Content-Type: application/json" \
        -d '{
          "name": "October outreach",
          "agentId": "00000000-0000-4000-8000-000000000001",
          "timezone": "America/New_York",
          "dailyWindowStart": "10:00",
          "dailyWindowEnd": "18:00"
        }'
      ```

      ```javascript Node theme={null}
      const created = await (await fetch(`${API}/campaigns`, {
        method: 'POST',
        headers,
        body: JSON.stringify({
          name: 'October outreach',
          agentId,
          timezone: 'America/New_York',
          dailyWindowStart: '10:00',
          dailyWindowEnd: '18:00',
        }),
      })).json()
      const campaignId = created.campaignId
      ```

      ```python Python theme={null}
      created = requests.post(f"{API}/campaigns", headers=headers, json={
          "name": "October outreach",
          "agentId": agent_id,
          "timezone": "America/New_York",
          "dailyWindowStart": "10:00",
          "dailyWindowEnd": "18:00",
      }).json()
      campaign_id = created["campaignId"]
      ```
    </CodeGroup>

    ```json Response (201) theme={null}
    { "campaignId": "00000000-0000-4000-8000-0000000000c1", "contactsAdded": 0, "contactsSkipped": 0, "skippedReasons": [] }
    ```
  </Step>

  <Step title="Attach a phone number">
    Calls are placed from the numbers attached to the campaign.

    <CodeGroup>
      ```bash curl theme={null}
      curl -X POST https://api.pineyard.ai/campaigns/00000000-0000-4000-8000-0000000000c1/phone-numbers \
        -H "Authorization: Bearer $PINEYARD_API_KEY" \
        -H "Content-Type: application/json" \
        -d '{"phoneNumberIds": ["00000000-0000-4000-8000-000000000002"]}'
      ```

      ```javascript Node theme={null}
      await fetch(`${API}/campaigns/${campaignId}/phone-numbers`, {
        method: 'POST', headers, body: JSON.stringify({ phoneNumberIds: [phoneNumberId] }),
      }) // 204 No Content
      ```

      ```python Python theme={null}
      requests.post(f"{API}/campaigns/{campaign_id}/phone-numbers", headers=headers,
                    json={"phoneNumberIds": [phone_number_id]})  # 204 No Content
      ```
    </CodeGroup>
  </Step>

  <Step title="Add contacts">
    Phone numbers must be in E.164 format. Put the agent's variables in `metadata`. Up to 10,000 contacts per request.

    <CodeGroup>
      ```bash curl theme={null}
      curl -X POST https://api.pineyard.ai/campaigns/00000000-0000-4000-8000-0000000000c1/contacts \
        -H "Authorization: Bearer $PINEYARD_API_KEY" \
        -H "Content-Type: application/json" \
        -d '{
          "contacts": [
            { "phone": "+15555550100", "name": "Jordan Example", "metadata": { "first_name": "Jordan" } },
            { "phone": "+15555550101", "name": "Sam Example", "metadata": { "first_name": "Sam" } }
          ]
        }'
      ```

      ```javascript Node theme={null}
      const added = await (await fetch(`${API}/campaigns/${campaignId}/contacts`, {
        method: 'POST',
        headers,
        body: JSON.stringify({
          contacts: [
            { phone: '+15555550100', name: 'Jordan Example', metadata: { first_name: 'Jordan' } },
            { phone: '+15555550101', name: 'Sam Example', metadata: { first_name: 'Sam' } },
          ],
        }),
      })).json()
      ```

      ```python Python theme={null}
      added = requests.post(f"{API}/campaigns/{campaign_id}/contacts", headers=headers, json={
          "contacts": [
              {"phone": "+15555550100", "name": "Jordan Example", "metadata": {"first_name": "Jordan"}},
              {"phone": "+15555550101", "name": "Sam Example", "metadata": {"first_name": "Sam"}},
          ]
      }).json()
      ```
    </CodeGroup>

    ```json Response (201) theme={null}
    { "added": 2, "total": 2 }
    ```
  </Step>

  <Step title="Start the campaign">
    <CodeGroup>
      ```bash curl theme={null}
      curl -X POST https://api.pineyard.ai/campaigns/00000000-0000-4000-8000-0000000000c1/start \
        -H "Authorization: Bearer $PINEYARD_API_KEY"
      ```

      ```javascript Node theme={null}
      const res = await fetch(`${API}/campaigns/${campaignId}/start`, { method: 'POST', headers })
      if (res.status !== 204) console.error(await res.json()) // e.g. { error: 'insufficient_credits' }
      ```

      ```python Python theme={null}
      res = requests.post(f"{API}/campaigns/{campaign_id}/start", headers=headers)
      if res.status_code != 204:
          print(res.json())  # e.g. {"error": "insufficient_credits"}
      ```
    </CodeGroup>

    A `204` means the campaign is running. Calls go out within the calling window you set.
  </Step>

  <Step title="Read the results">
    Check progress, then list the campaign's calls:

    <CodeGroup>
      ```bash curl theme={null}
      curl https://api.pineyard.ai/campaigns/00000000-0000-4000-8000-0000000000c1/stats \
        -H "Authorization: Bearer $PINEYARD_API_KEY"

      curl "https://api.pineyard.ai/calls?campaignId=00000000-0000-4000-8000-0000000000c1&limit=50" \
        -H "Authorization: Bearer $PINEYARD_API_KEY"
      ```

      ```javascript Node theme={null}
      const stats = await (await fetch(`${API}/campaigns/${campaignId}/stats`, { headers })).json()
      const calls = await (await fetch(`${API}/calls?campaignId=${campaignId}&limit=50`, { headers })).json()
      ```

      ```python Python theme={null}
      stats = requests.get(f"{API}/campaigns/{campaign_id}/stats", headers=headers).json()
      calls = requests.get(f"{API}/calls", headers=headers,
                           params={"campaignId": campaign_id, "limit": 50}).json()
      ```
    </CodeGroup>

    ```json Stats response theme={null}
    { "status": "RUNNING", "total": 2, "PENDING": 1, "IN_FLIGHT": 0, "MESSAGED": 0, "COMPLETED": 1, "FAILED": 0, "SKIPPED": 0 }
    ```

    Fetch one call with `GET /calls/{id}` for its transcript, summary and extracted data.
  </Step>
</Steps>

## Next steps

<CardGroup cols={3}>
  <Card title="Run a campaign end to end" icon="list-check" href="/guides/run-a-campaign">Scheduling, retries, pausing and the `/v1` lead API.</Card>
  <Card title="Receive results" icon="webhook" href="/guides/receive-results">Get each lead's result pushed to your server.</Card>
  <Card title="Recordings & transcripts" icon="waveform" href="/guides/recordings-and-transcripts">Download audio and read transcripts.</Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.