API Reference
Quick reference for all Trusted Server HTTP endpoints.
Endpoint Categories
- First-Party Endpoints - Core ad serving and proxying
- Edge Cookie Endpoints - Identity sync and enrichment
- Request Signing - Cryptographic signing and key management
- Admin Diagnostics - Protected EC troubleshooting
- TSJS Library - JavaScript library serving
- Utility Endpoints - Optional operational helpers
- Integration Endpoints - Third-party service proxying
Utility Endpoints
GET /_ts/set-tester
Sets a first-party tester marker cookie for QA or troubleshooting workflows.
Configuration: Disabled by default. Enable with:
[tester_cookie]
enabled = trueResponse when enabled:
- Status:
204 No Content - Headers:
Set-Cookie: ts-tester=true; Domain=<publisher.cookie_domain>; Path=/; Secure; SameSite=Lax
Cache-Control: no-store, privateThe cookie domain comes from [publisher].cookie_domain.
Response when disabled:
- Status:
404 Not Found - Set-Cookie: none
Example:
curl -i "https://edge.example.com/_ts/set-tester"GET /_ts/clear-tester
Clears the first-party tester marker cookie for QA or troubleshooting workflows.
Configuration: Uses the same [tester_cookie].enabled flag as /_ts/set-tester.
Response when enabled:
- Status:
204 No Content - Headers:
Set-Cookie: ts-tester=; Domain=<publisher.cookie_domain>; Path=/; Secure; SameSite=Lax; Max-Age=0
Cache-Control: no-store, privateThe cookie domain comes from [publisher].cookie_domain.
Response when disabled:
- Status:
404 Not Found - Set-Cookie: none
Example:
curl -i "https://edge.example.com/_ts/clear-tester"First-Party Endpoints
GET /first-party/ad
Server-side ad rendering endpoint. Returns complete HTML for a single ad slot.
Query Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
slot | string | Yes | Ad slot identifier (matches ad unit code) |
w | integer | Yes | Ad width in pixels |
h | integer | Yes | Ad height in pixels |
Response:
- Content-Type:
text/html; charset=utf-8 - Body: Complete HTML creative with first-party proxying applied
Example:
curl "https://edge.example.com/first-party/ad?slot=header-banner&w=728&h=90"Response Headers:
No EC ID response header is emitted. EC identity is maintained with the ts-ec cookie.
Use Cases:
- Server-side ad rendering
- Direct iframe embedding
- First-party ad delivery
Edge Cookie Endpoints
Partners are configured statically in [[ec.partners]] and loaded into an in-memory registry at startup. There is no runtime partner-registration endpoint and the legacy browser pixel sync endpoint has been removed; browser-resolved IDs are ingested through Prebid EID cookies.
GET /_ts/api/v1/identify
Returns EC identity plus the authenticated partner's UID and EID for the current user.
Auth: Bearer token (Authorization: Bearer <partner-api-key>)
Request:
- Uses
ts-eccookie and consent signals
Response (example):
{
"ec": "954d...e0c3.nZ1GxL",
"consent": "ok",
"degraded": false,
"source_domain": "formally-vital-lion.edgecompute.app",
"uid": "mock-user-123",
"eid": {
"source": "formally-vital-lion.edgecompute.app",
"uids": [{ "id": "mock-user-123", "atype": 3 }]
},
"cluster_size": 3
}uid, eid, and cluster_size are optional and omitted when unavailable (e.g. no partner UID synced yet, KV read degraded, or cluster size not re-evaluated within the recheck window).
POST /_ts/api/v1/batch-sync
Server-to-server batch sync endpoint for writing EC ID to partner UID mappings. Mapping timestamps are retained in the request schema for compatibility, but they no longer order writes because EC identity entries do not store per-partner sync timestamps. Valid mappings use idempotent last-write-wins semantics.
Auth: Bearer token (Authorization: Bearer <partner-api-key>)
Request Body:
{
"mappings": [
{
"ec_id": "954d8e7398dd993f78e3875ca1ef7841249781240e913157c1f2d6a6c960e0c3.nZ1GxL",
"partner_uid": "mock-user-123",
"timestamp": 1775147300
}
]
}Response:
{
"accepted": 1,
"rejected": 0,
"errors": []
}POST /third-party/ad
Client-side auction endpoint for TSJS library.
Request Body:
{
"adUnits": [
{
"code": "header-banner",
"mediaTypes": {
"banner": {
"sizes": [
[728, 90],
[970, 250]
]
}
}
}
],
"config": {
"debug": false
}
}Response:
{
"seatbid": [
{
"bid": [
{
"impid": "header-banner",
"adm": "<html>...</html>",
"price": 1.5,
"w": 728,
"h": 90
}
]
}
]
}Example:
curl -X POST https://edge.example.com/third-party/ad \
-H "Content-Type: application/json" \
-d '{"adUnits":[{"code":"banner","mediaTypes":{"banner":{"sizes":[[300,250]]}}}]}'GET /first-party/proxy
Unified proxy for resources referenced by creatives (images, scripts, CSS, etc.).
Query Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
tsurl | string | Yes | Target URL without query parameters (base URL) |
tstoken | string | Yes | Base64 URL-safe SHA-256 digest of encrypted full target URL |
* | any | No | Original target URL query parameters (preserved as-is) |
Response:
- Content-Type: Mirrors upstream or inferred from content
- Body: Proxied resource content
- HTML responses: Rewritten with creative processor
- Image responses: Proxied with content-type inference
- Other: Passed through
Behavior:
- Validates
tstokenagainst reconstructed URL - Follows redirects (301/302/303/307/308, max 4 hops)
- Injects EC ID as
ts-ecquery parameter - Logs 1×1 pixel impressions
Example:
# Original URL: https://ad.doubleclick.net/pixel?id=123&type=view
# Signed proxy URL:
curl "https://edge.example.com/first-party/proxy?tsurl=https://ad.doubleclick.net/pixel&id=123&type=view&tstoken=abc123xyz..."Error Responses:
400 Bad Request- Missing or invalidtstoken403 Forbidden- Token validation failed500 Internal Server Error- Upstream fetch failed
GET /first-party/click
Click tracking redirect endpoint.
Query Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
tsurl | string | Yes | Target redirect URL without query parameters |
tstoken | string | Yes | Base64 URL-safe SHA-256 digest of encrypted full target URL |
* | any | No | Original target URL query parameters |
Response:
- Status:
302 Found - Location: Reconstructed target URL with EC ID injected
Behavior:
- Validates
tstokenagainst reconstructed URL - Injects
ts-ecquery parameter - Logs click metadata (tsurl, referer, user agent)
- Does not proxy content (redirect only)
Example:
curl -I "https://edge.example.com/first-party/click?tsurl=https://advertiser.com/landing&campaign=123&tstoken=xyz..."
# → 302 Location: https://advertiser.com/landing?campaign=123&ts-ec=abc123GET/POST /first-party/sign
URL signing endpoint. Returns signed first-party proxy URL for a given target URL.
Request Methods: GET or POST
GET Request:
curl "https://edge.example.com/first-party/sign?url=https://external.com/pixel.gif"POST Request:
curl -X POST https://edge.example.com/first-party/sign \
-H "Content-Type: application/json" \
-d '{"url":"https://external.com/pixel.gif"}'Response:
{
"signed_url": "https://edge.example.com/first-party/proxy?tsurl=https://external.com/pixel.gif&tstoken=abc123..."
}Use Cases:
- TSJS creative runtime (image/iframe proxying)
- Dynamic URL signing in client-side code
- Testing proxy URL generation
POST /first-party/proxy-rebuild
URL mutation recovery endpoint. Re-signs a click URL after creative JavaScript modifies its query parameters. The original tstoken is validated first, and tsurl, tstoken, and tsexp can never be added or removed.
Request Body:
{
"tsclick": "/first-party/click?tsurl=https%3A%2F%2Fadvertiser.example&campaign=123&tstoken=original...",
"add": {
"utm_source": "banner"
},
"del": ["old_param"]
}tsclick may be root-relative (the form the rewriter emits) or absolute.
Response:
{
"href": "/first-party/click?tsurl=https%3A%2F%2Fadvertiser.example&campaign=123&utm_source=banner&tstoken=new...",
"base": "https://advertiser.example",
"added": { "utm_source": "banner" },
"removed": ["old_param"]
}Use Cases:
- TSJS click guard (automatic URL repair) on same-origin pages
- Handling creative JavaScript that modifies tracking URLs
GET /first-party/proxy-rebuild
Navigation form of the same recovery, for creatives rendered in a sandboxed iframe without allow-same-origin. Their opaque origin makes the JSON POST a CORS-preflighted cross-origin request that the endpoint does not answer, so the click guard navigates here instead — navigations are not subject to CORS.
Query Parameters:
| Parameter | Required | Description |
|---|---|---|
tsclick | Yes | URL-encoded signed click URL |
add | No | URL-encoded JSON object of parameters to add |
del | No | URL-encoded JSON array of parameter names to drop |
Response: 302 with the rebuilt /first-party/click?... URL in Location and Cache-Control: no-store, private. The browser follows it to /first-party/click, which redirects on to the advertiser.
Validation is identical to the POST form.
Form-encoded POST (navigation, no URL length limit): when the GET recovery URL would exceed the platform's request-URL limit (Fastly Compute rejects request URLs over 8192 bytes before the handler runs), the click guard submits a form instead. A POST carrying Content-Type: application/x-www-form-urlencoded with the same tsclick/add/del fields is treated as a navigation and answered with the same 302, not the JSON body.
Request Signing Endpoints
GET /.well-known/trusted-server.json
Returns the Trusted Server discovery document, which includes active public keys in JWKS format for signature verification.
Response:
{
"version": "1.0",
"jwks": {
"keys": [
{
"kty": "OKP",
"crv": "Ed25519",
"kid": "ts-2025-01-A",
"use": "sig",
"x": "UVTi04QLrIuB7jXpVfHjUTVN5aIdcbPNr50umTtN8pw"
}
]
}
}Example:
curl https://edge.example.com/.well-known/trusted-server.jsonUse Cases:
- Signature verification by downstream systems
- Key rotation validation
- Integration testing
POST /verify-signature
Verifies a signature against a payload and key ID.
Request Body:
{
"payload": "base64-encoded-data",
"signature": "base64-signature",
"kid": "ts-2025-01-A"
}Response (Success):
{
"verified": true,
"kid": "ts-2025-01-A",
"message": "Signature verified successfully"
}Response (Failure):
{
"verified": false,
"kid": "ts-2025-01-A",
"message": "Invalid signature"
}Example:
curl -X POST https://edge.example.com/verify-signature \
-H "Content-Type: application/json" \
-d '{"payload":"SGVsbG8gV29ybGQ=","signature":"abc123...","kid":"ts-2025-01-A"}'POST /_ts/admin/keys/rotate
Generates and activates a new signing key.
Authentication: Requires basic auth (configured via handlers in trusted-server.toml)
Request Body (Optional):
{
"kid": "custom-key-id"
}If omitted, auto-generates date-based ID (e.g., ts-2025-01-15-A).
Response:
{
"new_kid": "ts-2025-01-15-A",
"previous_kid": "ts-2025-01-14-A",
"active_kids": ["ts-2025-01-15-A", "ts-2025-01-14-A"],
"message": "Key rotation successful"
}Example:
curl -X POST https://edge.example.com/_ts/admin/keys/rotate \
-u admin:password \
-H "Content-Type: application/json"Behavior:
- Keeps both new and previous key active
- Updates
current-kidto new key - Preserves old key for graceful transition
See Key Rotation Guide for workflow details.
POST /_ts/admin/keys/deactivate
Deactivates or deletes a signing key.
Authentication: Requires basic auth
Request Body:
{
"kid": "ts-2025-01-14-A",
"delete": false
}| Field | Type | Required | Description |
|---|---|---|---|
kid | string | Yes | Key ID to deactivate |
delete | boolean | No | If true, permanently removes key (default: false) |
Response:
{
"kid": "ts-2025-01-14-A",
"active_kids": ["ts-2025-01-15-A"],
"message": "Key deactivated successfully"
}Example:
curl -X POST https://edge.example.com/_ts/admin/keys/deactivate \
-u admin:password \
-H "Content-Type: application/json" \
-d '{"kid":"ts-2025-01-14-A","delete":true}'Admin Diagnostic Endpoints
These endpoints expose sensitive identity and cookie data and require HTTP Basic Authentication. Configure a handler that covers the entire /_ts/admin namespace; startup rejects configurations that do not protect every admin route, including handlers that match only some /_ts/admin/ec/{id} values — the dynamic route needs a prefix-level matcher such as ^/_ts/admin or ^/_ts/admin/ec/. The whole /_ts/admin prefix is reserved: any admin path that reaches publisher fallback — unknown, malformed, or percent-encoded (/_ts/admin%2Fec) — is answered locally with 404 and is never proxied, so an admin Authorization header and request body never reach the publisher origin. The retired non-/_ts /admin/keys aliases are reserved the same way. Normal diagnostic-handler responses after successful authentication are JSON with Cache-Control: no-store. Missing or invalid credentials receive the shared plaintext 401 Unauthorized Basic-auth challenge. Unexpected configuration or KV failures use the adapter's shared plaintext 5xx error response. Those authentication and internal-error responses are outside the diagnostic JSON and cache-header contract.
The examples below use fictional IDs and values only.
GET /_ts/admin/ec
GET /_ts/admin/ec/{id}
Reads an EC identity-graph record for troubleshooting. The explicit route accepts an EC ID in {64 lowercase hex}.{6 alphanumeric} format. The bare route uses the request's ts-ec cookie.
This lookup is implemented only by the Fastly adapter because the identity graph is stored in Fastly KV. Other adapters return 501 Not Implemented.
Response fields:
ec_id,store, andgenerationidentify the raw KV lookup.entrypreserves the stored JSON shape, including unknown and legacy fields. Derivedcreated_isoandconsent.updated_isofields are added only when absent.metadatapreserves the stored metadata JSON shape.tombstonereports whether consent has been withdrawn. It is absent when the entry body cannot be parsed as JSON or deserialized as the typed EC schema.auction.eidspreviews the partner EIDs the stored record can contribute;auction.skippedexplains filtered IDs.entry_error,metadata_error, andraw_bodykeep malformed or schema-incompatible records inspectable.
The auction preview validates the stored record and partner configuration, but cannot reproduce live per-request consent checks. It must not be treated as proof that a specific auction request will receive those EIDs.
Status codes:
| Status | Meaning |
|---|---|
200 | Record found, including inspectable corrupt records |
400 | Invalid explicit EC ID |
401 | Missing or invalid Basic credentials |
404 | Record not found, or the bare route has no ts-ec cookie |
405 | Method other than GET (Allow: GET) |
501 | EC identity graph unavailable on this adapter or deployment |
5xx | Unexpected configuration or KV failure (plaintext) |
curl -u admin:secure-password \
"https://edge.example.com/_ts/admin/ec/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.abc123"
curl -u admin:secure-password \
--cookie "ts-ec=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.abc123" \
"https://edge.example.com/_ts/admin/ec"GET /_ts/admin/eids
Parses the request's ts-eids and sharedId cookies and previews which configured partner IDs cookie ingestion would match or drop. It performs request inspection only: it does not read or write KV and is available on every adapter.
After successful authentication this endpoint always returns 200 OK; missing or malformed cookies are represented by cookie_present, sharedid_present, and parse_error. The ingest.matched and ingest.unmatched arrays show the ingestion preview. Each unmatched entry contains its source and either a no_partner reason when no configured partner recognizes it or no_valid_uid when the partner exists but every supplied UID is empty or exceeds the storage limit.
{
"ingest": {
"matched": [
{
"source_domain": "configured.example",
"uid": "fictional-uid"
}
],
"unmatched": [
{
"source": "unknown.example",
"reason": "no_partner"
}
]
}
}curl -u admin:secure-password \
--cookie "sharedId=fictional-shared-id" \
"https://edge.example.com/_ts/admin/eids"Malformed diagnostic paths return a local 404, and unsupported methods return a local 405; they are never forwarded to the publisher origin.
TSJS Library Endpoint
GET /static/tsjs=<filename>
Serves the TSJS (Trusted Server JavaScript) library.
Path Pattern: /static/tsjs=<filename>?v=<hash>
Supported Filenames:
tsjs-unified.jstsjs-unified.min.js
Query Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
v | string | No | Cache-busting hash (SHA256 of bundle contents) |
Response:
- Content-Type:
application/javascript; charset=utf-8 - Body: TSJS bundle (IIFE format)
- Headers: ETag for caching
Example:
<script
src="/static/tsjs=tsjs-unified.min.js?v=a1b2c3d4..."
id="trustedserver-js"
></script>Module Selection: All integration modules are built at compile time. At runtime, the server concatenates only the modules whose integrations are enabled in trusted-server.toml (or env vars). No rebuild is required to change the module set.
Integration Endpoints
Prebid Integration
GET /first-party/ad
See First-Party Endpoints above.
POST /third-party/ad
See First-Party Endpoints above.
GET /prebid.js (Optional)
Returns empty JavaScript to override Prebid.js when script_handler is configured.
Configuration:
[integrations.prebid]
script_handler = "/prebid.js"Response:
- Content-Type:
application/javascript; charset=utf-8 - Body:
// Prebid.js override by Trusted Server - Cache:
immutable, max-age=31536000
Permutive Integration
GET /integrations/permutive/sdk
Serves Permutive SDK from first-party domain.
Response:
- Content-Type:
application/javascript; charset=utf-8 - Body: Permutive SDK fetched from
{organization_id}.edge.permutive.app/{workspace_id}-web.js - Cache: 1 hour (configurable via
cache_ttl_seconds)
GET/POST /integrations/permutive/api/*
Proxies to api.permutive.com.
Example:
curl https://edge.example.com/integrations/permutive/api/settings
# → Proxies to https://api.permutive.com/settingsGET/POST /integrations/permutive/secure-signal/*
Proxies to secure-signals.permutive.app.
GET/POST /integrations/permutive/events/*
Proxies to events.permutive.app for event tracking.
GET/POST /integrations/permutive/sync/*
Proxies to sync.permutive.com for ID synchronization.
GET /integrations/permutive/cdn/*
Proxies to cdn.permutive.com for static assets.
Testlight Integration
POST /integrations/testlight/auction
Testing auction endpoint with EC ID injection.
Request Body:
{
"user": {
"id": null
},
"imp": [{ "id": "slot-1" }]
}Response: Proxies to configured endpoint with user.id populated with EC ID.
Response Headers:
No EC ID response header is emitted. EC identity is maintained with the ts-ec cookie.
Error Responses
All endpoints use consistent error response format:
{
"error": "Error type",
"message": "Detailed error description",
"details": {
"field": "Additional context"
}
}Common HTTP Status Codes:
| Code | Meaning | Common Causes |
|---|---|---|
| 400 | Bad Request | Missing required parameters, invalid JSON |
| 401 | Unauthorized | Missing or invalid basic auth credentials |
| 403 | Forbidden | Invalid token signature, disabled integration |
| 404 | Not Found | Unknown endpoint, missing resource |
| 500 | Internal Server Error | Upstream service failure, configuration error |
| 502 | Bad Gateway | Backend service unavailable |
| 504 | Gateway Timeout | Backend service timeout exceeded |
Authentication
Basic Authentication
Endpoints under protected paths require HTTP Basic Authentication:
Configuration:
[[handlers]]
path = "^/_ts/admin"
username = "admin"
password = "secure-password"Usage:
curl -u admin:secure-password https://edge.example.com/_ts/admin/keys/rotateProtected Endpoints:
/_ts/admin/keys/rotate/_ts/admin/keys/deactivate/_ts/admin/ec/_ts/admin/ec/{id}/_ts/admin/eids- Any paths matching configured
handlerspatterns
Rate Limiting
Trusted Server relies on Fastly's built-in rate limiting. Configure in Fastly dashboard:
Recommended Limits:
- Public endpoints: 1000 req/min per IP
- Admin endpoints: 10 req/min per IP
- TSJS serving: 10000 req/min (highly cacheable)
CORS
CORS headers are not enabled by default. Configure response_headers for cross-origin requests:
[response_headers]
Access-Control-Allow-Origin = "*"
Access-Control-Allow-Methods = "GET, POST, OPTIONS"
Access-Control-Allow-Headers = "Content-Type, Authorization"Next Steps
- Explore Integrations Overview
- Learn about Configuration
- Review Error Reference
- Understand Configuration Reference