Build with TimePeace

DEVELOPERS
One door in. Your app feeds a person's diary — one-way, with their permission — and can remove only what it added.
In developer preview. The inbound API is built and documented here; access opens shortly. Apply below and we'll be in touch when the programme goes live. Nothing on this page is live yet.
The inbound data API

Let the things people do in your app show up in their TimePeace — without anyone typing.

A meditation app, a physio's booking system, a habit tracker, a school's timetable — each can push a person's activity straight into their TimePeace diary, mapped onto the diary types they already use, visibly attributed to you. You build to one endpoint; every person who connects your app makes both products more useful.

And because a person's real data is end-to-end encrypted and unreadable to our servers, your integration lives entirely outside that boundary — which is exactly why it can only ever touch what it put there.

Two rules that shape everything

Rule 1 · one-way

You write in. You never read back out.

The API is a write-in rail. It never returns a person's diary data to you — not yours, not anyone's. There is no read-a-user endpoint, by design.

Rule 2 · reversible

You can remove only what you added.

Every item you push is stamped with your app's id and your own id for it. A delete can only ever tombstone your own rows — never another app's, and never a person's hand-typed entries.

How a connection works

  1. You apply once. We review your app and approve it (manually, in this first version). On approval you get a secret API key, shown once — that key identifies your app to us.
  2. A person connects you. They open a consent card in TimePeace — "[Your app] wants to add your sessions to your diary. It can add these; it can't read anything. Allow?" — and approve. That mints a grant token tied to that one person and the scopes they allowed.
  3. You push their activity. With your app key and that person's grant token, you push items to the endpoint. They surface in the person's diary automatically, attributed to your app. Delete an item and it disappears from their diary on their next sync.

Two independent keys are required on every request: your app key proves you're a trusted app; the grant token proves this particular person let you in. A valid app key with no grant can address nobody.

API reference

Endpoint

One endpoint, POST only. The action is named in the JSON body.

POST https://lhcsqadnkjqbonhmvqcn.supabase.co/functions/v1/inbound-ingest

Authentication

Send three headers. Your app key and the person's grant token are your credentials; the apikey is our public project key (safe to embed — it identifies the project, not you).

apikey: sb_publishable_s7EFgzZ3PVzjr8sA73GJvA
Authorization: Bearer tp_live_your_app_key
x-tp-grant: the_persons_grant_token
Content-Type: application/json

Both credentials are checked on every call. A missing or wrong key returns 401 unauthorized; a suspended app or a revoked grant is treated the same way — no detail is leaked about which failed.

Actions

ActionDoes
pushStage one or many items into the person's diary (idempotent).
deleteRemove items you previously added, by your own external id.
list_mineList the items you have staged for this person, so you can reconcile. Returns only your own rows — never the person's data.
statusHealth check: confirms your keys work and echoes the scopes the person granted you.

push

Send up to 100 items. Each item carries your own external_id (used for idempotency and for later deletion), a kind, an optional occurred_at (ISO 8601), and a payload whose fields must match that kind.

{
  "action": "push",
  "items": [
    {
      "external_id": "session-8412",
      "kind": "session",
      "occurred_at": "2026-07-02T07:30:00Z",
      "payload": { "title": "Morning meditation", "duration_min": 15, "category": "mindfulness" }
    }
  ]
}

Response — every item reports its own result, and nothing is dropped silently:

{ "ok": true, "action": "push", "staged": 1, "failed": 0,
  "results": [ { "external_id": "session-8412", "ok": true, "result": "created" } ] }

A repeat push of the same external_id updates the item ("result":"updated") rather than duplicating it. Send an idempotency_key on an item and an exact retry is a no-op ("result":"duplicate"). If the person has deleted an item themselves, re-pushing it will not resurrect it ("result":"dismissed").

delete

Name one or many of your own external_ids (up to 200). Only your rows are affected.

{ "action": "delete", "external_id": [ "session-8412", "session-8399" ] }
{ "ok": true, "action": "delete", "tombstoned": [ "session-8412", "session-8399" ] }

