Server adapters and raw HTTP

View as Markdown

You can call the server API with the dependency-free adapter files this Telltale server serves, or directly over HTTP from any language. No npm package is published.

Download the adapters

Each file is plain JavaScript with no dependencies. The table is generated from the files this Telltale server serves right now:

Download the adapters
FileURLRuntimeIntegrity (SHA-256)
server.jshttps://app.trytelltale.dev/adapters/v1/server.jsNode 18+ (node:crypto, node:timers/promises)sha256-40PkaM2umvQAJFO8K1ldGkc21DBHVKx8NOWTujk/6PM=
identifiers.jshttps://app.trytelltale.dev/adapters/v1/identifiers.jsAnysha256-7WhD86As429qJgiaPyDii+9CSY0T1jjVBecEd4cFZlk=
browser.jshttps://app.trytelltale.dev/adapters/v1/browser.jsBrowser (ES module)sha256-mzfT5t6/cM7/MEh16BWUywk8DX8yYoJvFxr9uHiTsb4=
better-auth.jshttps://app.trytelltale.dev/adapters/v1/better-auth.jsBrowser (ES module)sha256-3JGANqoHsA+7j1wIKrbbl4SiFj/+K23sePAYs2UoCFY=
better-auth-server.jshttps://app.trytelltale.dev/adapters/v1/better-auth-server.jsServer (Fetch API Request/Response)sha256-vAIWVsGB0ubnxTmnl9xsB7eBi5yto3GTtu4k0Ib58U0=
cmp.jshttps://app.trytelltale.dev/adapters/v1/cmp.jsBrowser (ES module)sha256-VQPMF3uzbxqv5e3nUPXVVlbXYy/zZE6/oaPkSgDsWwc=
identity-digest.jshttps://app.trytelltale.dev/adapters/v1/identity-digest.jsBrowser or server (Web Crypto)sha256-pYyUJCGyvvv6msGANwAoSzu7AHVIGLpac9Obi2oxZZs=
gpp.jshttps://app.trytelltale.dev/adapters/v1/gpp.jsNode 18+ or browser (ES module)sha256-/PJkVslA3oudeIJ8EIaM9NBcB8sp7IS5FT9NC+pE5VY=
api.jshttps://app.trytelltale.dev/adapters/v1/api.jsNode 18+ or any runtime with fetch and Web Cryptosha256-qfQmeMfEz7yld9zHdC+mvvswgMrNhtAE+PW0svFsnCk=
mcp.jshttps://app.trytelltale.dev/adapters/v1/mcp.jsNode 18+ or any runtime with fetch and Web Cryptosha256-PuQO7br8r6hqHJww2efp73EEe5eN8ZHHiVVGk+fS2Uo=
server.d.tshttps://app.trytelltale.dev/adapters/v1/server.d.tsTypeScript declarationssha256-vC+BUCLRiBrwCfafKMvCqmn35qNMSg0h5sydPvlba2Q=
identifiers.d.tshttps://app.trytelltale.dev/adapters/v1/identifiers.d.tsTypeScript declarationssha256-v00ATdPyRPL5jJyUF+6MYDaIfTrdQx41FukPdD/sn08=
browser.d.tshttps://app.trytelltale.dev/adapters/v1/browser.d.tsTypeScript declarationssha256-zYsTg7gScpiaTbP4uC6j5taEVvTi5tDzEJG9JYWbNZo=
better-auth.d.tshttps://app.trytelltale.dev/adapters/v1/better-auth.d.tsTypeScript declarationssha256-V/m6bieX2MAjWrVrmfzBZxEZIwFJSiXG96P6BDUOJEs=
better-auth-server.d.tshttps://app.trytelltale.dev/adapters/v1/better-auth-server.d.tsTypeScript declarationssha256-EMWd/YtfcLfPbTjxNbhXU5jat5kNxGDRCJT+azLLzZk=
cmp.d.tshttps://app.trytelltale.dev/adapters/v1/cmp.d.tsTypeScript declarationssha256-MBI7C8Uc+v9A7sWnUAUY0HGWUAdxGAyVJDVP2D0gS34=
sdk.d.tshttps://app.trytelltale.dev/adapters/v1/sdk.d.tsTypeScript declarationssha256-U/1l+tDz/wcbgZGx9mH21LKuKs0DR/Qz4b6cceJvJd4=
mcp.d.tshttps://app.trytelltale.dev/adapters/v1/mcp.d.tsTypeScript declarationssha256-4fxKCtTo0FvfFMDlCB6GQZJQ3tOfSiB//pWlFsLhj8A=
api.d.tshttps://app.trytelltale.dev/adapters/v1/api.d.tsTypeScript declarationssha256-ztd27zvuP1tH0RcOyE1zF5TBPzOl9hCppHFyxojZQ88=

