API reference

Push rows from n8n, Make, Zapier or a script, and read your charts back. Base URL: https://chartomodo.com/api/v1

Authentication

Create a token in Settings → API. The API needs the Pro plan or higher. Send the token on every request as a Bearer token; the scheme name is not case-sensitive. The token is shown once. Revoke it in the same place at any time.

GET /api/v1/me returns your id, email and plan. Use it to check that a token works.

curl https://chartomodo.com/api/v1/me -H "Authorization: Bearer cmo_…"

Create a dataset

POST /api/v1/datasets with a name (1–100 characters) and the first rows. It answers 201 with the dataset id, rowCount, columns, addedColumns and droppedColumns. Open the dataset from Home to build a chart on it. A dataset counts toward your plan's source limit, like a CSV upload.

GET /api/v1/datasets lists your datasets as {"data":[…]}, newest first, each with id, name, columns, rowCount, lastIngestAt and createdAt. DELETE /api/v1/datasets/{id} answers 204. Charts built on it stay, with no data source.

curl -X POST https://chartomodo.com/api/v1/datasets -H "Authorization: Bearer cmo_…" -H "Content-Type: application/json" \
  -d '{"name":"Daily sales","rows":[{"day":"2026-10-01","amount":120.5}]}'

Replace or append rows

PUT /api/v1/datasets/{id}/rows replaces every row in one step: embeds show the old rows or the new ones, never half. Columns the new rows no longer have are listed in droppedColumns.

POST /api/v1/datasets/{id}/rows appends. A column that is new gets null in the rows already stored. Both answer 200 with id, rowCount, columns, addedColumns and droppedColumns.

The body is {"rows":[…]}: a non-empty array of flat objects. A cell is a string, a finite number, true, false or null. Send dates as ISO strings (2026-10-01). A nested object or array is refused with nested_value. A key missing from a row becomes null. Column names are trimmed, must not repeat within a row, and are at most 200 characters. A string cell holds at most 10,000 characters.

curl -X PUT https://chartomodo.com/api/v1/datasets/{id}/rows -H "Authorization: Bearer cmo_…" -H "Content-Type: application/json" \
  -d '{"rows":[{"day":"2026-10-02","amount":98}]}'

Retrying an append safely

Send an Idempotency-Key header on PUT or POST /api/v1/datasets/{id}/rows: 1–255 printable ASCII characters, no spaces. Other endpoints ignore it. Keys are scoped to the token and the dataset. Once a write with a key has succeeded, the same key on the same dataset within 24 hours writes nothing and answers 200 {"duplicate":true}. A write that fails frees its key, so the retry goes through. An empty header is ignored. An invalid key is a 400 invalid_idempotency_key, and a body that is not valid JSON (400 invalid_json) never uses up a key.

While the first request with a key is still running, a retry gets 503 idempotency_in_progress with Retry-After: 5. Wait and send it again: if the first write succeeded you get the duplicate answer, if it failed the retry writes the rows.

If the key cannot be checked, the write is refused with 503 idempotency_unavailable rather than risk adding rows twice. Retry later.

curl -X POST https://chartomodo.com/api/v1/datasets/{id}/rows -H "Authorization: Bearer cmo_…" -H "Content-Type: application/json" \
  -H "Idempotency-Key: sales-2026-10-02" \
  -d '{"rows":[{"day":"2026-10-02","amount":98}]}'

Read charts

GET /api/v1/charts lists your newest 500 charts as {"data":[…]}. Each has id, title, type, datasetId (set when the chart reads an API dataset, otherwise null), sourceType, embedUrl and updatedAt. embedUrl is null for a private chart that has no embed token yet. GET /api/v1/charts/{id} returns one chart. An unknown id, or a chart that belongs to someone else, answers 404 not_found.

GET /api/v1/charts/{id}/data returns the numbers the chart draws: id, title, type, categories, series (one array per series), percent, degraded and truncated. If the chart's source cannot be read, it answers 502 source_unavailable.

curl https://chartomodo.com/api/v1/charts/{id}/data -H "Authorization: Bearer cmo_…"

Limits

5 MB per request body, 50,000 rows and 100 columns per dataset, 10 MB stored per dataset, 60 requests per minute per token.

Past the rate limit the answer is 429 with a Retry-After header in seconds. Wait that long, then retry. If a write answers 503 busy, another write holds the dataset: retry in a few seconds.

Errors

Every error is JSON: {"error":{"code":"…","message":"…"}}. Branch on the code, not the message.

  • 400 invalid_json: The body is missing or is not valid JSON.
  • 400 invalid_idempotency_key: Idempotency-Key is not 1–255 printable ASCII characters without spaces.
  • 401 invalid_token: The token is missing, malformed, unknown or revoked.
  • 403 plan_required: Your plan does not include the API. It needs the Pro plan or higher.
  • 403 account_suspended: The account is suspended.
  • 403 connection_limit: Creating the dataset would pass your plan's source limit.
  • 404 not_found: The dataset or chart does not exist or is not yours.
  • 413 payload_too_large: The body is larger than 5 MB.
  • 422 invalid_name: name must be 1–100 characters.
  • 422 invalid_rows: rows is empty, not an array, holds something that is not an object, or has no columns.
  • 422 invalid_column: A column name is empty, reserved, longer than 200 characters, or repeats after trimming.
  • 422 nested_value: A cell is an object or an array. Flatten it first.
  • 422 invalid_value: A cell is not a string, a finite number, true, false or null.
  • 422 cell_too_long: A string cell is longer than 10,000 characters.
  • 422 row_limit: The dataset would hold more than 50,000 rows. Replace instead of appending.
  • 422 column_limit: The dataset would hold more than 100 columns.
  • 422 dataset_too_large: The dataset would be stored at more than 10 MB. Send fewer rows or columns.
  • 429 rate_limited: More than the per-token limit. Wait for Retry-After seconds.
  • 500 internal_error: The write or the dataset creation failed and nothing was changed. Retry.
  • 502 source_unavailable: The chart's data source could not be read.
  • 503 api_disabled: The API is switched off for now. Try again later.
  • 503 rate_limit_unavailable: The rate limit could not be checked. Retry in a minute.
  • 503 maintenance: Writes are paused for maintenance. Reads still work.
  • 503 busy: Another write holds the dataset. Retry in a few seconds.
  • 503 idempotency_in_progress: A write with this Idempotency-Key is still running. Retry after Retry-After seconds (5).
  • 503 idempotency_unavailable: The Idempotency-Key could not be checked, so the write was refused. Retry later.

n8n

Use the HTTP Request node with an Authorization header set to Bearer and your token. On a write, turn on Retry On Fail with a 5 second wait: it covers 503 busy and 503 idempotency_in_progress. Add an Idempotency-Key header so a retry cannot add rows twice.

May we use Google Analytics cookies to count visits to our public pages? Nothing inside the app or your embeds is ever tracked. Privacy Policy