Documentation Index
Fetch the complete documentation index at: https://docs.finvera.ai/llms.txt
Use this file to discover all available pages before exploring further.
Finvera supports two authentication models. Pick the one that matches where your code runs:
| Key type | Prefix | Where it runs | How it authenticates |
|---|
| Publishable key | pk_… | Browser / mobile / any client-side code | Exchanged for a short‑lived session token at the edge |
| Secret key | sk_… / finvera_… | Trusted servers only | Sent directly on every request as Authorization: Bearer |
The base URL for all requests is https://api.finvera.news.
Client-side: publishable keys + session tokens
Publishable keys are safe to ship in client bundles, but on their own they cannot call data endpoints. A client first exchanges its pk_ for a session token at the Finvera edge, then uses that session token as a bearer credential on every subsequent call.
Each session is bound to:
- the publishable key it was minted for,
- the browser Origin that minted it (must match the key’s allowlist), and
- the caller’s network prefix (IPv4 /24 or IPv6 /48).
A leaked session token therefore cannot be replayed from a different site or a different network, and any session can be revoked instantly by revoking its parent pk_.
Flow at a glance
Browser Finvera Edge
│ │
│ 1. Solve Turnstile challenge │
│ ─────────────────────────────────────►│
│ │
│ 2. POST /v1/session │
│ x-api-key: pk_… │
│ cf-turnstile-token: … │
│ Origin: https://yourapp.com │
│ ─────────────────────────────────────►│ verify Turnstile,
│ │ check pk + origin,
│ │ rate-limit,
│ ◄─────────────────────────────────────│ mint session JWT
│ { token, expires_in, … } │
│ │
│ 3. GET /kms/api/v1/… │
│ Authorization: Bearer <token> │
│ ─────────────────────────────────────►│ verify session,
│ ◄─────────────────────────────────────│ forward to origin
│ 200 OK + JSON │
1. Solve a Turnstile challenge
The mint endpoint is protected by Cloudflare Turnstile. Render the widget on the page that will mint the session and pass the resulting token along with the mint request.
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>
<div class="cf-turnstile"
data-sitekey="YOUR_TURNSTILE_SITE_KEY"
data-action="mint_session"
data-cdata="SHA256_OF_YOUR_PK"
data-callback="onTurnstileToken"></div>
Required widget configuration:
data-action must equal mint_session. Tokens issued for any other action are rejected.
data-cdata should be the lowercase hex SHA-256 of your publishable key. This binds the challenge to the specific pk_ so a token harvested under one key on the same site cannot be reused with another. (Only enforced on interactive widgets; invisible widgets may omit it.)
- The widget’s hostname must match the
Origin of the mint request — Turnstile is bound to the page that served it.
The widget invokes your callback with a single-use token. Send that token to /v1/session immediately.
2. Mint a session token
curl -X POST https://api.finvera.news/v1/session \
-H "x-api-key: pk_live_xxxxxxxxxxxxxxxx" \
-H "Origin: https://yourapp.com" \
-H "cf-turnstile-token: <token-from-widget>"
Response (200):
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.…",
"token_type": "Bearer",
"expires_in": 900,
"expires_at": 1747058400,
"refresh_window_seconds": 28800,
"action": "mint_session"
}
token — opaque JWT to send on subsequent requests.
expires_in — seconds until this token expires (default 15 min).
refresh_window_seconds — the maximum lifetime of a session chain. Once this much time has passed since the original mint, you must mint a fresh session from pk_ again.
3. Call API endpoints with the session token
Every other Finvera endpoint accepts the session token as a standard bearer credential:
curl https://api.finvera.news/kms/api/v1/press-releases \
-H "Authorization: Bearer <session-token>"
The request must come from the same Origin and the same network prefix the session was minted on. Sending a pk_ directly to a data endpoint (without first exchanging it) returns 401 session_required.
4. Sliding refresh (zero-effort token rotation)
When a request is served past the halfway point of the session’s TTL — and the session chain is still within refresh_window_seconds — the edge mints a fresh token and returns it on the response:
x-session-token: eyJhbGciOi… ← replace your in-memory token with this
x-session-expires-at: 1747059300
These headers are exposed via CORS (Access-Control-Expose-Headers), so browsers can read them. Clients should:
- Inspect every response for
x-session-token.
- If present, replace the in-memory token with it (and update
expires_at).
- When the refresh window is exhausted, restart from step 1 — solve Turnstile and call
/v1/session again.
Error responses
/v1/session returns JSON {"error":"<reason>"} with these status codes:
| Status | error | Meaning |
|---|
| 401 | publishable_key_required | x-api-key is missing or not a pk_ |
| 401 | unknown_key | pk_ is not recognised |
| 401 | key_revoked | pk_ was revoked |
| 403 | origin_required / origin_malformed | Origin header missing or unparseable |
| 403 | origin_not_allowed | Origin is not in this key’s allowlist |
| 403 | turnstile_token_missing | No cf-turnstile-token header |
| 403 | turnstile_verify_failed | Turnstile siteverify rejected the token |
| 403 | turnstile_hostname_mismatch | Widget hostname doesn’t match Origin |
| 403 | turnstile_action_mismatch | Widget action wasn’t mint_session |
| 403 | turnstile_cdata_mismatch | Widget cdata didn’t match sha256(pk_) |
| 429 | rate_limited_ip / rate_limited_pk / rate_limited_pk_ip | Mint rate limit hit |
| 503 | snapshot_unavailable | Edge couldn’t load the key snapshot — retry |
Data-plane endpoints (with Authorization: Bearer) add:
| Status | error | Meaning |
|---|
| 401 | session_expired | Token TTL elapsed — refresh it |
| 401 | session_bad_signature / session_malformed | Token was tampered with |
| 401 | session_mint_window_exceeded | Session chain hit refresh_window_seconds; re-mint from pk_ |
| 401 | session_revoked | Underlying pk_ was revoked |
| 401 | session_required | You sent a pk_ directly instead of a session token |
| 403 | session_origin_mismatch | Request Origin differs from the one in the token |
| 403 | session_network_mismatch | Caller’s network prefix differs from the one in the token |
Browser implementation (JavaScript / TypeScript)
const API_BASE = "https://api.finvera.news";
const PUBLISHABLE_KEY = "pk_live_xxxxxxxxxxxxxxxx";
class FinveraClient {
constructor() {
this.token = null;
this.expiresAt = 0;
}
// Called once after the Turnstile widget produces a token.
async mintSession(turnstileToken) {
const res = await fetch(`${API_BASE}/v1/session`, {
method: "POST",
headers: {
"x-api-key": PUBLISHABLE_KEY,
"cf-turnstile-token": turnstileToken,
},
});
if (!res.ok) {
const { error } = await res.json().catch(() => ({}));
throw new Error(`session mint failed: ${error ?? res.status}`);
}
const body = await res.json();
this.token = body.token;
this.expiresAt = body.expires_at * 1000;
}
async request(path, init = {}) {
if (!this.token || Date.now() >= this.expiresAt) {
throw new Error("no valid session — re-solve Turnstile and call mintSession()");
}
const res = await fetch(`${API_BASE}${path}`, {
...init,
headers: {
...(init.headers ?? {}),
Authorization: `Bearer ${this.token}`,
},
});
// Sliding refresh — pick up the rotated token if the edge issued one.
const refreshed = res.headers.get("x-session-token");
if (refreshed) {
this.token = refreshed;
this.expiresAt = Number(res.headers.get("x-session-expires-at")) * 1000;
}
return res;
}
}
A few practical notes:
- Keep the session token in memory only. Never persist it to
localStorage — its short TTL plus origin/network binding is the protection.
- The
Origin header is set automatically by the browser; don’t try to override it.
- If you get
session_network_mismatch you’ve roamed networks (e.g. Wi-Fi → cellular). Mint a fresh session.
Server-to-server: secret keys
For backend services, batch jobs, or any code running outside a browser, use a secret key (sk_… or finvera_…). Secret keys do not go through the session flow — they are sent directly on every request and validated at the origin.
curl https://api.finvera.news/kms/api/v1/press-releases \
-H "Authorization: Bearer sk_live_xxxxxxxxxxxxxxxx"
import os
import requests
API_BASE = "https://api.finvera.news"
class FinveraClient:
def __init__(self, secret_key: str):
self.session = requests.Session()
self.session.headers["Authorization"] = f"Bearer {secret_key}"
def get(self, path: str, **kwargs):
return self.session.get(f"{API_BASE}{path}", **kwargs)
client = FinveraClient(os.environ["FINVERA_SECRET_KEY"])
resp = client.get("/kms/api/v1/press-releases")
resp.raise_for_status()
print(resp.json())
Treat secret keys like any other credential:
- Store them in your secret manager (Vault, AWS Secrets Manager, Doppler, …), never in source control.
- Rotate them from the dashboard if you suspect exposure — revocation propagates to the edge within ~70 seconds.
- Do not ship secret keys to browsers, mobile apps, or any user-controlled environment. Use a publishable key there instead.
Choosing between pk_ and sk_
| Question | Use pk_ + session | Use sk_ |
|---|
| Will the code run in a browser, mobile app, or other untrusted environment? | ✅ | ❌ |
| Do you need per-Origin and per-network binding? | ✅ | ❌ |
| Is the caller a server you control? | ❌ | ✅ |
| Do you need long-lived, non-interactive access (cron, ETL)? | ❌ | ✅ |
If you need both — a public web app and a backend that calls Finvera — provision one of each from the dashboard.