# Long Term Access

> Read your users' data from your own servers, without them signing in again. Restricted mode, granted on request.

<warning>

**This mode is disabled by default and is not self-service.** It is granted client by client, after a
justified request. See [Requesting access](#requesting-access) below.

</warning>

## 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.

<table>
<thead>
  <tr>
    <th>
      
    </th>
    
    <th>
      Standard mode
    </th>
    
    <th>
      Long term access
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      Credential per user
    </td>
    
    <td>
      Refresh token, expires
    </td>
    
    <td>
      Long term token, until revoked
    </td>
  </tr>
  
  <tr>
    <td>
      Keeping it alive
    </td>
    
    <td>
      Refresh on a schedule, handle rotation
    </td>
    
    <td>
      Nothing
    </td>
  </tr>
  
  <tr>
    <td>
      User must sign in again
    </td>
    
    <td>
      Yes, when the refresh token expires
    </td>
    
    <td>
      No
    </td>
  </tr>
  
  <tr>
    <td>
      User can revoke
    </td>
    
    <td>
      On logout / token revocation
    </td>
    
    <td>
      Any time, from their account page
    </td>
  </tr>
  
  <tr>
    <td>
      Endpoints
    </td>
    
    <td>
      The public API
    </td>
    
    <td>
      The same public API
    </td>
  </tr>
</tbody>
</table>

## Requesting access

<warning>

**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.

</warning>

The [standard per-user mode](/api/authentication) 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](#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

<u-button icon="simple-icons:discord" label="Discord" to="https://diapstash.com/discord" variant="link">



</u-button>

or through the
<u-button icon="lucide:mail" label="contact form" to="https://diapstash.com/contact" variant="link">



</u-button>

.

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](https://account.diapstash.com/account#api) 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.

<table>
<thead>
  <tr>
    <th>
      Scope
    </th>
    
    <th>
      Description
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        cloud-sync.permanent.latest-change
      </code>
    </td>
    
    <td>
      Read the latest change of enrolled users
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        cloud-sync.permanent.stats
      </code>
    </td>
    
    <td>
      Read aggregated statistics of enrolled users
    </td>
  </tr>
</tbody>
</table>

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

<warning>

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.

</warning>

## Enrolling a user

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

### 1. Get the user's consent

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

```http
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

```http
POST https://account.diapstash.com/api/long-term-access/tokens
Authorization: Bearer <THE USER ACCESS TOKEN FROM STEP 1>
```

```json
{
  "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.

<warning>

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.

</warning>

<warning>

**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.

</warning>

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.

```text
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.

```bash
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.

<note>

The credential is **static**: no token endpoint round-trip before a call, nothing to refresh, nothing
to cache. One request per read.

</note>

<table>
<thead>
  <tr>
    <th>
      Endpoint
    </th>
    
    <th>
      Long term scope
    </th>
    
    <th>
      Regular scope
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        GET /api/v1/history/latest-change
      </code>
    </td>
    
    <td>
      <code>
        cloud-sync.permanent.latest-change
      </code>
    </td>
    
    <td>
      <code>
        cloud-sync.history
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        GET /api/v1/history/stats
      </code>
    </td>
    
    <td>
      <code>
        cloud-sync.permanent.stats
      </code>
    </td>
    
    <td>
      <code>
        cloud-sync.history
      </code>
    </td>
  </tr>
</tbody>
</table>

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.

<table>
<thead>
  <tr>
    <th>
      Status
    </th>
    
    <th>
      Meaning
    </th>
    
    <th>
      What to do
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        401
      </code>
    </td>
    
    <td>
      Credential unknown, malformed, or the client secret does not match
    </td>
    
    <td>
      Check your configuration — this is not necessarily a revocation
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        401 PermanentAccessRevoked
      </code>
    </td>
    
    <td>
      The access existed and has been revoked
    </td>
    
    <td>
      Delete your copy of the token; it will never work again
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        401 NoCloudSyncLinked
      </code>
    </td>
    
    <td>
      The account has no Cloud Sync linked
    </td>
    
    <td>
      Ask the user to link it in the app
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        403 CloudSyncInactive
      </code>
    </td>
    
    <td>
      Cloud Sync silent for over 90 days
    </td>
    
    <td>
      <strong>
        Keep the token
      </strong>
      
       — access resumes on its own
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        403
      </code>
    </td>
    
    <td>
      Neither your client nor the user granted the scope you asked for
    </td>
    
    <td>
      Re-enroll asking for that scope
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        404
      </code>
    </td>
    
    <td>
      Nothing recorded for that user (current change only)
    </td>
    
    <td>
      Nothing; the access is fine
    </td>
  </tr>
</tbody>
</table>

<note>

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.

</note>

<tip>

`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.

</tip>

## 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](https://account.diapstash.com), 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`.

<note>

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

</note>

### Your service

Drop a token you no longer need:

```http
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

<warning>

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.

</warning>

- **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: <u-button icon="simple-icons:discord" label="Discord" to="https://diapstash.com/discord" variant="link">



</u-button>
