Authentication & tokens
Every /api/2.0 request must carry a bearer token:
curl -H "Authorization: Bearer vutuv_pat_YOUR_TOKEN" \
https://vutuv.de/api/2.0/me
There is no anonymous access to /api/2.0. (Anonymous public data is served
by the extension URLs instead — see the
reference.)
Personal access tokens
A personal access token (PAT) acts as the member who created it, limited to the permissions ("scopes") they picked.
- Create one at vutuv.de/access_tokens (you need a vutuv account, logged in).
-
The token starts with
vutuv_pat_and is shown exactly once. vutuv stores only a hash; a lost token cannot be recovered, only replaced. - Every token expires — after 30, 90 (default) or 365 days. The expiry limits the damage if a token leaks; you can always revoke earlier and mint a fresh one in seconds.
- Revocation is one click on the same page — per token, or all at once. A revoked token fails on its very next request.
Treat tokens like passwords:
- Never commit them. Pass them via environment variables:
export VUTUV_TOKEN="vutuv_pat_..."
curl -H "Authorization: Bearer $VUTUV_TOKEN" https://vutuv.de/api/2.0/me
-
The
vutuv_pat_prefix exists so secret scanners can recognize leaked tokens. If a token may have leaked, revoke it immediately — replacing it costs you one minute.
Scopes
A token only ever does what its scopes allow. A *:write scope implies its
*:read sibling.
| Scope | Allows |
|---|---|
profile:read |
Read your profile, including entries only you can see |
profile:write |
Edit your profile and its sections |
social:read |
See your followers, who you follow, and your connections |
social:write |
Follow people, manage connections, endorse tags |
posts:read |
Read posts visible to you |
posts:write |
Write, edit and delete your posts |
messages:read |
Read your messages |
messages:write |
Send messages as you |
Request only the scopes you will use — members see the list when they create the token, and a narrow token is an easy yes.
Errors
Errors are RFC 9457 problem
documents with the content type application/problem+json:
{
"title": "Missing scope",
"status": 403,
"detail": "This endpoint needs the \"profile:read\" scope, which this token was not granted.",
"required_scope": "profile:read"
}
| Status | Meaning |
|---|---|
401 |
No token, unknown token, revoked, expired, or the account/app behind it is unavailable. The detail says which. |
403 |
Valid token, missing scope. required_scope names what to add. |
404 |
The resource does not exist or is not visible to you — deliberately indistinguishable. |
429 |
Rate limit exceeded. Honor Retry-After. |
Rate limits
Each token gets 5,000 requests per hour. Every response reports the budget:
x-ratelimit-limit: 5000
x-ratelimit-remaining: 4998
Over the limit you receive 429 with a Retry-After header (seconds).
Back off and retry after that time; do not hammer.
Calling the API from code
Anything that speaks HTTPS works. Two minimal examples:
JavaScript (browser or Node — CORS is open, tokens never go in cookies):
const res = await fetch("https://vutuv.de/api/2.0/me", {
headers: { Authorization: `Bearer ${process.env.VUTUV_TOKEN}` },
});
if (!res.ok) throw new Error(`API error ${res.status}`);
const profile = await res.json();
console.log(profile.name, profile.counts);
Python:
import os, requests
res = requests.get(
"https://vutuv.de/api/2.0/me",
headers={"Authorization": f"Bearer {os.environ['VUTUV_TOKEN']}"},
timeout=10,
)
res.raise_for_status()
profile = res.json()
print(profile["name"], profile["counts"])
Accountability
The API is tied to accounts on both sides: tokens belong to vutuv members, and (once OAuth app registration ships) third-party applications must be registered by a vutuv account too. Abuse — spam, scraping beyond the rate limits, acting against members' interests — leads to token revocation, app suspension, or account moderation. A suspended app's tokens all stop working at once.
OAuth 2 for third-party apps
Personal access tokens are for your own scripts — the member has to create and paste the token. A real third-party app uses OAuth 2 (authorization code + PKCE) instead: your users click "Connect with vutuv", approve the permissions on a consent screen, and your app receives tokens. Members see and revoke the connection at vutuv.de/connected_apps.
1. Register your application
At vutuv.de/developers/apps (you need a vutuv account —
that account is the accountability anchor; misbehaving apps get suspended,
which cuts off all of their tokens at once). You receive a client_id and
a client_secret (shown once). Register your exact redirect URLs —
https:// only, http://localhost allowed for development.
API 2.0 supports confidential clients only: the token exchange needs the client secret, so a purely client-side app needs a small server-side exchange. PKCE (S256) is required on top for every client.
2. Send the member to the consent screen
https://vutuv.de/oauth/authorize
?response_type=code
&client_id=vutuv_app_…
&redirect_uri=https://yourapp.example/callback
&scope=profile:read posts:write
&state=RANDOM_OPAQUE_VALUE
&code_challenge=BASE64URL(SHA256(code_verifier))
&code_challenge_method=S256
Scopes are space-separated (the table above). Generating the PKCE pair in bash:
code_verifier=$(openssl rand -base64 48 | tr -dc 'a-zA-Z0-9' | head -c 64)
code_challenge=$(printf '%s' "$code_verifier" | openssl dgst -sha256 -binary | basenc --base64url | tr -d '=')
The member logs in if needed, sees your app's name and the requested
permissions in plain language, and approves or denies. You get redirected
to your exact registered redirect_uri:
https://yourapp.example/callback?code=vutuv_ac_…&state=RANDOM_OPAQUE_VALUE
# or, on deny: ?error=access_denied&state=…
Always verify state matches what you sent.
3. Exchange the code for tokens
Within 10 minutes, server-side (form-encoded POST):
curl -X POST https://vutuv.de/oauth/token \
-d grant_type=authorization_code \
-d client_id=vutuv_app_… \
-d client_secret=vutuv_sec_… \
-d code=vutuv_ac_… \
-d redirect_uri=https://yourapp.example/callback \
-d code_verifier=$code_verifier
{
"access_token": "vutuv_at_…",
"refresh_token": "vutuv_rt_…",
"token_type": "Bearer",
"expires_in": 7200,
"scope": "profile:read posts:write"
}
The access token works exactly like a personal access token
(Authorization: Bearer …), acting as the consenting member with the
granted scopes. Codes are one-time: redeeming a code twice revokes
every token of that authorization (the standard theft response).
4. Refresh
Access tokens live 2 hours. Refresh tokens live 90 days and rotate on every use — store the new pair, discard the old one. Using an old (rotated) refresh token revokes the whole authorization: that, too, is theft detection, not flakiness.
curl -X POST https://vutuv.de/oauth/token \
-d grant_type=refresh_token \
-d client_id=vutuv_app_… \
-d client_secret=vutuv_sec_… \
-d refresh_token=vutuv_rt_…
5. Revoke (RFC 7009)
When a user disconnects inside your app, throw the tokens away properly:
curl -X POST https://vutuv.de/oauth/revoke \
-d client_id=vutuv_app_… \
-d client_secret=vutuv_sec_… \
-d token=vutuv_rt_…
Revoking a refresh token kills the whole pair. Members can do the same
unilaterally at any time on their Connected apps page — handle 401s
gracefully, they are a normal part of life.
Token endpoint errors
RFC 6749 vocabulary: {"error": "invalid_client"} with 401 for bad
client credentials, {"error": "invalid_grant"} with 400 for a bad/used
code, failed PKCE, redirect mismatch or a dead refresh token, and
{"error": "unsupported_grant_type"} for anything but the two grant
types above.