chore: regenerate OpenAPI spec

This commit is contained in:
Antiheld86 committed 2026-08-31 12:48:18 +00:00
1 parent 7c23b14053
commit a778acac4e
1 file changed
+179 -12
+179 -12
View File
@@ -2731,11 +2731,13 @@ paths:
Three things the name understates. The list is not limited to tokens a person minted from the settings surface:
a native login mints a wildcard access token as an `ApiToken` row, so a signed-in phone shows up here too. There
is no mint on this path any more — the generic POST that issued `["medication:ingest"]` was removed, because
that scope reached no ingest route while the pre-fail-closed default let it reach everything else; the working
credential comes from the per-medication API-endpoint toggle. And unlike the revoke, this read is NOT gated on
the operator's instance-wide API switch: that switch governs the surfaces a token is for, not a token's ability
to authenticate, so tokens stay live while it is off and their owner has to be able to see them.
is no GENERIC mint any more — the POST at this exact path issued `["medication:ingest"]` and was removed,
because that scope reached no ingest route while the pre-fail-closed default let it reach everything else. The
credentials that work are minted where they are scoped: the per-medication API-endpoint toggle, `POST
/api/mcp/tokens` for a connector, and the sibling `POST /api/tokens/measurements` for third-party measurement
ingest. And unlike the revoke, this read is NOT gated on the operator's instance-wide API switch: that switch
governs the surfaces a token is for, not a token's ability to authenticate, so tokens stay live while it is off
and their owner has to be able to see them.
responses:
"200":
description: The caller's tokens, newest first.
@@ -2746,6 +2748,86 @@ paths:
"401": *a1
"422": *a4
"429": *a5
/api/tokens/measurements:
post:
tags:
- Auth
summary: Mint a measurement-ingest token
description: >-
Mints a Bearer scoped to exactly `measurements:write` and audits the mint. THE RESPONSE CARRIES THE RAW TOKEN —
the only place it ever exists; it is stored as an HMAC and no path re-reveals it.
**What it can do.** `POST /api/measurements` and `POST /api/measurements/batch`, on its owner's own record,
attributing `source: MANUAL`. An entry naming `APPLE_HEALTH` is refused 422 rather than relabelled: that source
is half a dedup key the phone also writes into, it participates in the cross-source merge, and it is what
decides the Apple Health card may claim a sync happened. For the same reason a write through this credential
does not move the native client's sync checkpoint.
**What it cannot do.** Everything else, including the measurement reads on the same paths, the edit and delete
legs, and the export — a scope grants what it names and nothing adjacent. It cannot be pointed at a shared
record: a request carrying the account selector is refused 403 before any grant is read, whatever grants its
holder actually has. And it cannot mint another token, this endpoint included.
Minting requires a COOKIE SESSION. No Bearer credential reaches this endpoint at any scope, wildcard included —
not because a wildcard lacks the reach, but because of the lifetimes involved: a native access token lives a day
and what it could mint here lives a year, so admitting one would let a short-lived compromise leave behind a
credential that outlives revoking it. Gated by the operator's instance-wide API switch. Body capped at 16 KiB;
10 mints per user per minute. Tokens appear in `GET /api/tokens` and are revoked at `DELETE /api/tokens/{id}`
like any other.
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/CreateMeasurementTokenRequest"
responses:
"201":
description: Token minted. The `token` field is the only copy of the secret.
content:
application/json:
schema:
$ref: "#/components/schemas/CreateMeasurementTokenEnvelope"
"400":
description: Body is not parseable JSON.
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorEnvelope"
"401": *a1
"403":
description: The operator has switched the API off instance-wide (`AppSettings.apiGlobal`). Nothing was minted. A Bearer
caller does not reach this arm — it is refused 401 before the switch is read.
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorEnvelope"
"413":
description: Body exceeds 16 KiB.
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorEnvelope"
"415":
description: Content-Type is not `application/json`.
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorEnvelope"
"422":
description: Validation failed. The envelope carries every offending issue, not just the first.
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorEnvelope"
"429":
description: More than 10 mints from this account in a minute. Nothing was minted.
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorEnvelope"
/api/tokens/{id}:
delete:
tags:
@@ -3564,9 +3646,18 @@ paths:
tags:
- Measurements
summary: Create one measurement (or a small array)
description: Single ingest. The body may also be a bare ARRAY of the same objects — the mode the iOS client uses for a
description: >-
Single ingest. The body may also be a bare ARRAY of the same objects — the mode the iOS client uses for a
combined blood-pressure + pulse or dual-value glucose write — in which case `data` is the array of created rows
in request order. Use `/api/measurements/batch` for Apple Health upload streams.
Also reachable with a narrow `measurements:write` Bearer, which is the supported way to push readings from a
scale, a watch bridge or a home-automation rule. That credential is confined to its owner's own record — a
request carrying the account selector is refused 403 before any sharing grant is read, however the record is
actually shared — and to `MANUAL` attribution, so an explicit `APPLE_HEALTH` source is refused 422
(`measurement.create.source_not_permitted`). The GET on this same path, and the PUT and DELETE beside it, name
no scope and so refuse it.
parameters:
- *a6
requestBody:
@@ -3595,7 +3686,14 @@ paths:
application/json:
schema:
$ref: "#/components/schemas/ErrorEnvelope"
"422": *a4
"422":
description: Validation failed, or (`measurement.create.source_not_permitted`) a narrow `measurements:write` credential
named `APPLE_HEALTH` as the source. Refused rather than relabelled, so the caller can fix the payload
instead of discovering rows it did not ask for.
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorEnvelope"
"429": *a5
/api/measurements/{id}:
get:
@@ -3715,10 +3813,18 @@ paths:
tags:
- Measurements
summary: Apple Health batch ingest
description: Up to 500 HealthKit entries per call. Idempotent via the `Idempotency-Key` header (replay window 24h).
Per-entry status lets the iOS client advance its sync cursor accurately. v1.4.25 W8c adds an optional
`deviceType` per entry — feed it from `HKDevice.model` so the cross-source canonical picker can break
Apple-Watch-vs-iPhone ties; null/absent stays backward-compatible with v1.4.23 clients.
description: >-
Up to 500 HealthKit entries per call. Idempotent via the `Idempotency-Key` header (replay window 24h). Per-entry
status lets the iOS client advance its sync cursor accurately. v1.4.25 W8c adds an optional `deviceType` per
entry — feed it from `HKDevice.model` so the cross-source canonical picker can break Apple-Watch-vs-iPhone ties;
null/absent stays backward-compatible with v1.4.23 clients.
Also reachable with a narrow `measurements:write` Bearer, for a bridge that is not the phone. Such a credential
writes only on its owner's own record; its entries are attributed `MANUAL` whatever they omit, an entry naming
`APPLE_HEALTH` is refused 422 (`measurement.batch.source_not_permitted`), and the call does not advance the
native sync checkpoint — `lastSyncedAt` means the CLIENT delivered what it was holding, and a third-party push
is not that.
parameters:
- *a6
requestBody:
@@ -3736,7 +3842,14 @@ paths:
$ref: "#/components/schemas/BatchMeasurementsResponse"
"401": *a1
"409": *a7
"422": *a4
"422":
description: The batch exceeded the 500-entry limit (`measurement.batch.too_large`), failed validation, or
(`measurement.batch.source_not_permitted`) carried an entry naming `APPLE_HEALTH` under a narrow
`measurements:write` credential. Nothing was written in any of the three cases.
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorEnvelope"
"429": *a5
/api/measurements/by-external-ids:
delete:
@@ -20732,6 +20845,23 @@ components:
required:
- refreshToken
description: Exchange a one-time-use refresh token for a fresh access + refresh pair.
CreateMeasurementTokenRequest:
type: object
properties:
name:
type: string
minLength: 1
maxLength: 100
expiresInDays:
type: integer
minimum: 1
maximum: 365
required:
- name
description: "Mint a measurement-ingest Bearer. There is no `scope` field: this endpoint mints one shape and the
permission array is a literal, so no request can widen it. `name` is what the token is listed under — name it
after the thing you are pasting it into, because that is what you will be reading when you decide whether to
revoke it. `expiresInDays` defaults to 365."
RegisterRequest:
type: object
properties:
@@ -36378,6 +36508,43 @@ components:
- createdAt
- revoked
additionalProperties: false
CreateMeasurementTokenEnvelope:
type: object
properties:
data:
$ref: "#/components/schemas/CreateMeasurementTokenResponse"
error:
type: "null"
meta:
type: object
properties:
requestId:
type: string
additionalProperties: false
required:
- data
- error
additionalProperties: false
CreateMeasurementTokenResponse:
type: object
properties:
token:
type: string
description: The raw `hlk_` Bearer, RETURNED EXACTLY ONCE. It is stored only as an HMAC-SHA256 hash, so there is no way
to retrieve it later; a client that does not capture it here has to mint a replacement. Treat it as a secret
in logs, crash reports and analytics.
name:
type: string
expiresAt:
type: string
format: date-time
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d(?:\.\d+)?)?(?:Z|([+-](?:[01]\d|2[0-3]):[0-5]\d)))$
required:
- token
- name
- expiresAt
additionalProperties: false
description: The one response that carries a usable measurement-ingest Bearer value.
ApiTokenRevokeEnvelope:
type: object
properties: