Public API

Long Term Access

Read your users' data from your own servers, without them signing in again. Restricted mode, granted on request.
This mode is disabled by default and is not self-service. It is granted client by client, after a justified request. See Requesting access below.

What it solves

The standard API is built around a user who is present: you hold an access token for one user, it lives an hour, and you refresh it as needed.

That model breaks down when a server-side service follows its users over months. Keeping 500 users readable means storing 500 refresh tokens, refreshing each of them on a schedule, handling rotation — and losing a user for good as soon as one of those tokens expires.

Long term access replaces that with a single durable token per user. Your service stores it and reads that user's data whenever it needs to, authenticating only as itself.

Standard modeLong term access
Credential per userRefresh token, expiresLong term token, until revoked
Keeping it aliveRefresh on a schedule, handle rotationNothing
User must sign in againYes, when the refresh token expiresNo
User can revokeOn logout / token revocationAny time, from their account page
EndpointsThe public APIThe same public API

Requesting access

We refuse by default. Long term access is granted only when the need is genuinely justified and coherent with what your service does. If a request does not make that case, the answer is no.

The standard per-user mode remains the one to reach for. It covers almost every integration, and if your feature can be built with it, that is what we will point you to rather than opening this one.

It is also not a key to the whole API. Only a deliberately small set of endpoints accepts a long term access token — see Reading data for the current list. Everything else stays reachable with a regular user token only, and being granted this mode does not change that. If what you need is not in that list, this is not the answer to your problem.

Long term access hands your service long-lived, unattended access to the personal data of many people. We open it deliberately, not on demand, and we need to understand your project before we do.

Reach us on

Discord

or through the contact form.

Describe your request precisely — a vague one will be turned down rather than chased up.

Your project

  • What your service does, in full: what it is for, who runs it, whether it is free or paid, open source or not, and where it is hosted.
  • Who your users are, and roughly how many of them you expect to enroll.
  • How your users reach it — a website, a mobile app, a home automation setup, a research protocol…

Your use of the DiapStash API

  • Which endpoints you already call, and how often.
  • Why the standard per-user mode does not fit — be concrete: what breaks, or what you cannot build without long term access. This is the claim we weigh most heavily, and the one most requests fail on.
  • Which long term scopes you need, and what each one is for. Asking for the narrowest set that makes your feature work makes the request much easier to grant.
  • How often you will read each enrolled user.

What you do with the data

This is the part we read most carefully. Our users are trusting you with intimate, personal data.

  • What you store, where it is hosted, and for how long.
  • How it is secured: encryption at rest and in transit, who on your side can access it, and how long term access tokens themselves are protected.
  • Who else sees it: any third party, subprocessor, or analytics provider it passes through.
  • That you do not resell, trade, or share the data, and that you do not use it to train models or build advertising profiles. We will ask you to state this explicitly.
  • What happens when a user revokes, or deletes their account on your side: how their data is deleted, and within what delay.
  • A link to your privacy policy, covering the above.

Requirements your client must meet

  1. It must be a confidential backend client — an application type of API / Server, with a client secret. A Javascript Web App or a native app can never be granted this mode: its secret cannot be kept.
  2. It must be able to keep long term access tokens secret, at rest, on your own servers.

Once we open the mode on your client, a Long term access section appears on it in your API access tab. You pick the long term scopes there yourself, and can take one back at any time by unticking it.

Scopes

Long term access uses its own scope family, separate from the regular cloud-sync.* scopes. Asking for one of them in an ordinary sign-in is what turns it into a long term enrollment.

ScopeDescription
cloud-sync.permanent.latest-changeRead the latest change of enrolled users
cloud-sync.permanent.statsRead aggregated statistics of enrolled users

Users see these on the consent screen with an explicit warning that the access survives their session.

Until we open the mode on your client, these scopes do not exist for it: an authorization request asking for one is refused with invalid_scope. That refusal is the gate — there is nothing else to configure on your side.

Enrolling a user

Enrollment happens once per user, against account.diapstash.com, in two steps.

An ordinary authorization code flow. The only difference from a regular sign-in is the scopes you ask for:

https://account.diapstash.com/oidc/auth?
  client_id=YOUR_CLIENT_ID
  &redirect_uri=YOUR_REDIRECT_URI
  &response_type=code
  &scope=openid cloud-sync.permanent.latest-change cloud-sync.permanent.stats

Exchange the code at the token endpoint as usual, and keep the resulting access token for the next step. It is short-lived, so do this right away.

2. Exchange it for a long term access token

POST https://account.diapstash.com/api/long-term-access/tokens
Authorization: Bearer <THE USER ACCESS TOKEN FROM STEP 1>
{
  "token": "dspa_a1b2c3d4e5f60718293a4b5c_XSyR...",
  "scopes": ["cloud-sync.permanent.latest-change", "cloud-sync.permanent.stats"]
}

