REST API
Save and delete documents of hundreds or thousands of pages from your own scripts. No AI involved.
Instead of adding a PDF, a spreadsheet or a report one at a time by hand, a script does the whole run: you extract the text and split it yourself, and send the result.
What you save here is identical to anything saved through a connected AI — same store, and your AI recalls it the same way.
Reading is by id, not by search. Semantic recall stays with MCP and your dashboard.
Getting started
- Create a key in the REST API card on your account page.
Its name is recorded as the origin of everything saved with it —
Import script,pdf-importer, whatever you'll recognise later. - The key is shown once, right after you create it. Copy it then; it can't be displayed again.
- Create as many keys as you like and delete any of them at any time.
Authentication
https://contextick.aiAuthorization: Bearer api_xxxxxxxx
Only keys that start with api_ work here. An MCP key or an OAuth token gets
401 on this API, and a REST API key gets 401 on the MCP endpoint — the two
surfaces are separate so you can revoke one without touching the other.
Endpoints
POST /api/v1/notes
Saves one item.
Body
| Field | Type | Required | Limit |
|---|---|---|---|
| title | string | ✓ | 120 characters — longer is trimmed |
| summary | string | ✓ | 300 characters — longer is trimmed |
| content | string | ✓ | 16,000 characters — longer is rejected |
All three must be non-empty; a blank one is refused rather than filled in by guesswork.
Request
curl -X POST https://contextick.ai/api/v1/notes \
-H "Authorization: Bearer api_xxxxxxxx" \
-H "Content-Type: application/json" \
-d '{"title":"Q3 report","summary":"Revenue and headcount for Q3.","content":"…"}'
Response 201
{ "id": 42, "saved_items": 118, "stored_bytes": 1048576 }
saved_items and stored_bytes are your totals after the save. If they
couldn't be read, the fields are absent rather than zero.
The origin recorded on the item is the key's name. It can't be set from the request.
GET /api/v1/notes/{id}
Reads one item in full.
Request
curl https://contextick.ai/api/v1/notes/42 \
-H "Authorization: Bearer api_xxxxxxxx"
Response 200
{ "id": 42, "title": "Q3 report", "summary": "…", "content": "…",
"author": "you@example.com", "origin": "Import script", "created_at": "…",
"status": "ready", "group_id": null, "times_opened": 3 }
Reading here does not change times_opened — that count tracks what you open, not what a script syncs.
status is ready, pending or failed — only a ready item can be found by search.
group_id is set when the item came from an uploaded file, and null otherwise. An id that isn't there answers 404.
GET /api/v1/notes/group/{group_id}
Lists the parts one uploaded file became.
Request
curl https://contextick.ai/api/v1/notes/group/6f1c2a94 \
-H "Authorization: Bearer api_xxxxxxxx"
Response 200
{ "group_id": "6f1c2a94", "title": "Q3 report",
"part_count": 13, "ready": 11, "pending": 2, "failed": 0,
"items": [
{ "id": 42, "title": "Q3 report (1/13)", "status": "ready" },
{ "id": 43, "title": "Q3 report (2/13)", "status": "pending" } ] }
title is the file's own title, taken from its first part without the part numbering, and the four counts save you walking items to get them.
The parts come back in id order, without their text — ask the read above for a part's content.
The group_id comes from the item you read above. A label with no items answers 404.
DELETE /api/v1/notes/group/{group_id}
Removes every part of one uploaded file.
Request
curl -X DELETE https://contextick.ai/api/v1/notes/group/6f1c2a94 \
-H "Authorization: Bearer api_xxxxxxxx"
Response 200
{ "group_id": "6f1c2a94", "deleted": [42, 43, 44],
"saved_items": 117, "stored_bytes": 1040000 }
All of the file or none of it — every part carrying that label goes in one transaction, so an interrupted call cannot leave the file half deleted.
Unlike the two deletes above there is no not_found array: this takes one label, not a list of ids, so a label with no items answers 404 like the read above.
GET /api/v1/notes/changes
Pulls items added since your last check, oldest first.
Query
| Parameter | Type | Required | Notes |
|---|---|---|---|
| cursor | integer | Where you left off — the cursor from the previous response. | |
| since | RFC 3339 | A point in time, e.g. 2026-09-01T13:00:00+09:00. The offset is required. First call only. | |
| limit | integer | 1–100. Defaults to 50. |
Start with since (or nothing at all), then follow the cursor. Sending both is an error.
Request
curl "https://contextick.ai/api/v1/notes/changes?since=2026-09-01T04:00:00Z&limit=50" \
-H "Authorization: Bearer api_xxxxxxxx"
Response 200
{ "data": [
{ "id": 41, "title": "Q3 report", "summary": "…", "created_at": "2026-09-01T04:00:12Z",
"origin": "Import script", "status": "ready", "group_id": null } ],
"pagination": { "limit": 50, "poll_cursor": "41" } }
next_cursor means more is waiting — ask again right away. poll_cursor means you are up to date — store it and check back later. Exactly one of the two is present.
Items come oldest first, so the cursor only moves forward and nothing is skipped, however long you were away. The text isn't included — read a single item when you need it.
since is a starting point, not a filter: a few slightly older items can ride along. status may be pending — stored and readable, not searchable yet. Deletions and later status changes don't show up here.
DELETE /api/v1/notes/{id}
Deletes one item.
Request
curl -X DELETE https://contextick.ai/api/v1/notes/42 \
-H "Authorization: Bearer api_xxxxxxxx"
POST /api/v1/notes/delete
Deletes several at once.
Body
| Field | Type | Required | Limit |
|---|---|---|---|
| ids | number[] | ✓ | 50 ids per call — more is rejected |
This is the way to undo a bulk import.
Request
curl -X POST https://contextick.ai/api/v1/notes/delete \
-H "Authorization: Bearer api_xxxxxxxx" \
-H "Content-Type: application/json" \
-d '{"ids":[42,43]}'
Delete response 200
{ "deleted": [42], "not_found": [43], "saved_items": 117, "stored_bytes": 1040000 }
Both deletes answer with the same shape. An id that isn't there is reported in
not_found, not treated as an error. Deleting is permanent and can't be
undone.
Errors
Every failure uses one shape:
{ "error": { "code": "quota_exceeded", "message": "…" } }
Branch on code. The message is prose for a human reader and may change at
any time.
empty_titleempty_summaryempty_contentcontent_too_longtoo_many_idsinvalid_bodyinvalid_idinvalid_query
api_ key
unauthorized
payment_past_duesubscription_pausedsubscription_canceledrefund_pending
not_foundendpoint_not_found
Allow header
method_not_allowed
Content-Type: application/json
unsupported_media_type
rate_limitedembedding_rate_limited
quota_exceeded
Receiving webhooks
Having saves, deletes and items becoming searchable pushed to your own endpoint now has a page of its own — the headers, the four event types, signature verification and retries are in the webhook docs.
Good to know
- How much you can store depends on your plan — see pricing.
- Calls are capped per hour and per day, and a
429carriesRetry-After. The caps sit well above ordinary scripted use; for a bulk import of one long document, upload it from your dashboard instead — that is counted per file, not per part. - Something unclear or broken? Get in touch — a real person reads every message.