Authentication
Every Stats API endpoint except public widget routes requires authentication. Today that means session cookies, the same auth_token and auth_token_refresh cookies the dashboard uses. Two flows can issue them: email OTP and Google OAuth.
API keys are coming soon
Long-lived keys (Authorization: Bearer sk_live_…) with per-key permissions are planned and will be issued from Settings → API Keys once the UI ships. Until then, automate against the cookie flow below. It's what the dashboard uses, so it's fully supported.
Cookie details
After successful login Statable sets two cookies:
| Cookie | Lifetime | Purpose |
|---|---|---|
auth_token | 15 minutes | Short-lived JWT access token. Sent with every request. |
auth_token_refresh | 7 days (sliding) | Refresh token. Used by the auth middleware to mint a new auth_token when the access token expires. |
Both are set with HttpOnly, Secure, and SameSite=None so they survive cross-origin XHR/fetch from https://statable.com and any embedding origin you've whitelisted. The browser handles them automatically. In non-browser clients, persist them in a cookie jar.
Auto-refresh is transparent: call any authenticated endpoint with an expired auth_token but a valid auth_token_refresh, the server mints a new access token and re-sets the cookie in the response. Your client just writes the new cookie back into its jar.
Flow 1, Email OTP
Three-step handshake: request code, verify, use the cookies.
1. Request a one-time code
curl -X POST https://statable.com/api/auth/send-otp \
-H 'Content-Type: application/json' \
-d '{"email": "[email protected]"}'
Response:
{ "message": "OTP sent", "email": "[email protected]" }
A six-digit code is emailed. Expires in 10 minutes.
2. Verify the code
curl -X POST https://statable.com/api/auth/verify-otp \
-H 'Content-Type: application/json' \
-c cookies.txt \
-d '{"email": "[email protected]", "code": "123456"}'
Response (and Set-Cookie headers for auth_token + auth_token_refresh):
{
"message": "Login successful",
"user": { "id": 42, "email": "[email protected]", "first_name": "Ada" }
}
-c cookies.txt saves the cookies to a jar.
3. Call any authenticated endpoint
-b cookies.txt replays the jar. Every Stats API endpoint is now reachable.
Flow 2, Google OAuth
For interactive apps: redirect the user to GET /auth/google/login, Google redirects to GET /auth/google/callback, the callback sets the same auth_token / auth_token_refresh cookies and redirects to the dashboard. No programmatic shortcut. The user must complete the consent screen.
Use this when you're building a tool a user signs into in their browser. Use OTP for headless / server-side automation.
Using cookies from JavaScript
Browser-side fetch needs credentials: 'include' to attach cookies on cross-origin requests:
const res = await fetch('https://statable.com/api/site/top-stats?website_id=42&period=7d', {
credentials: 'include',
headers: { 'Accept': 'application/json' }
});
const stats = await res.json();
If the access token has expired, the server's Set-Cookie header writes the refreshed auth_token back into the browser's jar. No extra work on your side.
Using cookies from Python
requests.Session keeps the jar:
import requests
s = requests.Session()
# Step 1: request OTP
s.post('https://statable.com/api/auth/send-otp',
json={'email': '[email protected]'})
# Step 2: verify (after the user types the code from email)
s.post('https://statable.com/api/auth/verify-otp',
json={'email': '[email protected]', 'code': '123456'})
# Step 3: call any endpoint, cookies replayed automatically
r = s.get('https://statable.com/api/site/top-stats',
params={'website_id': 42, 'period': '7d'})
print(r.json())
For unattended jobs, persist s.cookies to disk after step 2 (e.g. pickle.dump(s.cookies, open('jar.pkl','wb'))) and reload on the next run. The refresh token lasts 7 days with sliding renewal, so a daily cron with a successful call resets the clock.
Logout
Invalidates the current refresh token server-side and clears both cookies. Other active sessions for the same user aren't affected. Only the token presented in the request is revoked.
What's coming next
API keys + Bearer auth
The next iteration adds long-lived keys you generate in Settings → API Keys:
Each key carries a scope (read-only vs. write) and can be revoked individually. The cookie flow keeps working. Keys are an additional, not a replacement, auth method.
Webhook signatures
Outbound webhooks (planned) will be signed with HMAC-SHA256 over the request body. The signature header and verification example will land here once shipped.
Related, GA4 OAuth
The Stats API also exposes a separate Google OAuth flow used solely to import historical GA4 data (/auth/ga4/connect, /ga4/properties, /ga4/imports/*). That flow grants Statable access to your Google Analytics. It does not log you into Statable. See Import from GA4 for the user-facing walkthrough.
Next step
You have cookies. Head to Endpoints reference for the full surface, or jump to Errors if you're debugging a 401.
Ready to take control of your web analytics? Try Statable free for 30 days — no credit card required, full feature access, GDPR-compliant by default. Start your free trial or view a live demo.