# Server adapters and raw HTTP

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:

| File | URL | Runtime | Integrity (SHA-256) |
| --- | --- | --- | --- |
| server.js | https://app.trytelltale.dev/adapters/v1/server.js | Node 18+ (node:crypto, node:timers/promises) | sha256-40PkaM2umvQAJFO8K1ldGkc21DBHVKx8NOWTujk/6PM= |
| identifiers.js | https://app.trytelltale.dev/adapters/v1/identifiers.js | Any | sha256-7WhD86As429qJgiaPyDii+9CSY0T1jjVBecEd4cFZlk= |
| browser.js | https://app.trytelltale.dev/adapters/v1/browser.js | Browser (ES module) | sha256-mzfT5t6/cM7/MEh16BWUywk8DX8yYoJvFxr9uHiTsb4= |
| better-auth.js | https://app.trytelltale.dev/adapters/v1/better-auth.js | Browser (ES module) | sha256-3JGANqoHsA+7j1wIKrbbl4SiFj/+K23sePAYs2UoCFY= |
| better-auth-server.js | https://app.trytelltale.dev/adapters/v1/better-auth-server.js | Server (Fetch API Request/Response) | sha256-vAIWVsGB0ubnxTmnl9xsB7eBi5yto3GTtu4k0Ib58U0= |
| cmp.js | https://app.trytelltale.dev/adapters/v1/cmp.js | Browser (ES module) | sha256-VQPMF3uzbxqv5e3nUPXVVlbXYy/zZE6/oaPkSgDsWwc= |
| identity-digest.js | https://app.trytelltale.dev/adapters/v1/identity-digest.js | Browser or server (Web Crypto) | sha256-pYyUJCGyvvv6msGANwAoSzu7AHVIGLpac9Obi2oxZZs= |
| gpp.js | https://app.trytelltale.dev/adapters/v1/gpp.js | Node 18+ or browser (ES module) | sha256-/PJkVslA3oudeIJ8EIaM9NBcB8sp7IS5FT9NC+pE5VY= |
| api.js | https://app.trytelltale.dev/adapters/v1/api.js | Node 18+ or any runtime with fetch and Web Crypto | sha256-qfQmeMfEz7yld9zHdC+mvvswgMrNhtAE+PW0svFsnCk= |
| mcp.js | https://app.trytelltale.dev/adapters/v1/mcp.js | Node 18+ or any runtime with fetch and Web Crypto | sha256-PuQO7br8r6hqHJww2efp73EEe5eN8ZHHiVVGk+fS2Uo= |
| server.d.ts | https://app.trytelltale.dev/adapters/v1/server.d.ts | TypeScript declarations | sha256-vC+BUCLRiBrwCfafKMvCqmn35qNMSg0h5sydPvlba2Q= |
| identifiers.d.ts | https://app.trytelltale.dev/adapters/v1/identifiers.d.ts | TypeScript declarations | sha256-v00ATdPyRPL5jJyUF+6MYDaIfTrdQx41FukPdD/sn08= |
| browser.d.ts | https://app.trytelltale.dev/adapters/v1/browser.d.ts | TypeScript declarations | sha256-zYsTg7gScpiaTbP4uC6j5taEVvTi5tDzEJG9JYWbNZo= |
| better-auth.d.ts | https://app.trytelltale.dev/adapters/v1/better-auth.d.ts | TypeScript declarations | sha256-V/m6bieX2MAjWrVrmfzBZxEZIwFJSiXG96P6BDUOJEs= |
| better-auth-server.d.ts | https://app.trytelltale.dev/adapters/v1/better-auth-server.d.ts | TypeScript declarations | sha256-EMWd/YtfcLfPbTjxNbhXU5jat5kNxGDRCJT+azLLzZk= |
| cmp.d.ts | https://app.trytelltale.dev/adapters/v1/cmp.d.ts | TypeScript declarations | sha256-MBI7C8Uc+v9A7sWnUAUY0HGWUAdxGAyVJDVP2D0gS34= |
| sdk.d.ts | https://app.trytelltale.dev/adapters/v1/sdk.d.ts | TypeScript declarations | sha256-U/1l+tDz/wcbgZGx9mH21LKuKs0DR/Qz4b6cceJvJd4= |
| mcp.d.ts | https://app.trytelltale.dev/adapters/v1/mcp.d.ts | TypeScript declarations | sha256-4fxKCtTo0FvfFMDlCB6GQZJQ3tOfSiB//pWlFsLhj8A= |
| api.d.ts | https://app.trytelltale.dev/adapters/v1/api.d.ts | TypeScript declarations | sha256-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:

```sh
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:

```sh
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](/docs/api-channel), the middleware for agents calling your own API, and `mcp.js` is [Telltale for MCP servers](/docs/mcp). `gpp.js` decodes an IAB GPP consent string on your server ([Consent bridge](/docs/consent#reading-an-iab-gpp-string-on-your-server)). 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](/docs/webhooks)). 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:

```sh tab=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"}'
```

```javascript tab=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:

- Verify immediately before the protected action, on your server, after your own authentication and input validation.
- A failure is not a verdict. Check the code field: unauthorized means your server key is wrong or missing the Bearer scheme, or the session token is not one this Telltale server issued (malformed, edited or from another Telltale server); session_reinit_required means the browser needs a fresh SDK session and token, so do not resend the old token. [Errors and retries](/docs/errors) lists every code.
- Retry only verification: after a 503 service_unavailable body (small random backoff), or once after a 408 or 429 when its Retry-After fits inside the five-second deadline; at most three attempts within five seconds. Never repeat the protected action itself.
- Unknown is never human. Scores (1, 99, 50) are categorical, not probabilities.

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](/docs/usage-metering).
