---
title: Conventions
description: The rules that cross the AdCrunch APIs — ids, provider values, money, time, casing, paths, and pagination — including the places where the APIs do not agree yet.
---

This page states each rule as the API applies it today. Where the four APIs do not agree, the page says so.

## Ids

- **An advertiser id** is `acc_` and the account id of the provider, for example `acc_1203456789012345` for a Meta ad account and `acc_4829301756` for a Google Ads customer. An advertiser is an ad account that your organization connects.
- **An entity id** is the id that its provider gives, with no prefix: a campaign, an ad group, an ad, a creative, or an asset group. The same id can exist at two providers, or for two types at one provider. So an entity is unique for its `provider`, its `type`, and its `id` together.
- **A Google Ads ad** has a composite id, `{adGroupId}~{adId}`, because Google Ads keeps an ad on the link between the ad group and the ad.
- **A Skill, a Brand, a Persona, and a Campaign Plan** have a `slug`. The slug is the handle in the path, for example `/context/brands/acme`. A Persona belongs to one Brand, so its handle is the pair: `/context/brands/acme/personas/loyalists`.
- **An id that AdCrunch makes** has a prefix: `ast_` for an Asset, `doc_` for a Document, `lni_` for a Line Item, and `psn_` for a Persona.
- **A Mutation** has the `workflowId` that `POST /mutations` returns.

## Providers

The field `provider` takes the API value of a provider, for example `meta`, `tiktok`, or `gads`. Send the value exactly. `gads` is Google Ads, and no value is `google`. [What each provider supports](/connect/providers) lists the API value of each provider, and what each provider can do.

### Which providers an operation works with

Some operations work with some providers only. A creative comes from Meta, an asset group comes from Google Ads, and a change runs on Meta. Each of those operations starts with one line of badges:

**Available on:** [![Meta](/providers/meta.svg)](/connect/providers) [![TikTok](/providers/tiktok.svg)](/connect/providers)

The same operation carries those API values in `x-adcrunch-providers`, beside its description in the OpenAPI document. A code generator reads that field, and it reads no prose.

**An operation with no badges works the same way whatever your organization connects.** The mutation history, every Context operation, and every Asset operation except registration are of this kind.

The line names the providers and stops there. To read what each provider supports, and where it stops, use [the one table](/connect/providers).

## Money

A read and a write use different units today.

| Where | Unit | 50.00 is |
| --- | --- | --- |
| Observe: `budget` on an entity | whole units | `50` |
| Observe: `spend` and each money metric on an Insight | whole units, with decimals | `50` |
| Mutations: `dailyBudget`, `lifetimeBudget` | integer minor units | `5000` |
| Context: each amount on a Campaign Plan and a Line Item | integer minor units | `5000` |

:::warning[Convert a budget before you write it]

A budget that you read from Observe is in whole units. `POST /mutations` takes minor units. If you send the value that you read, the new budget is 100 times smaller than you intend.

:::

A minor unit is the smallest unit of the currency: a cent of a dollar or of a euro.

Each amount is in the currency of its advertiser or of its Campaign Plan. A `currency` field names that currency as an ISO 4217 code.

`GET /observe/insights` also takes a `currency` parameter. With it, AdCrunch converts each row at the European Central Bank rate of the date of that row. It converts the rows before it adds them. Without the parameter, a group that mixes two currencies answers `currency: null`.

## Time

- **A timestamp that AdCrunch writes** is a number: milliseconds since the Unix epoch, in UTC. `createdAt` and `updatedAt` are examples.
- **A calendar date** is a string, `YYYY-MM-DD`: `since` and `until` on Insights, and each date on a Campaign Plan.
- **A delivery time of Meta** is an ISO 8601 string: `startTime` and `endTime` when you create an ad set.

## Casing

The APIs do not use one casing for field names.

- **Observe, Mutations, and Assets** use camelCase: `advertiserId`, `dailyBudget`.
- **Context** takes snake_case in a request, and answers camelCase. You send `website_url`, and you read `websiteUrl`. The query parameter `base_revision` is snake_case too.
- **An Insight** mixes the two in one row: `advertiserId` and `entityId` beside `conversion_value` and `action_add_to_cart`.

Send each field in the casing that its operation page shows.

## Paths

Some published paths end in a slash, for example `/context/skills/` and `/assets/`. Others do not, for example `/observe/campaigns`. Send each path as its operation page shows it.

## Pagination

The lists use three forms today.

| Operations | Send | Read | The last page |
| --- | --- | --- | --- |
| The entity lists of Observe | `limit` (default 500, maximum 500) and `offset` | `data`, and `pagination` with `limit`, `offset`, and `total` | `offset + limit` is `total` or more |
| `GET /observe/insights` | `limit` (maximum 100) and `offset` | an array of rows | a page with fewer rows than `limit` |
| `GET /mutations` | `cursor`, from the previous answer | `mutations` and `nextCursor` | the answer has no `nextCursor` |
| The lists of Context and Assets | nothing | the whole list | the first page |

A live read (`fresh=true`) refuses an `offset` above `0` with `422`, because a provider pages with its own cursor. To read more rows live, raise `limit`. A live read can also answer with no `pagination.total`. [What each provider supports](/connect/providers) says which provider does this.
