Slicelytics monogram

Compare Oura API Responses

You build on Oura's API v2. A webhook says a sleep was updated, you fetch the document again, and half its numbers are different from the ones you stored. Slicelytics puts the two versions side by side and lists every field that changed, so you can see what the update did before you decide what your code should do with it. The documents stay on your computer.

See What an Update Changed

Slicelytics diff of two versions of an Oura sleep document: sleep_analysis_reason went from foreground_sleep_analysis to bedtime_edit, bedtime_start from 22:51:30 to 23:26:30, time_in_bed from 29100 to 27000 and latency from 2610 to 510

The sample is one night's sleep document from GET /v2/usercollection/sleep/{document_id}, fetched twice: after the create webhook, and after the update webhook that followed it. In between, the member opened the Oura app and moved their bedtime 35 minutes later, because they had been reading in bed. The sample opens in Compare with a view of the fields the edit touched. In the Diff view:

  1. sleep_analysis_reason went from foreground_sleep_analysis to bedtime_edit. Oura's reference describes the field as the reason the latest version of the sleep was created or updated, so it tells you what kind of change to expect.
  2. bedtime_start moved from 22:51:30 to 23:26:30, while bedtime_end stayed at 06:56:30. time_in_bed fell from 29100 to 27000 seconds, and awake_time from 3420 to 1320.
  3. latency, the time it took to fall asleep, fell from 2610 to 510 seconds, and efficiency rose from 88 to 95. total_sleep_duration is 25680 in both: the edit removed time awake, not sleep.
  4. heart_rate.timestamp and hrv.timestamp moved with the bedtime, and restless_periods went from 165 to 147.

Click Reset filters to see the whole document. The sample arrays now start 35 minutes later, so heart_rate.items, hrv.items and sleep_phase_5_min lose their first seven 5-minute values and movement_30_sec its first 70 characters. Compare matches array items by position, so every item after the cut shows as changed, even where the reading is the same.

Both documents are synthetic, written to the sleep model in Oura's API v2 reference. The sample opens without an account. To open your own documents with the same view, type its paths into Filters, or give it to an agent:

{
  "v": 1,
  "view": "compare",
  "props": [
    "bedtime_start",
    "bedtime_end",
    "time_in_bed",
    "awake_time",
    "latency",
    "efficiency",
    "total_sleep_duration",
    "restless_periods",
    "sleep_analysis_reason",
    "heart_rate.timestamp",
    "hrv.timestamp"
  ]
}

Save Each Version When the Webhook Arrives

Oura's webhooks don't carry the data. You subscribe once for each pair of data_type and event_type, e.g. sleep and update, and each notification names what changed: its event_type, data_type, object_id, event_time and user_id. You then fetch the document by its object_id:

curl -s -H "Authorization: Bearer $TOKEN" \
  "https://api.ouraring.com/v2/usercollection/sleep/$OBJECT_ID" \
  > "sleep-$OBJECT_ID-$(date +%H%M%S).json"

Keep every version under its own name instead of overwriting the last one, and you can open any two of them later. Oura says notifications arrive about 30 seconds after data syncs from the app, and that sleep and readiness sync only when the member opens the app, so a night can come in as a create in the morning and an update whenever they next open it.

Subscriptions expire: each has an expiration_time, and PUT /v2/webhook/subscription/renew/{id} renews it. Oura retries a failed delivery for about an hour, then gives up, so a nightly fetch of the last few days catches what a missed notification would have told you. Comparing that fetch with what you stored shows whether anything slipped through.

What Else Changes in a Sleep Document

Look at these fields when two versions of the same sleep differ:

  1. Did type change?

    Oura's reference lists long_sleep, sleep, late_nap, rest and deleted. A nap the member rejects in the app's confirm prompt is rest, and a sleep they delete is deleted, so a record you counted can stop counting without disappearing.

  2. What does sleep_analysis_reason say?

    The reference lists foreground_sleep_analysis, background_sleep_analysis, background_created_foreground_updated and bedtime_edit. Comparing two versions shows you which fields each kind of update touches in your data.

  3. Which sleep_algorithm_version scored it?

    v1 is Oura's original sleep staging and v2 the latest. When stage durations differ between two members, or between an old and a new document, check this first.

  4. Did the day's scores move?

    Scores live in their own documents: daily_sleep and daily_readiness, each with a score and contributors such as efficiency, latency and timing. After a sleep changes, fetch the day's documents again and compare them the same way.

Compare a Range of Documents

The collection endpoints, such as /v2/usercollection/sleep with start_date and end_date, return a data list and a next_token. Compare matches list items by position, so fetch the same range both times; a sleep added or deleted in between shifts every document after it. The wearable API guide covers ranges, pagination and filtering a response by value.

If you receive Oura data through an aggregator rather than from Oura, see Compare Terra Webhooks: the payloads are Terra's normalised ones, and they come with their own update rules.

Before You Connect Users

API v2 is Oura's only API; v1 has been sunset. Access is through OAuth2, since personal access tokens were deprecated in December 2025. A new API application is limited to 10 users until Oura approves it, and each member consents to each data type separately. A 403 can mean a missing permission or that the member's Oura subscription has expired.

For the same kind of check on other APIs, see Compare WHOOP API Responses and Compare Garmin Health API Data.

Common Questions

Why does every heart rate sample show as changed?

Compare matches array items by position. When bedtime_start moves, heart_rate.items and hrv.items start at a different time, so item 9 of one version is not the same 5 minutes as item 9 of the other. Compare the scalar fields and the timestamp of each sample object, as the sample view does.

Does the webhook carry the new values?

No. An Oura notification has the event_type, data_type, object_id, event_time and user_id, and you fetch the document by its object_id. To compare versions, save each fetched document; the notification alone doesn't tell you what changed.

Can I compare daily_sleep or daily_readiness documents?

Yes. Any JSON response opens: a single document, or a collection response with its data list. Two versions of the same day line up field by field, contributors included.

Which other formats open?

JSON, NDJSON/JSONL, CSV, TSV, TOON and FIT files, plain or compressed as .gz or .zst. If you log every document you fetch as a line of an NDJSON file, see View JSONL Logs.

Does the data leave my computer?

No. Slicelytics reads the files in your browser and uploads nothing. If you share a comparison, the data travels in the part of the link after #, which browsers never send to a server, so treat the link like the data. Check Oura's API terms before you share a member's data with anyone.