Version 1 · last updated 27 October 2026
An access key lets another app use your household's plants without signing in as you. Make one in the app under Settings > Access keys. There are three kinds:
A key is shown once when you make it. Keep it secret like a password, make one per app, and revoke it in Settings when you no longer need it. Only a household member can make or revoke keys, and a key only ever reaches its own household's data. Settings also shows when each key was last used and a record of what keys did.
Make a key of the kind Calendar link. The app shows the full address, which looks like this:
https://guglanruymczrxailwny.supabase.co/functions/v1/api/v1/calendar.ics?key=pj_…
In your calendar app choose “subscribe to a calendar” (or “add by URL”) and paste it. Each plant's next watering, feeding, misting and repotting appears on its due day (overdue ones sit on today), and so does each open reminder, with its note. When you mark something done in Plant Journal the event moves on the next refresh; calendar apps usually refresh every few hours. A calendar link may be fetched 30 times an hour.
Send the key in the Authorization header. The base address is:
https://guglanruymczrxailwny.supabase.co/functions/v1/api/v1
curl -H "Authorization: Bearer pj_…" \
"https://guglanruymczrxailwny.supabase.co/functions/v1/api/v1/due?days=3"
(Only calendar keys may be sent in the web address, because calendar apps cannot send headers.) Answers are JSON. Dates are YYYY-MM-DD in Helsinki time, the same day the app and the daily email use. Version 1 will not change in ways that break programs: fields may be added, never renamed or removed.
GET /plants — your plants (?include_archived=1 adds archived ones). Each has id, name, genus, species, light, height_cm, the care intervals and last-done dates, cutting_since, photo_url, notes, and next_due (the next due date per kind of task).
GET /due — what is due today or overdue, with ?days=N (0 to 14) to look ahead. Gives tasks (plant_id, plant, task as water, fert, mist or repot, due, overdue_days) and open reminders. It follows the same rules as the app: a cutting is watered every 2 days and not fed or misted, snoozed tasks return on their day, and task kinds the owner switched off, never-logged tasks and archived plants are left out.
GET /reminders — all open reminders with their notes.
GET /history — completed tasks, newest first (?days=30, up to 90; at most 500).
GET /calendar.ics — the calendar file described above (any read or calendar key).
Needs a key of the kind Read and mark done.
POST /tasks/done with a JSON body:
{"plant_id": "p_123", "task": "water"}
It works like the app's “Mark done”: it records today as the last time, clears a snooze, and adds a line to the care history (with how late it was). Repotting also writes the pot history and, for a cutting, moves the cutting stage and its check-in reminder. The same call twice on one day changes nothing the second time (the answer says "already_done_today": true), so a program that retries cannot log it twice. Archived plants, a cutting’s feeding and misting, tasks the plant has no schedule for and task kinds the owner switched off are refused with 409.
POST /reminders/done with {"id": "r_123"} marks a reminder done.
Instead of asking again and again, an app can be told. On a key of the kind Read only or Read and mark done (not a calendar link), choose Add a webhook in Settings > Access keys: give an https address and the events you want. A key has at most one webhook, and a household at most 3. The page shows a signing secret (whsec_…) once.
tasks.due — every morning (around 07:20 to 08:20 Helsinki time): what is due today or overdue, only when there is something.task.done — a care task was marked done, in the app or through a key.reminder.done — a reminder was marked done.ping — sent by the “Send test” button.Each message is a POST with a JSON body:
{"id": "…", "event": "task.done", "created_at": "2026-10-27T08:01:02Z",
"data": {"plant_id": "p_123", "plant": "Monstera", "task": "water", "days_late": 1, "completed_at": "…"}}
The data of tasks.due has date, tasks (plant_id, plant, task, due, overdue_days) and reminders. The headers are X-PlantJournal-Event, User-Agent: PlantJournal-Webhook/1 and X-PlantJournal-Signature: t=<unix time>,v1=<signature>. The signature is the hex HMAC-SHA256, with your signing secret as the key, of the text <t>.<raw body>. Check it, and reject a t that is more than a few minutes old:
const sig = crypto.createHmac('sha256', secret).update(t + '.' + rawBody).digest('hex');
const ok = crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(v1));
Answer with any 2xx status within 5 seconds. Otherwise the message is tried again after 1 minute and after 5 more minutes (the same message, a new signature time), so make your handler safe to run twice (the id is the same). After 10 failed messages in a row the webhook is paused until you remove it and add it again. At most 500 messages a day per webhook. The address must be https on a public website name (no IP numbers, no port, no password); what your server answers is not stored. Settings shows the latest messages and whether they arrived.
| What | Limit |
|---|---|
| One key | 60 calls a minute and 5000 a day |
| A calendar link | 30 fetches an hour and 200 a day |
| Changes through keys, per household | 500 a day |
| Wrong keys from one address | 20 a minute, then that address waits a minute |
| Keys per household | 5 working keys, 10 new keys a day |
Over a limit the answer is 429 with a Retry-After header (seconds).
Errors look like {"error": {"code": "invalid_key", "message": "…"}}. The codes: invalid_key (401: missing, wrong or revoked key), wrong_key_kind and key_in_url (403), not_found / plant_not_found / reminder_not_found (404), bad_request / bad_json (400), plant_archived, not_during_cutting, task_not_in_use, task_switched_off (409), rate_limited, too_many_failures, household_write_limit (429), api_off (503: the owner switched access keys off), api_admin_only (403: access keys are limited to the owner for now).
Every change made through a key, and every refused or slowed-down call, is recorded with the key's name for 30 days. The household sees it in Settings. Plain reads are not recorded; the key shows when it was last used. The privacy policy has the details.
See also the privacy policy and the terms of service.