HTTP API
Every endpoint under /api, read out of the route files themselves — the
methods they export, and the scope each one demands taken from its own
authenticateApi call. A handler that changes the scope it requires
changes this page on the next build.
Scopes
A token holds some of these and nothing else. Each description is the sentence somebody agrees to when they grant it.
| Scope | What it permits |
|---|---|
streams:write |
Send readings into your data streams, and create new streams |
streams:read |
Read everything your data streams have ever recorded |
schedule:read |
Read everything on your calendar for the days ahead |
today:read |
See today's blocks, habits and tasks — what the phone widget shows |
plugin:declare |
Name and describe itself on your integrations page |
webhooks:manage |
Ask to be told when things happen — and manage those subscriptions |
shopping:read |
See everything on your shopping list |
shopping:write |
Add to your shopping list, and tick things bought |
calendar:read |
Show your plan in a calendar app. It can see the plan and change nothing |
Endpoints
| Endpoint | Method | Scope |
|---|---|---|
/api/billing/paddle |
POST | — |
/api/capture-options |
GET | — |
/api/client-errors |
POST | — |
/api/reminders |
GET | — |
/api/reminders |
POST | — |
/api/search |
GET | — |
/api/v1/me |
GET | — |
/api/v1/plugin |
GET | plugin:declare |
/api/v1/plugin |
PUT | plugin:declare |
/api/v1/plugin |
DELETE | plugin:declare |
/api/v1/schedule/upcoming |
GET | schedule:read |
/api/v1/shopping |
GET | shopping:read |
/api/v1/shopping/items |
POST | shopping:write |
/api/v1/shopping/items/[id]/bought |
POST | shopping:write |
/api/v1/streams |
GET | streams:read |
/api/v1/streams |
POST | streams:write |
/api/v1/streams/[slug]/points |
POST | streams:write |
/api/v1/streams/[slug]/points |
GET | streams:read |
/api/v1/streams/[slug]/points/[externalId] |
DELETE | streams:write |
/api/v1/today |
GET | today:read |
/api/v1/webhooks |
GET | webhooks:manage |
/api/v1/webhooks |
POST | webhooks:manage |
/api/v1/webhooks/[id] |
DELETE | webhooks:manage |
Webhook events
What a subscription can ask to be told about. Managed through
/settings/integrations or the webhooks:manage scope.
| Event | Fires when |
|---|---|
todo.created |
a todo is added |
todo.completed |
a todo is finished |
idea.created |
an idea is captured |
diary.created |
a diary entry is written |
shopping.added |
something goes on the shopping list |
shopping.bought |
something on the list is bought |
/api/billing/paddle
Where Paddle tells us what happened.
Unauthenticated by design — the signature is the authentication. The raw body is read as text and hashed before anything parses it, because re-serialising JSON changes bytes and the comparison would fail for a reason nobody could see.
Always answers 200 once the signature checks out, including for events it ignores: a provider that gets an error retries, and retrying something we deliberately did nothing with is noise for both sides.
POST
/api/capture-options
The choices the capture dialogs offer, fetched when one opens.
Capture lives in the app shell, which is on every page — so putting these into the layout's own load would run three queries on every request to serve a dialog most visits never open. Asked for once, the first time one is opened, and held for the rest of the session.
GET
/api/client-errors
Where the page sends what broke, and the answer it was given.
Session-authenticated like /api/search — this exists for the page that is
already open, not for a plugin. One body, two shapes: { decision } records
the person's yes or no, { error } is a report, accepted only after a yes —
or with once, which is the error page's own button and speaks for that one
report only.
POST
/api/reminders
What should have gone off by now.
Polled by the page that is already open, so it is session-authenticated like
/api/search rather than token-based like /api/v1. The reads and the
"I have shown this" are separate calls on purpose: a reminder is only marked
delivered once something has actually put it in front of somebody, so a
failed request loses nothing.
GET
POST
/api/search
Search, for the command palette.
Session-authenticated like the rest of the app rather than token-based like
/api/v1 — this exists for the page that is already open, not for a plugin.
GET
/api/v1/me
Token introspection — lets a producer verify its credentials and discover the user's timezone during setup, without needing any other scope.
GET
/api/v1/plugin
The manifests this account has been given.
GET — requires plugin:declare
PUT — requires plugin:declare
Declare what this plugin understands. Idempotent per source, so a producer can call it at every startup and the newest version's vocabulary wins.
DELETE — requires plugin:declare
Withdraw a manifest. The metadata keys keep working; they just lose their label.
/api/v1/schedule/upcoming
Upcoming scheduled occurrences, so an external app can act on the plan.
This is what massalarme reads to set alarms from planner slots. Ontoplano
reports what is scheduled; deciding which occurrences deserve an alarm —
and what kind — is the consumer's business, matched on title, category
or label.
GET — requires schedule:read
/api/v1/shopping
The whole list, bought and waiting alike — the reader decides what matters.
GET — requires shopping:read
/api/v1/shopping/items
Put something on the list.
The same semantics as typing it in the app: a name already held is put back
on the list rather than duplicated, and the response says which happened.
category is a name, created if new — a producer should not need a second
request to find out what number "Dairy" is.
POST — requires shopping:write
/api/v1/shopping/items/[id]/bought
State, not a toggle: { "bought": true } twice means bought, not un-bought.
A plugin mirroring two lists needs to be able to repeat itself.
POST — requires shopping:write
/api/v1/streams
List the caller's streams.
GET — requires streams:read
POST — requires streams:write
Declare a stream. Idempotent per slug — safe to call at every producer startup.
/api/v1/streams/[slug]/points
Push points. Batch, idempotent, partial success.
Re-sending an already-stored point is reported under duplicates and is not
an error — producers should treat it as success and mark the reading synced.
POST — requires streams:write
GET — requires streams:read
Read points back, for reconciliation or charting.
/api/v1/streams/[slug]/points/[externalId]
Delete a single point by its producer-supplied id.
DELETE — requires streams:write
/api/v1/today
Today's blocks, habits and tasks, for the home-screen widget.
Separate from /schedule/upcoming, which answers "what is coming" for an
alarm consumer. This answers "what does today look like", which is a
different question and a different scope: a widget on a lock screen should
not carry a token that can also read a week ahead.
GET — requires today:read
/api/v1/webhooks
List the caller's webhook subscriptions.
GET — requires webhooks:manage
POST — requires webhooks:manage
Subscribe an address to events.
The response carries the secret deliveries are signed with — keep it, and
verify X-Ontoplano-Signature (sha256= + HMAC-SHA256 of the raw body).
/api/v1/webhooks/[id]
DELETE — requires webhooks:manage