That access token is the whole authorization: it says which user consented, to which client, and for which scopes. The long term access token inherits exactly those scopes — never more.

Because that token alone can create a long term access, treat it as sensitive for the minutes it lives: keep it server-side, never log it, and exchange it immediately. Only a user token is accepted here — minting acts on behalf of a user, so one has to have consented.
The token is returned once and never again. We only store a hash of it — we cannot show it to you later. Store it before you close the response, and treat it like a password.

Re-enrolling the same user rotates their token: the previous one stops working immediately.

Reading data

Long term access does not add endpoints. It adds a second way to authenticate on the ordinary ones: the routes below accept a cloud-sync.permanent.* scope and work in both modes, returning the exact same payload.

regular    Authorization: Bearer <USER ACCESS TOKEN>
           DS-API-CLIENT-ID: <CLIENT ID>

long term  Authorization: Bearer <LONG TERM TOKEN>.<YOUR CLIENT SECRET>

The long term credential is a single bearer token: the user's long term access token, a dot, then your client secret. Its dspa_ prefix is what tells it apart from a user token.

curl -H "Authorization: Bearer dspa_a1b2c3d4e5f60718293a4b5c_XSyR....YOUR_CLIENT_SECRET" \
  https://api.diapstash.com/api/v1/history/latest-change

Both halves are needed. Our database only stores a hash of the long term access token, and your client secret on its own names no user.

The credential is static: no token endpoint round-trip before a call, nothing to refresh, nothing to cache. One request per read.
EndpointLong term scopeRegular scope
GET /api/v1/history/latest-changecloud-sync.permanent.latest-changecloud-sync.history
GET /api/v1/history/statscloud-sync.permanent.statscloud-sync.history

Everything else — the paginated /history/changes and /history/accidents, stocks, types — stays reachable only with a regular user token. Long term access deliberately covers the two reads a server-side service needs to follow a user over time, not the whole API.

Error responses

A long term call fails as a whole; there is no per-user status to read.

StatusMeaningWhat to do
401Credential unknown, malformed, or the client secret does not matchCheck your configuration — this is not necessarily a revocation
401 PermanentAccessRevokedThe access existed and has been revokedDelete your copy of the token; it will never work again
401 NoCloudSyncLinkedThe account has no Cloud Sync linkedAsk the user to link it in the app
403 CloudSyncInactiveCloud Sync silent for over 90 daysKeep the token — access resumes on its own
403Neither your client nor the user granted the scope you asked forRe-enroll asking for that scope
404Nothing recorded for that user (current change only)Nothing; the access is fine
A plain 401 covers an unknown token and a wrong client secret alike, on purpose: we do not confirm whether a token ever existed to anyone who cannot already prove it. You only get PermanentAccessRevoked when both halves of your credential are correct — which is why it is safe to tell you. We never say who revoked it.
403 CloudSyncInactive is not a revocation. A user who stops syncing for a few months and comes back keeps working with the same token — do not delete it.

Rate limits

Per client, per hour, returned in the standard RateLimit response headers — the same budget as the rest of the public API. Your client is identified by the credential itself, so DS-API-CLIENT-ID is not needed in long term mode.

Revoking

The user

Users see every service holding a long term access in the Connected services tab of their account, with the scopes granted, when it was granted, and when you last used it. One click revokes it, and your very next call returns PermanentAccessRevoked.

Design for this. Revocation is immediate, silent, and does not warn you in advance.

Your service

Drop a token you no longer need:

DELETE https://account.diapstash.com/api/long-term-access/tokens
Authorization: Bearer <LONG TERM TOKEN>.<YOUR CLIENT SECRET>

The credential names the token to drop, so there is nothing to send in the body. Always answers 204, whether or not the token existed.

Us

We can withdraw the mode from your client. Every token you hold stops working at once. We do this if the feature is used outside the scope of your request, or on abuse reports.

Handling the tokens

One long term access token is a durable key to one person's data. Together, your token store is the most sensitive thing your integration holds.
  • Encrypt them at rest. They are equivalent to passwords, not to identifiers.
  • Never log them, and never put them in a URL — they belong in the Authorization header, which most logging setups already redact.
  • Never expose them to a browser or ship them to a mobile app.
  • Delete your copy as soon as a token comes back PermanentAccessRevoked. A plain 401 is more likely a configuration problem than a revocation — check before discarding anything.
  • Delete a user's token when they delete their account on your side. Do not wait for them to also find the DiapStash account page.

Deleting a DiapStash account revokes every long term access on it.

Support

Questions or issues: Discord