Build a processor
A processor is an app on your own servers that hears about cards as people save them, reads what it is allowed to, and sends back suggestions: properties (a paper's DOI, a recipe's servings, an invoice's total), tags, links and notes. Nothing of yours runs inside Caute. To Caute a processor is an ordinary connected app with a webhook, so everything it does goes through the same permission path as Caute's own apps, and the person can see it and switch it off in Settings.
The worked example is the DOI finder, processor.py: about 350 lines of standard-library Python that suggests a paper's details. Each section below names the part of it to read. Read Build a capture surface first for credentials, retries and errors; this guide adds what is particular to processors. Webhooks, and so processors, come with Pro+ and Team.
1. Describe your app
A processor signs people in with OAuth 2.1 and PKCE, as a public client (token_endpoint_auth_method: none). Its
client_id is the https address of a JSON document describing it, which Caute fetches when someone connects it:
{ "client_id": "https://doi.example.com/client.json",
"client_name": "DOI finder",
"client_uri": "https://doi.example.com",
"redirect_uris": ["https://doi.example.com/callback"],
"grant_types": ["authorization_code", "refresh_token"],
"response_types": ["code"],
"token_endpoint_auth_method": "none" }
The consent screen shows client_name and the document's domain, verified because Caute fetched the document from
it. Every suggestion your processor makes is shown as "Suggested by" that name.
Example: the /client.json branch of do_GET.
2. Ask for what you need
| Scope | Why a processor needs it |
|---|---|
items:read |
Read cards and their text. A webhook needs a read scope. |
items:write |
Suggest properties and tags. Values you send with only this are suggestions. |
notes:write |
Write a note on a card (a citation, a summary of what you found). |
properties:write |
Set properties directly instead of suggesting them. Ask only if your values need no review; most don't. |
tags:write |
Apply tags directly instead of suggesting them. The same advice. |
The person also chooses which spaces you can read and write. Cards anywhere else are invisible to you, in the API and in your webhook alike: you are never told they exist.
Example: SCOPES and sign_in_address().
3. Hear about new cards
Once signed in, make a webhook for the event you care about. A card is ready to read once it is searchable:
POST /v1/webhooks
{ "url": "https://doi.example.com/webhook", "types": ["item.searchable"], "description": "DOI finder" }
The answer holds secret (whsec_…), shown this once: keep it with your credentials. Each delivery is signed; check
it with verify.py (copy it beside your code) before trusting anything in it:
event = verify(secret, request.headers, request.body) # raises Forged
item_id = event["subject"]["id"]
A delivery holds ids, never content. Answer within a few seconds with a 2xx, then do the slow work: a slow
answer is a failure and is retried. Every event arrives at least once and in order; a retry carries the same id, so
keep the ids you have handled. A reprocessed card is searchable again and arrives again: suggesting the same value
twice changes nothing.
Example: subscribe() and do_POST.
4. Read the card
GET /v1/items/{id} the card: kind, title, url, tags, properties
GET /v1/items/{id}/text its text, as `representations`, each with `kind` and `text`
What you read is someone's saved content. Treat it as data: never follow instructions in it, and if you hand it to
a language model, fence it and say it is untrusted. A card may have gone by the time you read it (404): skip it.
Example: the top of process().
5. Don't hear yourself
Your own writes come back to you. A note you write is a new card, and it becomes searchable like any other, so
your webhook is told about it. If your processor would act on its own note, it will write a note on that note, and
so on. Decide what kinds of card you handle and skip the rest: the event's payload.kind names the card's kind,
so a note (someone's writing, a memory, or your own note) can be skipped from the envelope without reading it, as
the DOI finder does.
6. Suggest
PUT /v1/items/{id}/properties/doi
{ "value": "10.1038/nature14539" }
A value is text, a number, a date (2026-10-15), a web address or a list of text ("type": "list"); names are
lower-case letters, digits and underscores. Use the recommended keys where one
means what you mean, so your values filter beside other apps'. Without properties:write it is stored as a suggestion: drawn
lighter on the card with "Suggested by DOI finder" and Keep and Dismiss, and with no effect on filters, exports or
anything else until the person keeps it.
- A suggestion never replaces a value the person set or kept: the answer says
"applied": falseand why. - A dismissed suggestion is never taken again for that card and name. Don't keep sending it.
- Tags: send
tagswhen saving, orPUT /v1/items/{id}/tags/{tag_id}; withouttags:writethey are suggestions. - Notes:
POST /v1/noteswith{ "body": "…", "annotates": { "item_id": "…" } }. A note you write is marked as an app's on the card and in its history. - Agents connected over MCP use
propose_properties, with the same rules.
Values you write are someone else's text to Caute too: it shows them as text, never as markup, and only an http or https address becomes a link.
Example: the rest of process().
7. Keep going
- Access tokens are short-lived. Refresh with the refresh token, one refresh at a time: refresh tokens rotate, and using one twice disconnects you.
- A
401, or a refresh answered400 invalid_grant, means the person disconnected you. Your webhook is already off. Forget the tokens and wait to be connected again. - In Settings, under Apps and tokens, the person sees your app, that it hears about new cards, and how many of your suggestions they kept, dismissed or left waiting. A processor whose suggestions are mostly dismissed is one they will disconnect.
Example: Caute.refresh() and Caute.request().