Skip to content

API Reference

Quick reference for all Trusted Server HTTP endpoints.

Endpoint Categories


Utility Endpoints

GET /_ts/set-tester

Sets a first-party tester marker cookie for QA or troubleshooting workflows.

Configuration: Disabled by default. Enable with:

toml
[tester_cookie]
enabled = true

Response when enabled:

  • Status: 204 No Content
  • Headers:
http
Set-Cookie: ts-tester=true; Domain=<publisher.cookie_domain>; Path=/; Secure; SameSite=Lax
Cache-Control: no-store, private

The cookie domain comes from [publisher].cookie_domain.

Response when disabled:

  • Status: 404 Not Found
  • Set-Cookie: none

Example:

bash
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:
http
Set-Cookie: ts-tester=; Domain=<publisher.cookie_domain>; Path=/; Secure; SameSite=Lax; Max-Age=0
Cache-Control: no-store, private

The cookie domain comes from [publisher].cookie_domain.

Response when disabled:

  • Status: 404 Not Found
  • Set-Cookie: none

Example:

bash
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:

ParameterTypeRequiredDescription
slotstringYesAd slot identifier (matches ad unit code)
wintegerYesAd width in pixels
hintegerYesAd height in pixels

Response:

  • Content-Type: text/html; charset=utf-8
  • Body: Complete HTML creative with first-party proxying applied

Example:

bash
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

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-ec cookie and consent signals

Response (example):

