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

# Quickstart

> From zero to a working integration — first request, create a contact, trigger a call, receive the webhook.

A working end-to-end integration in four steps. Everything here uses real endpoints — see the [reference](/api-reference/introduction) for full parameters.

## 1. Create a key

In the dashboard, go to **API keys** and create one with only the permissions this integration needs. For the example below that's `contacts:create` and `calls:create`.

Copy it immediately — the full key is shown once.

```bash theme={null}
export RECEPTA_API_KEY="rcp_your_key_here"
```

<Warning>
  Keep this server-side. Never ship a key in frontend JavaScript or a mobile app — anything on a user's device is readable by that user. Proxy through your own backend.
</Warning>

## 2. Confirm it works

```bash theme={null}
curl "https://api.recepta.ai/api/v1/api/calls?limit=1" \
  -H "x-api-key: $RECEPTA_API_KEY"
```

A `200` with `"success": true` means you're connected. A `401` means the key is wrong or truncated; a `403` means it's valid but lacks the permission. See [Errors](/api-reference/errors).

Every response uses the same envelope:

```json theme={null}
{
  "success": true,
  "message": "Calls retrieved successfully",
  "data": {}
}
```

Branch on the HTTP status and `success` — never on the text of `message`.

## 3. Create a contact and call them

The common pattern: a lead arrives in your system, you push it to Recepta.ai and have an agent call it immediately.

<CodeGroup>
  ```bash cURL theme={null}
  # Create the contact
  curl -X POST https://api.recepta.ai/api/v1/api/contacts \
    -H "x-api-key: $RECEPTA_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "firstName": "Dana",
      "lastName": "Reyes",
      "phone": "+14155551234",
      "source": "API",
      "tags": ["website-lead"]
    }'

  # Call them
  curl -X POST https://api.recepta.ai/api/v1/api/calls/outbound \
    -H "x-api-key: $RECEPTA_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "toNumber": "+14155551234",
      "fromNumber": "+14155559876",
      "metadata": { "source": "website-form" }
    }'
  ```

  ```javascript Node.js theme={null}
  const API = 'https://api.recepta.ai/api/v1';
  const headers = {
    'x-api-key': process.env.RECEPTA_API_KEY,
    'Content-Type': 'application/json',
  };

  async function callNewLead(lead) {
    await fetch(`${API}/api/contacts`, {
      method: 'POST',
      headers,
      body: JSON.stringify({
        firstName: lead.firstName,
        lastName: lead.lastName,
        phone: lead.phone,      // normalized to E.164 for you
        source: 'API',
        tags: ['website-lead'],
      }),
    });

    const res = await fetch(`${API}/api/calls/outbound`, {
      method: 'POST',
      headers,
      body: JSON.stringify({
        toNumber: lead.phone,
        fromNumber: process.env.RECEPTA_FROM_NUMBER,
        metadata: { source: 'website-form' },
      }),
    });

    if (res.status === 429) {
      // Rate limit, or your plan's call allowance is exhausted.
      // The message distinguishes them.
    }
    return res.json();
  }
  ```

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

  API = "https://api.recepta.ai/api/v1"
  headers = {
      "x-api-key": os.environ["RECEPTA_API_KEY"],
      "Content-Type": "application/json",
  }

  def call_new_lead(lead):
      requests.post(f"{API}/api/contacts", headers=headers, json={
          "firstName": lead["first_name"],
          "lastName": lead["last_name"],
          "phone": lead["phone"],
          "source": "API",
          "tags": ["website-lead"],
      })

      r = requests.post(f"{API}/api/calls/outbound", headers=headers, json={
          "toNumber": lead["phone"],
          "fromNumber": os.environ["RECEPTA_FROM_NUMBER"],
          "metadata": {"source": "website-form"},
      })
      r.raise_for_status()
      return r.json()
  ```
</CodeGroup>

<Warning>
  `fromNumber` must be one of your own provisioned numbers, and outbound calling carries calling-hour, consent, and do-not-call obligations in the called party's jurisdiction. See [Recording & consent](/phone/recording-and-consent) before you automate this.
</Warning>

## 4. Receive the result

Don't poll for the outcome. Register a webhook endpoint and subscribe to `call.ended`, then read the result as it happens.

```javascript theme={null}
import crypto from 'crypto';

app.post('/webhooks/recepta', express.raw({ type: 'application/json' }), (req, res) => {
  const signature = req.get('X-Webhook-Signature');
  const expected = crypto
    .createHmac('sha256', process.env.RECEPTA_WEBHOOK_SECRET)
    .update(req.body, 'utf8')          // raw body, not re-serialized JSON
    .digest('hex');

  const a = Buffer.from(expected, 'hex');
  const b = Buffer.from(signature, 'hex');
  if (a.length !== b.length || !crypto.timingSafeEqual(a, b)) {
    return res.sendStatus(401);
  }

  const event = JSON.parse(req.body);
  enqueue(event);        // do the work off the request path
  res.sendStatus(200);   // respond fast
});
```

<Warning>
  Compute the HMAC over the **raw request body**. Parsing and re-serializing the JSON changes whitespace and key order, and the signature will never match — this is the most common webhook integration bug.
</Warning>

See [Webhooks](/api-reference/webhooks) for the full event list and Python/PHP verification.

## Before you go to production

<AccordionGroup>
  <Accordion title="Handle 429 properly" icon="gauge-high">
    Read `X-RateLimit-Remaining` and back off before you hit the limit; honour `Retry-After` when you do. Add jitter so parallel workers don't retry in lockstep. See [Rate limits](/api-reference/rate-limits).
  </Accordion>

  <Accordion title="Make webhook handling idempotent" icon="copy">
    Key on the event `id` and ignore duplicates. Assume any event can arrive more than once and out of order.
  </Accordion>

  <Accordion title="Scope keys per integration" icon="key">
    One key per system, with minimum permissions. Then you can revoke one without breaking the rest — and rate limits are tracked per key, so they don't compete.
  </Accordion>

  <Accordion title="Don't log secrets or transcripts" icon="lock">
    Keys are credentials; transcripts contain customer personal data. See [Security & privacy](/account/security-and-privacy).
  </Accordion>
</AccordionGroup>

<Card title="Let an assistant query your workspace instead" icon="robot" href="/api-reference/mcp">
  If you want analysis rather than automation, the MCP server exposes read-only tools to AI assistants with no integration code at all.
</Card>
