# API Conventions

Use this page as the baseline contract for direct HTTP integrations. Endpoint pages document endpoint-specific fields, limits, and examples.


> Most teams should use an [official SDK](/docs/sdks) where possible. SDKs handle authentication, common request shapes, and language-specific gotchas for you.


---

## Base URL {{ id: 'base-url' }}

All REST API endpoints use this base URL:

```text
https://app.bentonow.com/api/v1
```

Endpoint pages show paths relative to `/api/v1`. For example, `/batch/events` becomes:

```text
https://app.bentonow.com/api/v1/batch/events
```

---

## Authentication {{ id: 'authentication' }}

Bento uses HTTP Basic authentication. Use your publishable key as the username and your secret key as the password.

```text
Authorization: Basic BASE64(publishable_key:secret_key)
```

Every API request must also include `site_uuid` as a query parameter so Bento knows which site to use.

```text
https://app.bentonow.com/api/v1/fetch/tags?site_uuid=YOUR_SITE_UUID
```

Read the full [Authentication](/docs/authentication) page if you need key locations, encoding examples, or security guidance.

---

## Required Headers {{ id: 'required-headers' }}

Every request should include these headers:

| Header | Required | Notes |
| --- | --- | --- |
| `Authorization` | Yes | Basic auth credentials. |
| `User-Agent` | Yes | Use a real app name, such as `AcmeCRM/1.0`. Requests without one may be blocked before they reach the API. |
| `Content-Type` | For JSON bodies | Use `application/json` for POST, PUT, and PATCH requests. |


> ⚠️ **Warning**
> If you receive HTML instead of JSON, check your `User-Agent` first. Missing or generic user agents are the most common cause.


---

## Request Shape {{ id: 'request-shape' }}

POST endpoints accept JSON request bodies. Batch endpoints usually wrap records in a plural top-level key:

| Endpoint | Top-level key | Typical batch size |
| --- | --- | --- |
| `/batch/events` | `events` | 1 to 100 events |
| `/batch/subscribers` | `subscribers` | 1 to 1000 subscribers |
| `/batch/emails` | `emails` | 1 to 60 emails |

Use UTF-8 JSON, avoid sending secrets in event details or custom fields, and keep field names lowercase and underscored where possible.

---

## Response Codes {{ id: 'response-codes' }}

| Status | Meaning | What to do |
| --- | --- | --- |
| `200` or `201` | Request accepted or completed. | Continue. |
| `400` | Invalid request body or parameters. | Check required fields and data types. |
| `401` | Missing or invalid auth, or missing `site_uuid`. | Check credentials and query string. |
| `403` | Request blocked or account access issue. | Check `User-Agent`, account status, and support guidance. |
| `404` | Endpoint or resource not found. | Check path, method, and identifiers. |
| `429` | Rate limited. | Slow down and retry with backoff. |
| `5xx` | Bento or upstream infrastructure error. | Retry later with backoff. |

For field-level guidance, use the [Errors Reference](/docs/api/errors).

---

## Pagination {{ id: 'pagination' }}

List-style endpoints use endpoint-specific pagination. Many older endpoints accept a `page` value. When an endpoint documents a `page` parameter, start at `1` and request the next page only after the previous response succeeds.

Do not poll broad list endpoints when a webhook, event trigger, or batch export would solve the same problem.

---

## Rate Limits And Retries {{ id: 'rate-limits-and-retries' }}

Rate limits vary by endpoint and account history. Endpoint pages call out important limits, such as the transactional email send pace.

Use these defaults unless a page says otherwise:

- Batch writes: prefer batches over many single-record requests.
- Retries: retry `429` and `5xx` with exponential backoff.
- Timeouts: set an application timeout and log the request body shape without logging secrets.
- Idempotency: avoid blindly retrying email sends from user-facing actions unless your application can prevent duplicates.

---

## Versioning {{ id: 'versioning' }}

The current public REST namespace is `/api/v1`. Bento keeps backwards compatibility where possible and documents meaningful API behavior changes in the [API Changelog](/docs/api/changelog).

---

## Examples {{ id: 'examples' }}

### cURL

```bash
curl -X POST 'https://app.bentonow.com/api/v1/batch/events?site_uuid=YOUR_SITE_UUID' \
  -H 'Authorization: Basic YOUR_BASE64_CREDENTIALS' \
  -H 'User-Agent: AcmeCRM/1.0' \
  -H 'Content-Type: application/json' \
  -d '{
    "events": [{
      "type": "$signed_up",
      "email": "person@example.com",
      "fields": { "first_name": "Ari" }
    }]
  }'
```

### Node.js

```js
const credentials = Buffer.from(`${process.env.BENTO_PUBLISHABLE_KEY}:${process.env.BENTO_SECRET_KEY}`).toString('base64')

await fetch(`https://app.bentonow.com/api/v1/batch/events?site_uuid=${process.env.BENTO_SITE_UUID}`, {
  method: 'POST',
  headers: {
    Authorization: `Basic ${credentials}`,
    'User-Agent': 'AcmeCRM/1.0',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    events: [{ type: '$signed_up', email: 'person@example.com' }],
  }),
})
```

### Python

```python
import base64
import os
import requests

credentials = base64.b64encode(
    f"{os.environ['BENTO_PUBLISHABLE_KEY']}:{os.environ['BENTO_SECRET_KEY']}".encode()
).decode()

requests.post(
    f"https://app.bentonow.com/api/v1/batch/events?site_uuid={os.environ['BENTO_SITE_UUID']}",
    headers={
        "Authorization": f"Basic {credentials}",
        "User-Agent": "AcmeCRM/1.0",
        "Content-Type": "application/json",
    },
    json={"events": [{"type": "$signed_up", "email": "person@example.com"}]},
)
```

## Related docs

- [API Quickstart](/docs/quickstart)
- [Authentication](/docs/authentication)
- [Errors Reference](/docs/api/errors)
- [SDKs](/docs/sdks)