Apps en OAuth

Een app is hoe andermans frontend namens een gebruiker met undrstand praat. Je registreert hem één keer, hij vraagt gebruikers om de scopes die hij nodig heeft, en elke gebruiker beslist op een toestemmingsscherm. Deze pagina behandelt registratie, de scopes en de authorization code flow met PKCE.

Waarom apps

Je eigen token is van jou: het opent alles op je account. Een frontend die je voor anderen bouwt mag hun tokens niet in handen hebben. Hij houdt een token dat elke gebruiker hem gaf, beperkt tot wat die goedkeurde.

Dat is wat een app is: een geregistreerde client met een naam, redirect-URI's en een lijst scopes die hij mag vragen. Een gebruiker die op je app inlogt ziet een toestemmingsscherm met die scopes en beslist.

Een app registreren

Registreer met je eigen account en je eigen token. Een app kan geen apps registreren voor een gebruiker: de apps-routes weigeren tokens die aan een app zijn gegeven.

POST /apps
curl -X POST https://api.undrstand.com/apps \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" -H "Accept: application/json" \
  -d '{
    "name": "Pattern board",
    "redirect_uris": ["https://board.example.com/callback", "http://localhost:5173/callback"],
    "scopes": ["captures:read", "patterns:read", "patterns:review"],
    "website_url": "https://board.example.com",
    "confidential": false
  }'

Regels voor de aanvraag: een naam van hoogstens honderd tekens; één tot tien redirect-URI's, alleen https, behalve http op localhost of 127.0.0.1 op elke poort tijdens het ontwikkelen, en nooit een fragment of een eigen scheme; minstens één van de zes scopes, en nooit mcp:use, dat alleen Claude en ChatGPT krijgen; een optionele https-website-URL, getoond op het toestemmingsscherm.

201 Created
{
  "data": {
    "id": "0199a3c2-…",
    "name": "Pattern board",
    "redirect_uris": ["https://board.example.com/callback", "http://localhost:5173/callback"],
    "website_url": "https://board.example.com",
    "scopes": ["captures:read", "patterns:read", "patterns:review"],
    "confidential": false,
    "secret": null,
    "created_at": "2026-09-27T09:12:44.000000Z",
    "updated_at": "2026-09-27T09:12:44.000000Z"
  }
}

Het antwoord is je app. Bewaar het id: dat is de client_id in de flow. Verander naam, redirect-URI's, website of scopes later met een patch op de app, haal je apps op, of verwijder er een, wat elk token intrekt dat hij gekregen heeft.

GET    https://api.undrstand.com/apps            # your apps, paginated (page, limit)
GET    https://api.undrstand.com/apps/{id}
PATCH  https://api.undrstand.com/apps/{id}       # name, redirect_uris, website_url, scopes
DELETE https://api.undrstand.com/apps/{id}       # 204, revokes every token the app was granted

Publiek of vertrouwelijk

Een app is standaard publiek: een browserpagina, een mobiele app, alles wat geen geheim kan bewaren. Hij bewijst zichzelf met alleen PKCE.

Geef confidential true mee voor een app met een server die een geheim kan bewaren. Het geheim staat één keer in het antwoord bij het aanmaken en daarna nergens meer; sla het dan op. Een vertrouwelijke app stuurt het als client_secret naar het token-endpoint, naast PKCE.

De flow

