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

# API reference

> REST shape, response envelope, idempotency, rate limits.

The Vibefollow API is a small, JSON-over-HTTPS surface. The TypeScript SDK wraps it — everything documented here is what the SDK does under the hood.

## Base URL

```
https://api.vibefollow.com/api/v1
```

<Info>
  All endpoints are namespaced under `/api/v1`. Future major versions will use a new namespace (`/api/v2`); v1 will remain available indefinitely.
</Info>

## Endpoint catalog

<CardGroup cols={3}>
  <Card title="POST /api/v1/users" icon="user-plus" href="/api-reference/users/upsert">
    Upsert a user.
  </Card>

  <Card title="POST /api/v1/events" icon="bolt" href="/api-reference/events/track">
    Track a single event.
  </Card>

  <Card title="POST /api/v1/events/batch" icon="layer-group" href="/api-reference/events/batch">
    Track multiple events in one POST.
  </Card>
</CardGroup>

<Note>
  The customer-facing API surface is intentionally narrow. The Vibefollow backend exposes a much larger surface for the in-dashboard product (drafts, audiences, sequences, etc.) — those endpoints aren't part of the public contract.
</Note>

## Response envelope

Successful responses always wrap their payload in `{ data, meta? }`:

```json theme={null}
{
  "data": { "accepted": true }
}
```

Collection responses also include `meta.pagination`:

```json theme={null}
{
  "data": [ ... ],
  "meta": {
    "pagination": { "total": 142, "offset": 0, "limit": 50 }
  }
}
```

## Error envelope

Errors return a non-2xx status and a body shaped as `{ errors: [{ code, message, field? }] }`:

```json theme={null}
{
  "errors": [
    {
      "code": "validation_failed",
      "message": "required",
      "field": "external_user_id"
    }
  ]
}
```

`code` is a machine-readable identifier (snake\_case). `message` is human-readable. `field` names the offending field when applicable. The SDK maps each status to a typed error class — see [Errors](/errors).

## Idempotency

Every mutating request should carry an `Idempotency-Key` header:

```http theme={null}
Idempotency-Key: 7b6c7e9e-2c2d-4a8d-9d3a-4f3d2c1b0a99
```

<Info>
  The SDK generates a v4 UUID on every request. The backend dedupes within a 24-hour window — if the same key arrives twice, the second request returns the first response and no work is duplicated.
</Info>

Safe to retry on `5xx`, `429`, or network failures. The SDK does this automatically.

## Rate limits

Per-project, per-endpoint limits apply. When you exceed a limit you get:

```http theme={null}
HTTP/1.1 429 Too Many Requests
Retry-After: 30
Content-Type: application/json

{ "errors": [{ "code": "rate_limited", "message": "Too many requests" }] }
```

Wait `Retry-After` seconds before retrying. The SDK does this for you — `RateLimitError` is only thrown after retries are exhausted.

## Authentication

All endpoints require an API key in the `Authorization` header:

```http theme={null}
Authorization: Bearer sk_live_••••••••••••••••••••
```

See [Authentication](/api-reference/authentication) for the wire-level detail.

## Content types

Requests must be `Content-Type: application/json`. Responses are always JSON. Bodies are UTF-8 encoded.

## Timestamps

All timestamps in request and response bodies are ISO 8601 strings in UTC:

```json theme={null}
{ "occurred_at": "2026-05-17T14:00:00.000Z" }
```

<Note>
  The backend stores timestamps as `TIMESTAMPTZ`; UI renders them in the user's timezone.
</Note>
