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

# API Overview

> Base URL, authentication, rate limits, error handling, and pagination for the Puffle API.

## Base URL

```
https://app.puffle.ai
```

Endpoint paths in this reference include their `/api` prefix, for example `GET /api/account`. Combine the base URL and documented path directly:

```bash theme={null}
curl -H "Authorization: Bearer pk_live_abc123" \
  "https://app.puffle.ai/api/account"
```

HTTPS is required on all requests.

<Note>
  The public product surface uses **Lead Finder**, **Feed**, **Outbound**, **Posts**, **Unibox**, and **Senders**. Lead Finder routes live under `/api/lead-finder`; Feed routes live under `/api/feed`. `/api/campaigns` powers Outbound execution, and `/api/calendar` powers Posts and connected social accounts. Use the documented endpoint paths exactly.
</Note>

## Public Endpoint Scope

The API Reference lists the supported public Bearer-token contract. Admin routes under `/api/admin/*`, dashboard-only session routes, inbound webhooks, OAuth callbacks, legacy Leads/Lists/Signals surfaces, and private integration plumbing are intentionally hidden from these docs.

If an endpoint is not documented here, do not infer that it is public or stable. Use the closest documented public endpoint or ask Puffle support for the intended workflow.

***

## Authentication

All public API requests require a Puffle API key as a Bearer token in the `Authorization` header. API keys are prefixed with `pk_live_`.

```http theme={null}
Authorization: Bearer pk_live_abc123...
```

### Generating an API key

1. Log in to the [Puffle dashboard](https://app.puffle.ai)
2. Navigate to **Settings -> API**
3. Click **Generate API key**
4. Copy and store the key. It is shown only once.

Each account has one active API key at a time. Rotation therefore requires a short interruption: pause callers, revoke the current key, generate the replacement, deploy it to every caller, and then resume traffic. You cannot create the replacement while the old key is active.

### Revoking an API key

Keys can be revoked from the dashboard or through the session-authenticated `/api/user/api-key` endpoint. Revocation is immediate, and subsequent requests using the revoked key fail with `401`.

### Session authentication

A small set of API key endpoints, specifically those that create, view, and revoke API keys (`/api/user/api-key`), use session-based authentication through browser cookies rather than Bearer tokens. These are designed to be called from the Puffle dashboard UI, not from your backend integration.

### Security best practices

<AccordionGroup>
  <Accordion title="Never expose keys in client-side code">
    API keys grant full access to your Puffle account. Always make API calls from your backend, never from browser JavaScript or mobile apps where the key could be extracted.
  </Accordion>

  <Accordion title="Use environment variables">
    Store your key in an environment variable (`PUFFLE_API_KEY`) and reference it in code. Never hard-code keys or commit them to version control.
  </Accordion>

  <Accordion title="Rotate keys periodically">
    Plan a maintenance window. Pause API traffic, revoke the active key, generate and securely store the replacement, update every service, verify a low-risk read such as `GET /api/account`, and only then resume normal traffic. Requests made between revocation and deployment return `401`.
  </Accordion>

  <Accordion title="Respond to suspected exposure">
    If a key may have been exposed, revoke it immediately from the Puffle dashboard and replace it before making more API requests.
  </Accordion>
</AccordionGroup>

***

## Rate Limits

Rate limits are endpoint-specific. Some routes enforce their own cooldowns or protective limits, and upstream providers may also return rate-limit errors. The endpoint page is the source of truth for request size and polling guidance.

If you receive a `429 Too Many Requests` response, respect the `Retry-After` header when it is present. If there is no `Retry-After`, back off exponentially and avoid tight polling loops.

### Rate-limit response

```json theme={null}
{
  "error": {
    "code": "rate_limited",
    "message": "Too many requests. Please retry after 60 seconds."
  }
}
```

### Handling rate limits

<Steps>
  <Step title="Detect the 429 status">
    Check for HTTP status `429` in every response before processing the body.
  </Step>

  <Step title="Read the Retry-After header">
    Wait the number of seconds specified in `Retry-After` before retrying.
  </Step>

  <Step title="Use exponential backoff">
    If `Retry-After` is not present, implement exponential backoff starting at 1 second.
  </Step>

  <Step title="Avoid polling loops">
    Avoid tight polling loops and back off aggressively after 429 responses.
  </Step>
</Steps>

***

## Errors

Public Bearer-authenticated routes generally return the structured error envelope below for framework-level authentication, method, validation, and typed route errors:

```json theme={null}
{
  "error": {
    "code": "error_code_string",
    "message": "Human-readable description of the error."
  }
}
```

Some route-specific errors still return the older flat shape:

```json theme={null}
{
  "error": "Human-readable description of the error."
}
```

Check the endpoint page examples for the exact response shape an operation can return. Treat `error.code` as stable when it is present; otherwise handle the HTTP status and human-readable `error` message.

### HTTP Status Codes

| Status | Meaning               | When It Happens                                                       |
| ------ | --------------------- | --------------------------------------------------------------------- |
| `200`  | OK                    | Successful GET, PATCH, PUT, or POST that returns data                 |
| `201`  | Created               | Successful resource creation (POST)                                   |
| `204`  | No Content            | Successful deletion or action with no response body                   |
| `400`  | Bad Request           | Invalid body, missing required fields, or constraint violation        |
| `401`  | Unauthorized          | Missing or invalid Bearer token / session                             |
| `403`  | Forbidden             | Valid auth but insufficient permissions                               |
| `404`  | Not Found             | Resource does not exist or does not belong to your account            |
| `409`  | Conflict              | Resource already exists, is already running, or cannot transition now |
| `422`  | Unprocessable Entity  | Well-formed but semantically invalid request                          |
| `429`  | Too Many Requests     | Rate limit exceeded                                                   |
| `500`  | Internal Server Error | Unexpected server error                                               |

### Error Codes

| Code              | Description                                                  |
| ----------------- | ------------------------------------------------------------ |
| `unauthorized`    | Missing or invalid authentication credentials                |
| `not_found`       | The requested resource does not exist                        |
| `invalid_request` | Body or parameters are invalid, or a constraint was violated |
| `rate_limited`    | Endpoint or provider rate limit hit                          |
| `conflict`        | Resource already exists or cannot transition now             |
| `internal_error`  | Unexpected server error                                      |

***

## Pagination

Pagination is endpoint-specific. Feed, Lead Finder, and sender endpoints generally return bounded lists. Unibox and some Outbound endpoints use `page`, `limit`, or route-specific pagination fields. Use the query parameters and response fields shown on each endpoint page.

### Common parameters

| Parameter | Type    | Description                                            |
| --------- | ------- | ------------------------------------------------------ |
| `limit`   | integer | Items to return. Maximum and default vary by endpoint. |
| `page`    | integer | Page number for page-based endpoints.                  |
| `cursor`  | string  | Cursor for endpoints that explicitly document cursors. |

### Iterating Pages

```bash theme={null}
# First page
curl -s -H "Authorization: Bearer pk_live_abc123" \
  "https://app.puffle.ai/api/threads?limit=50"

# Subsequent pages: follow the pagination field documented by the endpoint
curl -s -H "Authorization: Bearer pk_live_abc123" \
  "https://app.puffle.ai/api/threads?limit=50&page=2"
```
