Apps en OAuth

Een app is hoe een frontend die je zelf host 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, beoordeling, 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.

Deze pagina is voor een app die je zelf schrijft en host. Een app die je met de bouwer in undrstand maakt wordt voor je gehost en heeft hier niets van nodig. Bouw je app

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"],
    "scopes": ["documents:read", "patterns:read", "patterns:review"],
    "website_url": "https://board.example.com",
    "privacy_policy_url": "https://board.example.com/privacy",
    "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 acht scopes, en nooit mcp:use, dat alleen Claude en ChatGPT krijgen; een optionele https-URL van je website en van je privacybeleid, allebei getoond op het toestemmingsscherm. Website en privacybeleid zijn nodig voor de beoordeling, en dan moeten ook alle redirect-URI's op het domein van de website staan.

201 Created
{
  "data": {
    "id": "0199a3c2-…",
    "name": "Pattern board",
    "redirect_uris": ["https://board.example.com/callback"],
    "website_url": "https://board.example.com",
    "privacy_policy_url": "https://board.example.com/privacy",
    "domain": {
      "name": "board.example.com",
      "record": {"type": "TXT", "host": "board.example.com", "value": "undrstand-verification=Xq7…"},
      "verified": false,
      "checked_at": null
    },
    "scopes": ["documents:read", "patterns:read", "patterns:review"],
    "approved_scopes": [],
    "pending_changes": {},
    "review": {"status": "none", "listing": null, "note": null, "sent_in_at": null, "reviewed_at": null},
    "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, privacybeleid 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, privacy_policy_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.

Bewijs je domein

Voordat iemand anders je app kan koppelen, controleert undrstand dat het domein van de website van jou is, zoals Google en Facebook dat doen. De app laat onder domain zien welk record je toevoegt: een TXT-record op het domein zelf, zonder www, met een waarde die alleen van jouw app is.

Voeg het toe bij je DNS-provider en vraag undrstand dan om ernaar te kijken. Gevonden, dan wordt domain.verified true; nog niet, dan krijg je een 422 die dat zegt. Een DNS-wijziging kan even duren, dus probeer het later opnieuw als het er nog niet staat.

POST /apps/{id}/domain
board.example.com.  TXT  "undrstand-verification=Xq7…"

curl -X POST https://api.undrstand.com/apps/0199a3c2-…/domain \
  -H "Authorization: Bearer $TOKEN" -H "Accept: application/json"

# {"data": {…, "domain": {"name": "board.example.com", …, "verified": true, …}}}

Je redirect-URI's en je privacybeleid moeten op dat domein of een subdomein ervan staan voordat je de app instuurt. Gebruik voor localhost een tweede app die je nooit instuurt. Laat het record staan: undrstand kijkt er elke week naar, en is het weg, dan kan niemand behalve jij de app koppelen tot je het domein opnieuw controleert. Je krijgt dan een melding.

Beoordeling

Een nieuwe app werkt voor één account: het jouwe. Je kunt de hele flow meteen doorlopen op je eigen gegevens. Ieder ander wordt geweigerd met access_denied tot undrstand de app heeft beoordeeld.

Stuur hem in als hij klaar is, met wat er in zijn vermelding komt te staan: wie hem maakte en wat hij doet. Het veld review op de app zegt hoe het ervoor staat: status gaat van none naar pending, en dan approved of rejected, en bij een afwijzing zegt een notitie waarom. Zolang hij wacht kun je de aanvraag terugnemen.

POST /apps/{id}/review
curl -X POST https://api.undrstand.com/apps/0199a3c2-…/review \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" -H "Accept: application/json" \
  -d '{
    "developer": "Board Labs",
    "description": "A board of your patterns: what keeps coming back, with the days it showed up on."
  }'

# {"data": {…, "approved_scopes": [],
#   "review": {"status": "pending", "listing": {"developer": "Board Labs", "description": "…"}, …}}}

DELETE https://api.undrstand.com/apps/{id}/review   # take the request back while it waits

Na goedkeuring staat in approved_scopes wat anderen hem mogen geven, en staat de app onder Apps in de webapp, bij de apps die met undrstand verbinden. Een token voor iemand anders draagt nooit meer dan de goedgekeurde scopes.

Ook wijzigingen na goedkeuring worden beoordeeld. Scopes die je toevoegt bereiken anderen pas nadat je de app opnieuw instuurt. Een nieuwe naam, redirect-URI's, website of privacybeleid wachten in pending_changes en gaan in bij goedkeuring. Verander je wat beoordeeld wordt terwijl de app wacht, dan vervalt de aanvraag, zodat wat goedgekeurd is ook is wat bekeken is.

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=documents: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 je geverifieerde domein, je privacybeleid en waar het de gebruiker naar terugstuurt, met 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 documents:write scope."}
ScopeOpentDe gebruiker leest
documents:readdocumenten ophalen, lezen en doorzoekenLezen wat je hebt ingeleverd en wat erin gevonden is
documents:writedocumenten insturen en verwijderenNamens jou nieuwe tekst inleveren en documenten 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
observations:readwat de Observer de gebruiker verteldeLezen wat je observer opmerkte en je vertelde
health:readwat gekoppelde wearables matenLezen wat je gekoppelde wearables maten, zoals slaap en hartslag
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 documents:write.
  • Registreer exacte redirect-URI's op je geverifieerde domein. 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.
  • Je app leest wat het abonnement van de gebruiker leest. Toon een vergrendeld patroon als vergrendeld; werk er niet omheen.

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.