Authentication
| Method | Token | Used for |
|---|---|---|
| API key | df_live_… | Your own scripts, servers and automations for your own account |
| OAuth 2.0 | dfo_… | Third-party apps acting on behalf of other users |
API keys
You create keys in the dashboard under Settings → Developers. There you choose the permissions and, optionally, an expiry date. The key has the format df_live_ plus 32 characters and is shown only once; DebtFlow only stores a hash. Revoked or expired keys are rejected with 401 invalid_api_key.
curl 'https://www.debt-flow.io/api/v1/me' \
-H "Authorization: Bearer $DEBTFLOW_API_KEY"OAuth 2.0
With OAuth, other DebtFlow users connect your application to their account without giving you a password or a key. DebtFlow supports the authorisation code flow with PKCE (RFC 6749, RFC 7636) and refresh tokens.
| Endpoint | URL |
|---|---|
| Authorisation | https://www.debt-flow.io/oauth/authorize |
| Token | POST https://www.debt-flow.io/api/v1/oauth/token |
| Revocation | POST https://www.debt-flow.io/api/v1/oauth/revoke |
| Metadata (RFC 8414) | https://www.debt-flow.io/.well-known/oauth-authorization-server |
1. Register your app
Under Settings → Apps → Register app you enter the name, description, website, redirect URIs and the maximum permissions the app may request. You receive a client ID (oac_…) and – for confidential apps – a client secret (dfs_…), shown only once.
- Confidential: runs on your server and keeps the secret private. Authenticates at the token endpoint with HTTP Basic or
client_id/client_secretin the body. PKCE is recommended. - Public: browser, mobile or desktop app without a secret. PKCE is mandatory; only
client_idis sent to the token endpoint. - Redirect URIs must match exactly and use
https://. For development,http://localhostandhttp://127.0.0.1are allowed, with any port.
2. Generate PKCE values
For every sign-in, generate a random code_verifier (43–128 characters) and derive the code_challenge from it with SHA-256. Also generate a random state to protect against CSRF. Both values stay in the user's session until the redirect back.
# code_verifier: 64 random characters from A–Z a–z 0–9 - . _ ~
CODE_VERIFIER=$(openssl rand -base64 48 | tr -dc 'A-Za-z0-9' | head -c 64)
# code_challenge = BASE64URL(SHA256(code_verifier)) without "="
CODE_CHALLENGE=$(printf '%s' "$CODE_VERIFIER" | openssl dgst -sha256 -binary | openssl base64 -A | tr '+/' '-_' | tr -d '=')3. Redirect the user for consent
https://www.debt-flow.io/oauth/authorize
?response_type=code
&client_id=oac_DeineClientId0000000
&redirect_uri=https%3A%2F%2Fapp.example.com%2Foauth%2Fcallback
&scope=contacts%3Aread%20invoices%3Awrite
&state=7Jx0…
&code_challenge=E9Mel…
&code_challenge_method=S256| Parameter | Meaning |
|---|---|
response_type | always code |
client_id | client ID of your app |
redirect_uri | a registered redirect URI; may be omitted if exactly one is registered |
scope | permissions separated by spaces; if omitted, all permissions registered for the app |
state | random value, returned unchanged |
code_challenge | BASE64URL(SHA256(code_verifier)), 43 characters |
code_challenge_method | only S256 |
If the user is not signed in, they sign in to DebtFlow first. They then see your app, the requested permissions and the redirect target, and either allow or deny access.
4. Handle the redirect
After consent, DebtFlow redirects to your redirect URI:
https://app.example.com/oauth/callback?code=dfc_…&state=7Jx0…&iss=https%3A%2F%2Fwww.debt-flow.ioCheck that state matches your session and that iss equals https://www.debt-flow.io (RFC 9207). The code is valid for 5 minutes and can be redeemed only once. If a code is redeemed a second time, DebtFlow revokes all tokens issued with it.
5. Exchange the code for tokens
The token endpoint expects application/x-www-form-urlencoded. redirect_uri is required if it was passed during authorisation.
curl -X POST 'https://www.debt-flow.io/api/v1/oauth/token' \
-u "$CLIENT_ID:$CLIENT_SECRET" \
-d grant_type=authorization_code \
-d code="$CODE" \
-d redirect_uri=https://app.example.com/oauth/callback \
-d code_verifier="$CODE_VERIFIER"{
"access_token": "dfo_…",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "dfr_…",
"scope": "contacts:read invoices:write"
}6. Call the API
The access token is sent like an API key: Authorization: Bearer dfo_…. It is valid for one hour. Expired or revoked tokens return 401 invalid_token; missing permissions return 403 insufficient_scope.
7. Refresh the token
Use the refresh token to obtain new tokens. Every refresh returns a new refresh token; the one you used becomes invalid. Optionally, request fewer permissions with scope – never more.
curl -X POST 'https://www.debt-flow.io/api/v1/oauth/token' \
-u "$CLIENT_ID:$CLIENT_SECRET" \
-d grant_type=refresh_token \
-d refresh_token="$REFRESH_TOKEN"Revoking tokens
When a user signs out of your app, send the refresh token to the revocation endpoint (RFC 7009). This also invalidates all access tokens derived from it. The response is always 200.
curl -X POST 'https://www.debt-flow.io/api/v1/oauth/revoke' \
-u "$CLIENT_ID:$CLIENT_SECRET" \
-d token="$REFRESH_TOKEN"Lifetimes
| Value | Valid for | Note |
|---|---|---|
| Authorisation code | 5 minutes | single use |
| Access token | 1 hour | expires_in |
| Refresh token | 90 days | every refresh starts a new 90-day period |
Errors at the token endpoint
The token and revocation endpoints respond in OAuth format { "error": "…", "error_description": "…" }:
| Code | Status | Meaning |
|---|---|---|
invalid_request | 400 | parameter missing, duplicated or invalid |
invalid_client | 401 | client ID unknown or secret wrong |
invalid_grant | 400 | code or refresh token invalid, expired or already used, PKCE does not match, or connection revoked |
unsupported_grant_type | 400 | only authorization_code and refresh_token |
invalid_scope | 400 | unknown permissions, or more than were granted |
When the user disconnects
Users see all connected apps under Settings → Apps and can revoke access at any time. All tokens of the connection then become invalid immediately; your app receives 401 or invalid_grant and must take the user through authorisation again. If you, as the developer, reduce your app's permissions, the reduced rights also apply immediately to existing connections.