server.js imports ./identifiers.js, and better-auth.js imports ./browser.js and ./identity-digest.js, so download a file's companions into the same folder. Every .js file is also served at the unversioned alias /adapters/NAME.js. That alias is deprecated and will be removed on April 30, 2027: its responses carry Sunset, Deprecation and a Link to the /adapters/v1/ path. Use the /adapters/v1/ path in code you commit.

v1 is the adapter API generation, not a frozen byte stream. Record the integrity you reviewed and check it whenever you download again:

Shell
mkdir -p lib/telltale && cd lib/telltale
curl -fsSO https://app.trytelltale.dev/adapters/v1/server.js
curl -fsSO https://app.trytelltale.dev/adapters/v1/identifiers.js
printf '{ "type": "module" }\n' > package.json   # the adapters are ES modules, even in a CommonJS project
# Compare with the Integrity column above (sha256-BASE64):
echo "sha256-$(openssl dgst -sha256 -binary server.js | openssl base64 -A)"

The adapters are ES modules. The package.json line marks that folder as ES modules, so they load in a CommonJS project too (what npm init writes): import them from an ES module (a .mjs file, or any file in a "type": "module" project), or require() them on Node 22.12 or later.

Responses also carry a Repr-Digest header (sha-256=:BASE64:) with the same value. Vendor the files into your repository rather than importing them from the Telltale server at runtime.

TypeScript types

Each adapter has a declaration file beside it (the .d.ts rows in the table): server.d.ts, identifiers.d.ts, browser.d.ts, better-auth.d.ts, better-auth-server.d.ts and cmp.d.ts. sdk.d.ts types the hosted SDK global, window.Telltale, including client.status(), getSessionToken({timeoutMs}) and the network_unavailable error code. Download the .d.ts next to its .js file and TypeScript picks it up:

Shell
curl -fsSO https://app.trytelltale.dev/adapters/v1/server.d.ts
curl -fsSO https://app.trytelltale.dev/adapters/v1/identifiers.d.ts
curl -fsSO https://app.trytelltale.dev/adapters/v1/sdk.d.ts   # browser code: import type { TelltaleClient } from './sdk.js'

MCP and API middleware

api.js is Telltale for APIs, the middleware for agents calling your own API, and mcp.js is Telltale for MCP servers. gpp.js decodes an IAB GPP consent string on your server (Consent bridge). Download them like the others and check their integrity the same way.

Use the server adapter

JavaScript
import { createTelltaleServer } from './lib/telltale/server.js';

const telltale = createTelltaleServer({
  endpoint: 'https://app.trytelltale.dev',
  serverKey: process.env.TELLTALE_SERVER_KEY
});

const evidence = await telltale.verify({ sessionToken, actionId: 'export_report' });
// evidence.verdict is human, agent or unknown; evidence.decision is allow, review or deny.

The adapter validates every response, applies a five-second deadline, preserves your caller's cancellation and retries verification only after a valid 503 response, or once after a 408 or 429 whose Retry-After still fits inside the deadline (at most three attempts in that deadline). requireAllowed() rejects review and deny. identify() and recordUsage() never retry automatically. verifyWebhook() checks Standard Webhooks deliveries (Webhooks guide). Your application still owns authentication, account authorization and the protected action.

Raw HTTP

Every operation takes the Site server key as a bearer token and JSON bodies. The API reference pages and /openapi.json are generated from the running contract; if anything here differs, they win. The same verification call with curl, or with fetch for runtimes without the adapter:

curl
curl -sS -X POST https://app.trytelltale.dev/api/v1/verify \
  -H "Authorization: Bearer $TELLTALE_SERVER_KEY" \
  -H 'Content-Type: application/json' \
  --max-time 5 \
  --data '{"sessionToken":"TOKEN_FROM_THE_BROWSER","actionId":"export_report"}'
fetch
async function verify(sessionToken, actionId, signal) {
  const deadline = AbortSignal.timeout(5000);
  const response = await fetch('https://app.trytelltale.dev/api/v1/verify', {
    method: 'POST',
    headers: { Authorization: 'Bearer ' + process.env.TELLTALE_SERVER_KEY, 'Content-Type': 'application/json' },
    body: JSON.stringify({ sessionToken, actionId }),
    // AbortSignal.any() throws on undefined, so combine only when the caller passed a signal.
    signal: signal ? AbortSignal.any([signal, deadline]) : deadline
  });
  const body = await response.json();
  if (!response.ok) throw Object.assign(new Error(body.code || 'verification_failed'), { status: response.status, code: body.code });
  if (!['human', 'agent', 'unknown'].includes(body.verdict) || !['allow', 'review', 'deny'].includes(body.decision)) throw new Error('invalid_verification_response');
  return body; // { verdict, score, decision, stale, policy, reasons, ... }
}

Rules that apply whatever client you use:

Server-verified identity (POST /api/v1/identify) and metering (POST /api/v1/usage) use the same headers; see their API reference pages and the Usage metering guide.