Skip to main content

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.

Create your account and provision keys at the API Key Dashboard.
Finvera supports two authentication models. Pick the one that matches where your code runs:
Key typePrefixWhere it runsHow it authenticates
Publishable keypk_…Browser / mobile / any client-side codeExchanged for a short‑lived session token at the edge
Secret keysk_… / finvera_…Trusted servers onlySent 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:
  1. Inspect every response for x-session-token.
  2. If present, replace the in-memory token with it (and update expires_at).
  3. 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:
StatuserrorMeaning
401publishable_key_requiredx-api-key is missing or not a pk_
401unknown_keypk_ is not recognised
401key_revokedpk_ was revoked
403origin_required / origin_malformedOrigin header missing or unparseable
403origin_not_allowedOrigin is not in this key’s allowlist
403turnstile_token_missingNo cf-turnstile-token header
403turnstile_verify_failedTurnstile siteverify rejected the token
403turnstile_hostname_mismatchWidget hostname doesn’t match Origin
403turnstile_action_mismatchWidget action wasn’t mint_session
403turnstile_cdata_mismatchWidget cdata didn’t match sha256(pk_)
429rate_limited_ip / rate_limited_pk / rate_limited_pk_ipMint rate limit hit
503snapshot_unavailableEdge couldn’t load the key snapshot — retry
Data-plane endpoints (with Authorization: Bearer) add:
StatuserrorMeaning
401session_expiredToken TTL elapsed — refresh it
401session_bad_signature / session_malformedToken was tampered with
401session_mint_window_exceededSession chain hit refresh_window_seconds; re-mint from pk_
401session_revokedUnderlying pk_ was revoked
401session_requiredYou sent a pk_ directly instead of a session token
403session_origin_mismatchRequest Origin differs from the one in the token
403session_network_mismatchCaller’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_

QuestionUse pk_ + sessionUse 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.