Guide · 4 min read

Oura, sleep, and a memory that knows your mornings

Use Oura's personal access token to pull your nightly readiness and sleep summary into Petals — one calm line of context, every morning.

2026-06-11

Before you start

Petals does not have a native Oura connector. There is no one-click sync, no Composio Oura toolkit, and no automated bridge. What exists is a stable metrics ingestion endpoint and Oura's own well-documented REST API. This guide shows you how to wire them together with a small daily script — a pattern to adapt, not a finished product.

Create an API key at Settings → API keys in the Petals app. Every key starts with petals-. Set it alongside your Oura token before running the script:

bash
export OURA_TOKEN="your-oura-personal-access-token" export PETALS_API_KEY="petals-yourkey..."

What this gives you

Your ring measures roughly a dozen things every night: total sleep duration, REM and deep sleep splits, sleep efficiency, resting heart rate, HRV, respiratory rate, body temperature deviation, and the readiness score that synthesizes it all into a single number. Most of that data lives in Oura's app and nowhere else.

Petals can hold it. Once a day, a script pulls yesterday's summary and pushes a compact text observation — "Slept 7h12, readiness 82, HRV 54ms" — into the metrics layer. Your assistant can then answer questions like "how did I sleep this week" or notice that a stressful run of days correlates with a readiness dip, without you ever thinking to mention it.

Oura's API

Oura offers a personal access token under cloud.ouraring.com/personal-access-tokens. No OAuth app registration, no redirect URI — just generate a token and use it. The endpoints you need:

  • GET https://api.ouraring.com/v2/usercollection/sleep — nightly sleep sessions with duration, efficiency, and stage breakdowns
  • GET https://api.ouraring.com/v2/usercollection/daily_readiness — readiness score, contributors, and HRV balance
  • GET https://api.ouraring.com/v2/usercollection/daily_sleep — the high-level daily summary (sleep score, total sleep time)

All three accept start_date and end_date query parameters in YYYY-MM-DD format and return JSON with a data array.

The Petals metrics endpoint

The research for this guide found that Oura data maps naturally to Petals' metrics system rather than the document endpoint. The relevant endpoint:

text
POST /api/memory/metrics/observations

A single observation looks like this (verbatim schema from the ingestion API):

json
{ "metric": { "slug": "oura-readiness", "label": "Oura readiness score", "unit": "score", "aggregationHint": "avg" }, "value": 82, "occurredAt": "2026-06-10T00:00:00Z" }

You can send multiple observations in one request using /api/memory/metrics/observations/bulk, which accepts an array in the same shape. The processing is async — the endpoint returns a jobId and the graph updates in the background.

The daily script

The script below runs once each morning, fetches yesterday's Oura data, and pushes three observations (sleep duration in minutes, readiness score, and HRV). It is written in Node with no dependencies beyond the standard fetch available in Node 18+.

js
// oura-to-petals.js // Run daily: node oura-to-petals.js // // Create your Petals API key at Settings → API keys. // Set OURA_TOKEN and PETALS_API_KEY in your environment. const OURA_TOKEN = process.env.OURA_TOKEN; const PETALS_API_KEY = process.env.PETALS_API_KEY; const PETALS_HOST = process.env.PETALS_HOST ?? "https://petals.chat"; if (!OURA_TOKEN || !PETALS_API_KEY) { console.error("Set OURA_TOKEN and PETALS_API_KEY"); process.exit(1); } function yesterday() { const d = new Date(); d.setDate(d.getDate() - 1); return d.toISOString().slice(0, 10); // YYYY-MM-DD } async function ouraFetch(path, date) { const url = `https://api.ouraring.com/v2/usercollection/${path}?start_date=${date}&end_date=${date}`; const res = await fetch(url, { headers: { Authorization: `Bearer ${OURA_TOKEN}` }, }); if (!res.ok) throw new Error(`Oura ${path} failed: ${res.status}`); const json = await res.json(); return json.data?.[0] ?? null; } async function pushObservations(observations) { const res = await fetch( `${PETALS_HOST}/api/memory/metrics/observations/bulk`, { method: "POST", headers: { "Content-Type": "application/json", "x-api-key": PETALS_API_KEY, }, body: JSON.stringify(observations), }, ); if (!res.ok) throw new Error(`Petals push failed: ${res.status}`); return res.json(); } async function run() { const date = yesterday(); const occurredAt = `${date}T00:00:00Z`; const [readiness, sleep] = await Promise.all([ ouraFetch("daily_readiness", date), ouraFetch("daily_sleep", date), ]); const observations = []; if (readiness) { observations.push({ metric: { slug: "oura-readiness", label: "Oura readiness score", unit: "score", aggregationHint: "avg", }, value: readiness.score, occurredAt, }); if (readiness.contributors?.hrv_balance != null) { observations.push({ metric: { slug: "oura-hrv", label: "Oura HRV balance", unit: "score", aggregationHint: "avg", }, value: readiness.contributors.hrv_balance, occurredAt, }); } } if (sleep?.total_sleep_duration != null) { observations.push({ metric: { slug: "oura-sleep-minutes", label: "Oura sleep duration", unit: "minutes", aggregationHint: "avg", }, value: Math.round(sleep.total_sleep_duration / 60), occurredAt, }); } if (observations.length === 0) { console.log(`No Oura data for ${date} — nothing pushed.`); return; } const result = await pushObservations(observations); const sleepMin = observations.find( (o) => o.metric.slug === "oura-sleep-minutes", )?.value; const readinessScore = observations.find( (o) => o.metric.slug === "oura-readiness", )?.value; const h = sleepMin ? `${Math.floor(sleepMin / 60)}h${sleepMin % 60}` : "?"; console.log( `Pushed ${observations.length} observations for ${date}. ` + `Slept ${h}, readiness ${readinessScore ?? "?"}.`, result, ); } run().catch((err) => { console.error(err.message); process.exit(1); });

Running it daily

Save the script, set your environment variables, and schedule it with your system's cron (or any scheduler you already trust):

text
# run at 8 AM every day 0 8 * * * OURA_TOKEN=... PETALS_API_KEY=... node /path/to/oura-to-petals.js

The first run creates the metric definitions in Petals automatically — slug values act as stable identifiers, so subsequent runs update the same series rather than creating duplicates.

What your assistant sees

Once a few days of observations are in, your assistants can reference your sleep and readiness data naturally. You do not need to tell them where it came from. The metrics layer surfaces in daily digests as "movers" — values that shifted meaningfully — which means a rough night shows up without you having to mention it.

The pattern here is a simple one: Oura's API knows your body; Petals holds the context. A script is the bridge.