json
{
  "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:

json
{
  "mappings": [
    {
      "ec_id": "954d8e7398dd993f78e3875ca1ef7841249781240e913157c1f2d6a6c960e0c3.nZ1GxL",
      "partner_uid": "mock-user-123",
      "timestamp": 1775147300
    }
  ]
}

Response:

json
{
  "accepted": 1,
  "rejected": 0,
  "errors": []
}

POST /third-party/ad

Client-side auction endpoint for TSJS library.

Request Body:

json
{
  "adUnits": [
    {
      "code": "header-banner",
      "mediaTypes": {
        "banner": {
          "sizes": [
            [728, 90],
            [970, 250]
          ]
        }
      }
    }
  ],
  "config": {
    "debug": false
  }
}

Response:

json
{
  "seatbid": [
    {
      "bid": [
        {
          "impid": "header-banner",
          "adm": "<html>...</html>",
          "price": 1.5,
          "w": 728,
          "h": 90
        }
      ]
    }
  ]
}

Example:

bash
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:

ParameterTypeRequiredDescription
tsurlstringYesTarget URL without query parameters (base URL)
tstokenstringYesBase64 URL-safe SHA-256 digest of encrypted full target URL
*anyNoOriginal 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 tstoken against reconstructed URL
  • Follows redirects (301/302/303/307/308, max 4 hops)
  • Injects EC ID as ts-ec query parameter
  • Logs 1×1 pixel impressions

Example:

bash
# 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 invalid tstoken
  • 403 Forbidden - Token validation failed
  • 500 Internal Server Error - Upstream fetch failed

GET /first-party/click

Click tracking redirect endpoint.

Query Parameters:

ParameterTypeRequiredDescription
tsurlstringYesTarget redirect URL without query parameters
tstokenstringYesBase64 URL-safe SHA-256 digest of encrypted full target URL
*anyNoOriginal target URL query parameters

Response:

  • Status: 302 Found
  • Location: Reconstructed target URL with EC ID injected

Behavior:

  • Validates tstoken against reconstructed URL
  • Injects ts-ec query parameter
  • Logs click metadata (tsurl, referer, user agent)
  • Does not proxy content (redirect only)

Example:

bash
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=abc123

GET/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:

bash
curl "https://edge.example.com/first-party/sign?url=https://external.com/pixel.gif"

POST Request:

bash
curl -X POST https://edge.example.com/first-party/sign \
  -H "Content-Type: application/json" \
  -d '{"url":"https://external.com/pixel.gif"}'

Response:

json
{
  "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:

json
{
  "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:

json
{
  "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:

ParameterRequiredDescription
tsclickYesURL-encoded signed click URL
addNoURL-encoded JSON object of parameters to add
delNoURL-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:

json
{
  "version": "1.0",
  "jwks": {
    "keys": [
      {
        "kty": "OKP",
        "crv": "Ed25519",
        "kid": "ts-2025-01-A",
        "use": "sig",
        "x": "UVTi04QLrIuB7jXpVfHjUTVN5aIdcbPNr50umTtN8pw"
      }
    ]
  }
}

Example:

bash
curl https://edge.example.com/.well-known/trusted-server.json

Use 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:

json
{
  "payload": "base64-encoded-data",
  "signature": "base64-signature",
  "kid": "ts-2025-01-A"
}

Response (Success):

json
{
  "verified": true,
  "kid": "ts-2025-01-A",
  "message": "Signature verified successfully"
}

Response (Failure):

json
{
  "verified": false,
  "kid": "ts-2025-01-A",
  "message": "Invalid signature"
}

Example:

bash
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):

json
{
  "kid": "custom-key-id"
}

If omitted, auto-generates date-based ID (e.g., ts-2025-01-15-A).

Response:

json
{
  "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:

bash
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-kid to 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:

json
{
  "kid": "ts-2025-01-14-A",
  "delete": false
}
FieldTypeRequiredDescription
kidstringYesKey ID to deactivate
deletebooleanNoIf true, permanently removes key (default: false)

Response:

json
{
  "kid": "ts-2025-01-14-A",
  "active_kids": ["ts-2025-01-15-A"],
  "message": "Key deactivated successfully"
}

Example:

bash
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, and generation identify the raw KV lookup.
  • entry preserves the stored JSON shape, including unknown and legacy fields. Derived created_iso and consent.updated_iso fields are added only when absent.
  • metadata preserves the stored metadata JSON shape.
  • tombstone reports 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.eids previews the partner EIDs the stored record can contribute; auction.skipped explains filtered IDs.
  • entry_error, metadata_error, and raw_body keep 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:

StatusMeaning
200Record found, including inspectable corrupt records
400Invalid explicit EC ID
401Missing or invalid Basic credentials
404Record not found, or the bare route has no ts-ec cookie
405Method other than GET (Allow: GET)
501EC identity graph unavailable on this adapter or deployment
5xxUnexpected configuration or KV failure (plaintext)
bash
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.

json
{
  "ingest": {
    "matched": [
      {
        "source_domain": "configured.example",
        "uid": "fictional-uid"
      }
    ],
    "unmatched": [
      {
        "source": "unknown.example",
        "reason": "no_partner"
      }
    ]
  }
}
bash
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.js
  • tsjs-unified.min.js

Query Parameters:

ParameterTypeRequiredDescription
vstringNoCache-busting hash (SHA256 of bundle contents)

Response:

  • Content-Type: application/javascript; charset=utf-8
  • Body: TSJS bundle (IIFE format)
  • Headers: ETag for caching

Example:

html
<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:

toml
[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:

bash
curl https://edge.example.com/integrations/permutive/api/settings
# → Proxies to https://api.permutive.com/settings

GET/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:

json
{
  "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:

json
{
  "error": "Error type",
  "message": "Detailed error description",
  "details": {
    "field": "Additional context"
  }
}

Common HTTP Status Codes:

CodeMeaningCommon Causes
400Bad RequestMissing required parameters, invalid JSON
401UnauthorizedMissing or invalid basic auth credentials
403ForbiddenInvalid token signature, disabled integration
404Not FoundUnknown endpoint, missing resource
500Internal Server ErrorUpstream service failure, configuration error
502Bad GatewayBackend service unavailable
504Gateway TimeoutBackend service timeout exceeded

Authentication

Basic Authentication

Endpoints under protected paths require HTTP Basic Authentication:

Configuration:

toml
[[handlers]]
path = "^/_ts/admin"
username = "admin"
password = "secure-password"

Usage:

bash
curl -u admin:secure-password https://edge.example.com/_ts/admin/keys/rotate

Protected Endpoints:

  • /_ts/admin/keys/rotate
  • /_ts/admin/keys/deactivate
  • /_ts/admin/ec
  • /_ts/admin/ec/{id}
  • /_ts/admin/eids
  • Any paths matching configured handlers patterns

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:

toml
[response_headers]
Access-Control-Allow-Origin = "*"
Access-Control-Allow-Methods = "GET, POST, OPTIONS"
Access-Control-Allow-Headers = "Content-Type, Authorization"

Next Steps

Released under the Apache License 2.0.