RecordKeeper is a personal health record. With a person's permission, your app or AI assistant can read the parts of their record they choose, and write to the parts they allow. It works like asking for Apple Health access: the person sees your app's name, picks sections, and can disconnect at any time.
Apps are approved one at a time. RecordKeeper registers your app and gives you a client ID, plus a client secret if your app runs on a server. Send:
https, a localhost address while you develop, or a reverse-domain app address such as com.yourcompany.app:/oauthConnecting outside apps is part of RecordKeeper Plus. A person without Plus is offered an upgrade on the consent screen instead of an Allow button.
Make a random code_verifier (43 to 128 characters), and its code_challenge = base64url(SHA-256(verifier)). Then open:
https://app.recordkeeperapp.com/v1/oauth/authorize
?response_type=code
&client_id=YOUR_CLIENT_ID
&redirect_uri=https://yourapp.example/callback
&scope=medications.read blood_pressure.write
&state=RANDOM_VALUE_YOU_CHECK_LATER
&code_challenge=YOUR_CODE_CHALLENGE
&code_challenge_method=S256
The person signs in if they need to, sees your app's name and the sections you asked for, and chooses. A mobile app should open this in the system browser, never an embedded web view.
They return to your redirect_uri with ?code=…&state=…. Check state matches. If they said no, you get ?error=access_denied instead. Codes last 10 minutes and work once.
POST https://app.recordkeeperapp.com/v1/oauth/token
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code
&code=THE_CODE
&redirect_uri=https://yourapp.example/callback
&client_id=YOUR_CLIENT_ID
&code_verifier=YOUR_CODE_VERIFIER
A server app also sends client_secret (in the body, or as HTTP Basic). The answer:
{
"access_token": "rka_…",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "rkr_…",
"scope": "blood_pressure.write medications.read"
}
Read scope: it is what the person actually allowed, which may be less than you asked for.
GET https://app.recordkeeperapp.com/v1/profiles
Authorization: Bearer rka_…
Every account has one or more profiles (the person, and anyone they manage, such as a child). Most records belong to a profile; pass profileId when you read or write. Then, for example:
GET https://app.recordkeeperapp.com/v1/medications?profileId=PROFILE_ID
Authorization: Bearer rka_…
POST https://app.recordkeeperapp.com/v1/blood-pressure
Authorization: Bearer rka_…
Content-Type: application/json
{ "profileId": "PROFILE_ID", "systolicMmhg": 118, "diastolicMmhg": 76, "measuredAt": "2026-10-15T08:30:00Z" }
Lists take ?since= (an ISO date) and ?limit= (the most recent N, up to 1000). Every path, field, and required value is in the OpenAPI reference.
Access tokens last one hour. Refresh before then:
POST https://app.recordkeeperapp.com/v1/oauth/token
Content-Type: application/x-www-form-urlencoded
grant_type=refresh_token&refresh_token=rkr_…&client_id=YOUR_CLIENT_ID
You get a new pair, and the old refresh token stops working. Use each refresh token once. If an old one is ever used again, RecordKeeper assumes it was stolen and ends every token on that connection; the person has to connect your app again. Refresh tokens last 90 days.
To disconnect from your side, send the token to POST https://app.recordkeeperapp.com/v1/oauth/revoke with your client_id.
Chatbots and always-on assistants that speak the Model Context Protocol connect to one address:
https://app.recordkeeperapp.com/v1/mcp
The assistant finds the sign-in on its own: an unauthenticated request answers 401 with a WWW-Authenticate header pointing at https://app.recordkeeperapp.com/.well-known/oauth-protected-resource, which names this server's OAuth endpoints. If it asks for no particular sections, the person is offered every section read-only and turns off what they don't want.
Already approved, nothing to register: these assistants use a ready-made client ID with no secret. Enter it when the assistant asks for one.
claude (step-by-step)meta-muse (step-by-step)chatgpt (step-by-step)Any other assistant needs its own client ID from RecordKeeper first (there is no automatic registration), entered in its connector settings.
The tools: list_profiles, list_record_types, query_records, get_record, and get_vitals. All of them read; none of them change anything. They see exactly what the person allowed, and every read appears under Connected Apps.
Every refusal is JSON with a machine-readable error:
401 invalid_token: expired, revoked, or the person disconnected you. Refresh; if that fails, send them through step 1 again.402 upgrade_required: the person's Plus subscription has ended. Tell them; nothing else will work until it's back.403 section_not_shared: they didn't allow that section.403 view_only: they allowed reading that section, not changing it.404 not_found: no such record, or one your app isn't allowed to see. These look the same on purpose.429 rate_limited: over 300 requests a minute on one connection. Back off.A scope is a section name plus .read or .write (write includes read). These sections are stable: their fields will not change without a new API version.
allergies.read, allergies.writeappointments.read, appointments.writeblood_pressure.read, blood_pressure.writediagnoses.read, diagnoses.writeimmunizations.read, immunizations.writelabs.read, labs.writemedications.read, medications.writesleep.read, sleep.writevitals.read, vitals.writeweight.read, weight.writeEvery other section in the reference is experimental (x-stability in the OpenAPI document): usable, but its fields may change.
Whatever you ask for, these are not offered and cannot be granted: visit transcripts, the safety plan, ID cards (transcripts, safety_plan, ids), the person's password vault, and anything they've marked Private. Asking for one of these sections returns invalid_scope.
The person can see every app they've connected, what it may do, what it read in the last 30 days, and a Disconnect button. Once your app has their data, it is governed by your privacy policy, and RecordKeeper tells them so on the consent screen. Ask only for what your app needs.