Authorization code met PKCE, op het API-domein. Er is geen andere grant.

  1. Maak een code verifier en zijn challenge. De verifier is een willekeurige string van 43 tot 128 tekens; de challenge is zijn SHA-256-hash, base64url-gecodeerd.

    PKCE
    // browser or Node 20+
    const bytes = crypto.getRandomValues(new Uint8Array(32));
    const base64url = (buf) =>
      btoa(String.fromCharCode(...new Uint8Array(buf)))
        .replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
    
    const verifier = base64url(bytes);                                   // keep it
    const challenge = base64url(
      await crypto.subtle.digest("SHA-256", new TextEncoder().encode(verifier)),
    );
  2. Stuur de gebruiker naar het authorize-endpoint. Scopes zijn gescheiden door spaties; state is van jou om op de terugweg te controleren.

    GET /oauth/authorize
    https://api.undrstand.com/oauth/authorize
      ?response_type=code
      &client_id=0199a3c2-…
      &redirect_uri=https://board.example.com/callback
      &scope=captures:read patterns:read
      &state=k3j9…
      &code_challenge=…
      &code_challenge_method=S256
  3. Een gebruiker die niet is ingelogd komt eerst op de connect-pagina, logt in met een gemailde link of een social account, en komt terug. Dan toont het toestemmingsscherm de scopes die je vroeg, beperkt tot die waarmee je app geregistreerd is. Bij goedkeuren keert de gebruiker terug naar je redirect-URI met een code en je state.

    https://board.example.com/callback?code=def502…&state=k3j9…
  4. Ruil de code voor tokens. Stuur de verifier uit stap 1 mee. Een vertrouwelijke app voegt zijn client_secret toe.

    POST /oauth/token
    curl -X POST https://api.undrstand.com/oauth/token \
      -H "Content-Type: application/json" -H "Accept: application/json" \
      -d '{
        "grant_type": "authorization_code",
        "client_id": "0199a3c2-…",
        "redirect_uri": "https://board.example.com/callback",
        "code": "def502…",
        "code_verifier": "…"
      }'
    
    # {"token_type": "Bearer", "expires_in": 86400,
    #  "access_token": "eyJ…", "refresh_token": "def502…"}
  5. Roep de API aan met het access token als bearer header, precies zoals met een persoonlijk token.

    GET /patterns
    curl https://api.undrstand.com/patterns \
      -H "Authorization: Bearer eyJ…" -H "Accept: application/json"

Scopes

Een route weigert een token zonder zijn scope met een 403 die de scope noemt. De profielroute heeft een geldig token nodig en geen scope, zodat een app altijd kan zien met wie hij praat.

HTTP/1.1 403 Forbidden
{"message": "This token is missing the captures:write scope."}
ScopeOpentDe gebruiker leest
captures:readcaptures ophalen en lezenLezen wat je hebt ingeleverd en wat erin gevonden is
captures:writecaptures insturen en verwijderenNamens jou nieuwe tekst inleveren en captures verwijderen
patterns:readpatronen, één patroon met zijn correlaties en flows, de tijdlijnJe patronen, correlaties, flows en tijdlijn lezen
patterns:reviewpatronen en correlaties accepteren of afwijzenPatronen en correlaties voor je accepteren of afwijzen
settings:readinstellingen lezenJe instellingen lezen
settings:writeinstellingen veranderenJe instellingen veranderen

Vernieuwen en verlopen

Een access token leeft één dag, een refresh token dertig. Vernieuw voor de dag om is; het antwoord is een nieuw paar en het oude refresh token is weg.

POST /oauth/token
curl -X POST https://api.undrstand.com/oauth/token \
  -H "Content-Type: application/json" -H "Accept: application/json" \
  -d '{
    "grant_type": "refresh_token",
    "client_id": "0199a3c2-…",
    "refresh_token": "def502…"
  }'

Een gebruiker kan de stekker eruit trekken: een app verwijderen trekt zijn tokens in, en het account verwijderen ook. Reken op elk moment op een 401 en stuur de gebruiker opnieuw door de flow.

Regels voor apps

  • Vraag alleen de scopes die je gebruikt. Een patronenbord heeft patterns:read nodig, niet captures:write.
  • Registreer exacte redirect-URI's. De flow weigert er een die niet op de lijst staat.
  • Dit zijn gegevens over iemands privéleven. Toon ze, bewaar ze niet: sla niets op buiten wat de gebruiker je vroeg, en breng ze nooit naar een andere dienst zonder dat te zeggen.
  • Presenteer een patroon niet als diagnose of label. Beschrijf wat de data laat zien, in de woorden die de beschrijving al gebruikt.
  • Noem nooit de analysemethode. De gebruiker krijgt het inzicht, niet de machinerie.

Waar nu heen

  • API — De API-gids beschrijft de resources die je app leest en schrijft.
  • /docs/api — De gegenereerde referentie noemt elke route, elk veld en elke fout, op het API-domein.