list_mine

{ "action": "list_mine", "status": "pending", "limit": 200 }

Returns your staged items with their status (pending · merged · tombstoned) and whether the person dismissed each — enough to reconcile your side, and nothing of theirs.

status

{ "action": "status" }
{ "ok": true, "action": "status", "app": "your-slug", "live": true, "scopes": ["health"] }

The kind catalogue

Each kind maps to a diary type TimePeace already understands, so imported data renders natively with no new interface. A payload may carry the common fieldstitle, name, note, duration_min — plus that kind's own fields below. Any unknown field is rejected, so you always know exactly what was accepted.

kindBecomesScopePayload fields
workouta workouthealthsport distance_km calories heart_rate_avg steps
sleepa sleep recordhealthstart end quality stages
meala mealbaselinecalories items meal_type
activitya hobby / activitybaselinecategory
sessiona hobby / activitybaselinecategory
appointmentan appointmentbaselinelocation with start end
bookingan appointmentbaselinelocation with start end reference
milestonea milestonebaselinevalue unit
study_sessiona study sessionbaselinesubject course
eventa general eventbaselinecategory location

A health kind is accepted only when the person granted your app the health scope and holds their own health permission — belt and braces. baseline kinds need only an active grant.

Limits, idempotency & errors

Limits

Up to 100 items per push, 200 ids per delete, an 8 KB payload ceiling per item, and an occurred_at that may lead now by at most a day and trail it by at most about five years (no backdated floods). Rate limits apply per app and per (app, person); over the limit you get 429 with a Retry-After header.

Idempotency

Items are keyed by (your app, the person, your external_id). Re-sending the same external id updates in place; it never duplicates. If a person's workout also arrives through their own health connection, TimePeace de-duplicates so it isn't counted twice.

Error codes

HTTPMeaning
401Missing or invalid app key or grant token (or the app is suspended / grant revoked).
400Bad request — no_items, no_ids, or bad_action.
413too_many_items / too_many_ids — you exceeded a batch cap.
422Every item in a push failed validation (see per-item errors).
429Rate limited — retry after the Retry-After seconds.
503not_enabled — the rail is not yet live (the preview state of this page).

Per-item push errors include unknown_kind, scope_denied, unknown_field:<name>, payload_too_large, bad_occurred_at, and occurred_at_out_of_range. Each is returned against its own external_id so a bad item never sinks a good batch.

Why "remove only what you added" is a guarantee, not a promise

A person's real diary is end-to-end encrypted. Our servers hold only ciphertext — they cannot read or write a single diary entry. So your integration never touches that store at all. Instead it writes to a small, provenance-stamped staging lane, and the person's own device (the only thing holding the key) drains it into their encrypted diary, tagging each item as from your app.

That is why the reversibility rule is structural. Your delete can only address rows stamped with your app id. A person's hand-typed entries have no id you could name and no key you could hold — they are simply unreachable from where your integration lives. If a person edits an item you imported, it detaches from you and your later delete leaves their version untouched. Their hand always wins.

For the person, that means connecting your app is safe in a way "give an app access to my data" usually isn't: you can add, you can tidy up after yourself, and you can never see or delete the rest of their life.

Apply to build

Access is approved by hand in this first version, the same way we run our partner programme — a small, deliberate start. Tell us about your app and what you'd feed in; we'll review and, on approval, issue your API key (shown once) so you can start pushing.

Tell us about your app

We review every app before issuing a key. Approval isn't automatic.
These map to the kinds above. Anything needing health data (workouts, sleep) is granted per person, with their explicit permission.
We review your app and homepage, then reply by email with next steps. Your API key is issued once, on approval.
Application prepared

Thanks — your application is in

We've received your application. We review your app and homepage, then reply by email with next steps.

1We review your app, your homepage and what you'd feed in.
2We approve selected apps — it isn't automatic — and reply by email.
3On approval we issue your API key, shown once. You start pushing, behind each person's permission.