Skip to content

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.


After successful login Statable sets two cookies:

CookieLifetimePurpose
auth_token15 minutesShort-lived JWT access token. Sent with every request.
auth_token_refresh7 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

curl https://statable.com/api/auth/me -b cookies.txt

-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

curl -X POST https://statable.com/api/auth/logout -b cookies.txt

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:

Authorization: Bearer <your-statable-api-key>

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.


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.