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