# API Quickstart

> Authenticate to the AgencyTitan REST API and handle its standard response envelopes, pagination, errors, permissions, and rate limits.

URL: https://www.agencytitan.com/help/getting-started/api-quickstart/

The AgencyTitan REST API uses resource-oriented URLs, JSON request bodies, and JSON responses. The production base URL is `https://api.agencytitan.com`.

The [API reference](/docs/) is generated from the current OpenAPI document. Use it for the complete request and response schema for each operation.

## Create and protect an API key

Create API keys under **Settings > System > API & MCP**. You can open the <a href="https://app.agencytitan.com/settings/api-mcp" target="_blank" rel="noopener noreferrer">API & MCP settings<span class="sr-only"> (opens in new tab)</span></a> directly after signing in.

An API key starts with `at_` and acts as its assigned service account, not as the person who created it. Store the key in a secret manager or environment variable. Do not put it in browser code, source control, logs, or URLs.

Send the key as a bearer token:

```bash
curl "https://api.agencytitan.com/v1/tenant-context" \
  --header "Authorization: Bearer $AGENCYTITAN_API_KEY" \
  --header "Accept: application/json"
```

See [`GET /v1/tenant-context`](/docs/tag/tenant-context/GET/v1/tenant-context) for the response schema. OAuth 2.0 access tokens use the same header and are also supported.

## Response envelopes

A successful operation that returns one resource wraps it in `data`:

```json
{
  "data": {
    "id": "11111111-1111-4111-8111-111111111111"
  }
}
```

A list operation returns an array in `data` and a `pagination` object:

```json
{
  "data": [],
  "pagination": {
    "total": 0,
    "limit": 50,
    "offset": 0
  }
}
```

An empty `data` array is a successful result. It can mean that no records matched or that the service account's assigned-data scope contains no matching records.

## Pagination, search, and sorting

List operations share these query conventions:

- `limit` is the maximum number of items to return. It accepts 1 through 200 and defaults to 50.
- `offset` is the number of matching items to skip. It starts at 0.
- `sort` accepts only the fields declared by that resource's operation.
- `order` is `asc` or `desc`.
- `search` is available only when the operation declares it.

Resource-specific filters appear alongside these shared parameters. When a filter accepts several values, send it in the array form shown by the operation schema. Do not assume that every list supports the same filters or sort fields.

Continue until `offset + data.length` is greater than or equal to `pagination.total`. For changing datasets, choose a stable sort field and avoid reusing an old offset after changing filters.

## Errors

Most API errors use this shape:

```json
{
  "error": {
    "type": "validation_error",
    "message": "Request validation failed."
  }
}
```

The supported error types map to these status codes:

| Status | Error type | Meaning |
| --- | --- | --- |
| `400` | `validation_error` | The request did not match the operation schema. |
| `403` | `permission_denied` | The service account cannot perform the operation. |
| `404` | `not_found` | The requested resource was not found or is not visible. |
| `409` | `conflict` | Current state or another record prevents the operation. |
| `429` | `rate_limited` | A rate-limit bucket is exhausted. |
| `500` | `internal` | The request could not be completed safely. |

Some errors include an `error.details` object. Authentication failures use a separate OAuth-style shape:

```json
{
  "error": "invalid_token",
  "error_description": "API key is invalid, expired, or revoked"
}
```

Unknown body or query properties are rejected instead of being silently ignored. Use the operation schema as the source of truth and handle error `type` in addition to the human-readable message.

## Permissions and record scope

Every request runs as the API key's assigned service account or the authenticated OAuth user. Existing product permissions and row-level access rules still apply.

A caller without the required permission receives `403 permission_denied`. A caller with assigned-data scope may receive a successful list with inaccessible rows omitted. Give service accounts only the permissions and client or task scope the integration needs.

## Rate limits

REST requests consume two fixed-window budgets: 600 requests per minute for the agency and 300 requests per minute for the API key or OAuth client. A noisy credential can exhaust its own budget without consuming the entire agency budget.

Responses expose the tighter budget through:

- `X-RateLimit-Limit`
- `X-RateLimit-Remaining`
- `X-RateLimit-Reset`, as a Unix timestamp in seconds

A `429` response also includes `Retry-After` in seconds. Wait for that interval, then retry with bounded exponential backoff and jitter. Do not retry validation or permission failures without changing the request or access configuration.

## Next steps

- Follow [Working with tasks and tickets](/help/guides/working-with-tasks-and-tickets/) for a core domain walkthrough.
- Use [Files and attachments](/help/guides/files-and-attachments/) to issue short-lived download URLs.
- Read [Bulk operations](/help/guides/bulk-operations/) to update as many as 100 clients, tasks, or tickets per request with per-item results.
