TV Turing Verify REST API · v1 Reference

Turing Verify REST API — v1

Base URL: https://api.verify.turingcerts.com Protocol: HTTPS + JSON. All timestamps are RFC-3339 UTC (YYYY-MM-DDTHH:MM:SSZ).

This page is the canonical reference for the /v1 public API. It is served at /v1/docs.md (raw markdown) and rendered as HTML at /v1/docs/reference. For the OpenAPI 3.1 schema use /v1/openapi.json. For the Model Context Protocol connection, see /mcp/docs.

Authentication

Every request must carry a bearer token:

CODE
Authorization: Bearer tv_live_sk_<secret>

Tokens are minted from the Developer Console (/developers/tokens). Prefixes:

  • tv_live_sk_... — production, charges credits.
  • tv_test_sk_... — deterministic, zero-credit.

Environments

  • Test mode — tokens starting with tv_test_sk_. Deterministic verdicts seeded from sha256(bytes) % 4, no model calls, zero credits. Use in CI and for offline integration tests.
  • Live mode — tokens starting with tv_live_sk_. Runs the full forensic pipeline, charges 1 credit per verification (2 for deep mode). Response headers carry X-Credits-Remaining and, when quota crosses 80 %, X-Credits-Warning: approaching-quota.

The server echoes the mode in X-Turing-Environment: test|live on every response.

Error envelope

Non-2xx responses always have this shape:

JSON
{
  "error": {
    "code": "invalid_request",
    "message": "Human-friendly description.",
    "request_id": "req_01HTN8Z9MJB00000000000000",
    "docs_url": "https://api.verify.turingcerts.com/v1/docs#errors"
  }
}

Common code values:

code HTTP Meaning
authentication_required 401 Missing or malformed Authorization header.
invalid_api_key 401 Token unknown, revoked, or expired.
insufficient_scope 403 Token is valid but lacks the required scope.
plan_required 403 Endpoint is gated to Pro / Business.
not_found 404 The resource does not exist (or is not yours).
idempotency_conflict 409 Idempotency-Key reused with a different body.
invalid_request 400 Validation failed (see message).
rate_limited 429 Token bucket exhausted or quota depleted.
internal_error 500 Server problem; retry with exponential backoff.

X-Request-ID is also sent as a response header — include it when filing support tickets.

Rate limits

Per-token, token-bucket scheme. Headers on every response:

CODE
X-RateLimit-Limit: 600
X-RateLimit-Remaining: 597
X-RateLimit-Reset: 42
  • Limit — sustained requests/minute for your tier.
  • Remaining — requests left in the current window.
  • Reset — seconds until the window resets.

Tiers (defaults; contact sales for custom):

Plan Burst Sustained (req/min)
Pro 20 600
Business 40 3000

When you breach the limit you get 429 rate_limited with a Retry-After header (seconds).

Idempotency

POST /v1/verifications supports an Idempotency-Key header. Replays within 24 hours return the cached response with Idempotency-Status: replayed. Reusing the same key with a different body returns 409 idempotency_conflict.

Every official SDK generates a UUID4 key automatically — you only need to set this header by hand if you're calling the API directly.

Pagination

All list endpoints use cursor pagination. Query params:

  • limit — page size, 1–100 (default 20).
  • starting_after — opaque cursor from the previous page's next_cursor.

Response shape:

JSON
{
  "object": "list",
  "data": [ ... ],
  "has_more": true,
  "next_cursor": "ver_01HTN..."
}

Endpoints

POST /v1/verifications

Submit a document for verification. Accepts a multipart upload (file) or a JSON body with document_url or document_base64 (+ content_type).

Request (multipart)

BASH
curl -X POST https://api.verify.turingcerts.com/v1/verifications \
  -H "Authorization: Bearer tv_live_sk_..." \
  -F "file=@diploma.pdf"

Response 200

JSON
{
  "id": "ver_01HTN...",
  "status": "completed",
  "verdict": "ACCEPT",
  "confidence": 0.94,
  "doc_type": "diploma",
  "livemode": true,
  "created_at": "2026-04-17T09:12:33Z",
  "poll_url": "/v1/verifications/ver_01HTN..."
}

