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.