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

  1. 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.
  2. The key is shown once, right after you create it. Copy it then; it can't be displayed again.
  3. Create as many keys as you like and delete any of them at any time.

Authentication

Base URLhttps://contextick.ai
Authorization: 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

FieldTypeRequiredLimit
titlestring120 characters — longer is trimmed
summarystring300 characters — longer is trimmed
contentstring16,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

ParameterTypeRequiredNotes
cursorintegerWhere you left off — the cursor from the previous response.
sinceRFC 3339A point in time, e.g. 2026-09-01T13:00:00+09:00. The offset is required. First call only.
limitinteger1–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

FieldTypeRequiredLimit
idsnumber[]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.

400 Fix the request and retry empty_titleempty_summaryempty_contentcontent_too_longtoo_many_idsinvalid_bodyinvalid_idinvalid_query
401 Check the api_ key unauthorized
402 Settle the billing state — recall keeps working meanwhile payment_past_duesubscription_pausedsubscription_canceledrefund_pending
404 The id, group, or path isn't there not_foundendpoint_not_found
405 Use a method from the Allow header method_not_allowed
415 Send Content-Type: application/json unsupported_media_type
429 Retry the same request shortly rate_limitedembedding_rate_limited
507 Free up space or move to a larger plan — don't retry as-is quota_exceeded
503 500 Temporary or server-side — retry shortly

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