GET /v1/verifications/{id}

Fetch a previously submitted verification.

BASH
curl https://api.verify.turingcerts.com/v1/verifications/ver_01HTN... \
  -H "Authorization: Bearer tv_live_sk_..."

404 if the id is unknown or the verification belongs to another user — we don't leak existence.

GET /v1/verifications

Cursor-paginated list of your verifications, newest first.

Query params: limit, starting_after, verdict (optional filter).

POST /v1/verifications/batch

Business-tier only. Submit up to 1 000 documents in a single request.

JSON
{
  "documents": [
    { "base64": "JVBERi...", "content_type": "application/pdf" },
    { "url": "https://example.com/cert.pdf" }
  ]
}

POST /v1/webhooks

Register a webhook endpoint. Returns the signing secret once.

JSON
{
  "url": "https://example.com/hooks/turing-verify",
  "events": ["verification.completed", "verification.failed"],
  "description": "Prod ATS ingestion"
}

GET /v1/webhooks

List endpoints you own.

GET /v1/account

JSON
{
  "user_id": "usr_01HTN...",
  "email": "alice@example.com",
  "plan": { "tier": "pro", "api_access": true, "rate_limit_burst": 20, "rate_limit_sustained": 600 },
  "credits": { "included": 150, "used": 12, "remaining": 138, "reset_at": "2026-05-01T00:00:00Z", "overage_opt_in": false }
}

GET /v1/account/usage

Month-to-date usage histogram.

Query params:

  • periodYYYY-MM, defaults to the current calendar month.
  • group_byday (default), endpoint, or token.

Webhooks

Events are delivered as HTTPS POSTs with this envelope:

JSON
{
  "id": "evt_01HTN...",
  "type": "verification.completed",
  "api_version": "2026-04-01",
  "created": "2026-04-17T09:12:33Z",
  "data": { "verification": { ... } }
}

HMAC signing

Every delivery includes:

CODE
X-TuringVerify-Signature: t=1713347553,v1=<hex digest>

The digest is computed as:

CODE
HMAC_SHA256(secret, f"{timestamp}.{raw_body}")

Python

PYTHON
from turingverify import verify_signature, SignatureVerificationError

body = await request.body()
try:
    verify_signature(body, request.headers["X-TuringVerify-Signature"], SECRET)
except SignatureVerificationError:
    return 400

TypeScript

TS
import { verifyWebhookSignature } from '@turingverify/sdk';

verifyWebhookSignature(
  req.rawBody,
  req.header('X-TuringVerify-Signature') ?? '',
  process.env.WEBHOOK_SECRET!,
);

Go (hand-rolled)

GO
mac := hmac.New(sha256.New, []byte(secret))
mac.Write([]byte(fmt.Sprintf("%d.", ts)))
mac.Write(body)
expected := hex.EncodeToString(mac.Sum(nil))

Retry ladder

Failed deliveries retry at 0s, 30s, 2m, 10m, 30m, 2h, 6h, 12h, 24h (nine attempts over ~45 h). After the last failure the delivery row is marked permanently_failed; the developer portal lets you replay one manually.

Event list (launch set)

  • verification.completed
  • verification.failed
  • verification.manual_review_required
  • batch.completed
  • token.rotated
  • webhook.ping (synthetic test fires)

MCP

The Model Context Protocol surface lives at https://mcps.verify.turingcerts.com/mcp. See /mcp/docs for the full manual (5 tools, 3 resources, 2 prompts). One-click client manifests are served at /.well-known/mcp-config.

SDKs

  • Pythonpip install turingverify — source in ./sdks/python.
  • TypeScript / Nodenpm install @turingverify/sdk — source in ./sdks/node.

Both SDKs wrap the endpoints documented here, handle retries, inject an auto-generated Idempotency-Key, and ship a verify_signature / verifyWebhookSignature helper for inbound webhooks.