---
title: Conventions
description: Authentication, pagination, errors, dates and versioning. The rules shared by every endpoint.
---

# Conventions

## Base URL

```
https://api.dokaa.app/v1
```

HTTPS only. All responses are JSON with `snake_case` field names.

## Authentication

Pass your API key on every request:

```
Authorization: Bearer dk_live_...
```

Each key carries **scopes** that gate which endpoints it can call, and a
**perimeter**: the shops it can read. Requests outside the key's scopes
return `403`; unknown or revoked keys return `401`.

## Scopes

Every key carries scopes that gate which endpoints it can call; enforcement
is server-side, on every request. Keys created today carry every read scope.
Fine-grained scope selection at creation is planned; the table below shows
which scope unlocks which endpoint.

| Scope | Endpoints it unlocks |
| ----- | -------------------- |
| `shops:read` | `/shops` |
| `reviews:read` | `/reviews`, `/analytics/reviews` |
| `feedbacks:read` | `/analytics/feedbacks` |
| `contacts:read` | `/contacts`, `/analytics/contacts` |
| `campaigns:read` | `/campaigns` |
| `missed_calls:read` | `/analytics/missed-calls` |
| `wheel_games:read` | `/analytics/wheel-games` |
| `audiences:read` | `/audiences` |
| `google_interactions:read` | `/google-interactions` |
| `google_posts:read` | `/google-posts`, `/google-post-groups`, `/google-post-groups/eligible-shops`, `/google-post-groups/{batch_id}` |
| `keyword_tracking:read` | `/keyword-tracking`, `/keyword-rankings/summary`, `/keyword-rankings/trend` |
| `competitors:read` | `/competitors`, `/competitors/positioning`, `/competitors/evolution` |
| `dashboard:read` | `/summary` |

A key's scopes cannot be extended after creation. Its shop perimeter follows
its definition: a key created for a group covers the shops that later join
that group.

## Shop perimeter

- On multi-shop endpoints, omit `shop_id` to query **all** the shops your key
  can access at once, or pass it to focus on a single shop.
- Competitor and keyword tracking endpoints are single-shop: `shop_id` is
  required on `/competitors`, `/competitors/positioning`,
  `/competitors/evolution`, `/keyword-tracking`, `/keyword-rankings/summary`
  and `/keyword-rankings/trend`.
- A `shop_id` outside your perimeter returns `403`.
- `GET /shops` lists your perimeter and the ids to use.

## Pagination

List endpoints are cursor-paginated:

```json
{
  "data": [ ... ],
  "pagination": {
    "next_cursor": "eyJkIjoiMjAyNi0w...",
    "has_more": true
  }
}
```

Pass `pagination.next_cursor` back as `?cursor=` to fetch the next page, until
`has_more` is `false`. Cursors are opaque: do not build or modify them. The
default page size is 25, and `?limit=` accepts 1 to 100.

Cursor pagination is stable: items created while you paginate can never shift
your pages, so exports get every row exactly once.

## Dates

- All timestamps are returned in **UTC**, ISO 8601 (`2026-08-01T09:30:00.000Z`).
- `from` / `to` filters accept ISO dates (`2026-01-01`). `to` is inclusive of
  the whole day.

## Errors

Errors follow [RFC 9457 Problem Details](https://www.rfc-editor.org/rfc/rfc9457)
with `Content-Type: application/problem+json`. The `type` URI identifies the
error kind; its last segment is the stable identifier to match on:

```json
{
  "type": "https://api.dokaa.app/docs/errors/invalid-parameters",
  "title": "Invalid parameters",
  "status": 422,
  "detail": "\"from\" must be in ISO 8601 date format"
}
```

| Status | Type | Meaning |
| ------ | ---- | ------- |
| 401 | `missing-api-key` / `invalid-api-key` | No key, unknown key, or revoked key |
| 403 | `missing-scope` | The key lacks the scope this endpoint requires |
| 403 | `shop-access-denied` | The requested `shop_id` is outside the key's perimeter |
| 404 | `resource-not-found` | The requested resource does not exist or holds no data (unknown batch id, no competitor or ranking snapshot) |
| 422 | `invalid-parameters` | A query parameter failed validation |
| 422 | `invalid-cursor` | The pagination cursor is malformed |
| 500 | `internal-error` | Unexpected error on our side |

## Versioning

- **Additive changes** (new endpoints, new optional fields, new enum values)
  ship continuously and are always safe: your integration must tolerate
  unknown fields.
- **Breaking changes** are exceptional and ship under a new base path
  (`/v1` → `/v2`), announced in advance in the changelog. `/v1` keeps
  working unchanged: you migrate when you choose to.

Every change is listed in the [changelog](/docs/changelog/).
