stile
HTTP API

Overview

Base URL, authentication, request and response conventions, pagination, the error object, idempotency, and rate limits — everything shared across the Stile HTTP API.

The Stile API is a standard REST API: predictable resource URLs, JSON request and response bodies, conventional HTTP status codes, and Bearer-token authentication. Any language that can make HTTP requests can integrate — no SDK required. This page covers the conventions shared by every endpoint; the resource pages linked at the bottom document each endpoint in detail.

Widget handles the client side

Most integrations only need the widget (<stile-button>) for the frontend and a webhook handler on the backend. You may never need to call the API directly.

Base URL

https://api.stile.id/v1/

All endpoint paths in this reference are relative to this base. The API version (v1) is part of the URL path.

Authentication

Pass your secret key as a Bearer token in the Authorization header:

curl https://api.stile.id/v1/verification_sessions \
  -H "Authorization: Bearer stile_sk_YOUR_SECRET_KEY" \
  -H "Content-Type: application/json"

There are two kinds of keys:

Key typeFormatWhere it belongs
Secretstile_sk_...Server-side only. Full API access.
Publishablestile_pk_...Frontend-safe. Limited scope.

Keys are managed at dashboard.stile.id/api-keys; secret keys are shown once at creation. There's a single environment — every organization is live by default. To build and test without billing or a webhook endpoint, enable sandbox mode on a testing organization. See Testing for what sandbox mode covers.

Keep secret keys on the server

Never embed a stile_sk_ key in browser code or a mobile app. Anything client-side should use the widget's session-url mode — your backend creates the session and hands the page the session's client_secret. See Security.

See Authentication for the full breakdown of key types and scopes.

Making a request

Here's the same API call — creating a verification session — in every supported language:

curl -X POST https://api.stile.id/v1/verification_sessions \
  -H "Authorization: Bearer stile_sk_..." \
  -H "Content-Type: application/json" \
  -d '{"type": "age", "workflow_id": "wf_YOUR_WORKFLOW_ID"}'
import requests

res = requests.post(
    "https://api.stile.id/v1/verification_sessions",
    headers={"Authorization": "Bearer stile_sk_..."},
    json={"type": "age", "workflow_id": "wf_YOUR_WORKFLOW_ID"},
)
session = res.json()
body := strings.NewReader(`{"type":"age","workflow_id":"wf_YOUR_WORKFLOW_ID"}`)
req, _ := http.NewRequest("POST", "https://api.stile.id/v1/verification_sessions", body)
req.Header.Set("Authorization", "Bearer stile_sk_...")
req.Header.Set("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
import Stile from "@stile/node";

const stile = new Stile("stile_sk_...", { baseUrl: "https://api.stile.id" });

const session = await stile.verificationSessions.create({
  type: "age",
  workflow_id: "wf_YOUR_WORKFLOW_ID",
});

Every session runs inside a published workflowworkflow_id is required, and the workflow carries the use case, jurisdictions, and verification methods. See Verification Sessions for the full parameter reference.

Request format

  • Content-Type: application/json for all POST requests
  • Query parameters: for GET requests and filtering
# POST — JSON body
curl -X POST https://api.stile.id/v1/verification_sessions \
  -H "Authorization: Bearer stile_sk_..." \
  -H "Content-Type: application/json" \
  -d '{"type": "age", "workflow_id": "wf_YOUR_WORKFLOW_ID"}'

# GET — query parameters
curl "https://api.stile.id/v1/verification_sessions?limit=10&status=verified" \
  -H "Authorization: Bearer stile_sk_..."

Response format

Responses are JSON. Every resource includes an object field identifying its type. Persistent objects also carry an id with a resource-specific prefix (vks_ for sessions, evt_ for events) and Unix-second timestamps (created, updated, expires_at, and so on):

{
  "id": "vks_abc123",
  "object": "verification_session",
  "status": "verified",
  "type": "age",
  "created": 1741564800
}

Pagination

List endpoints use cursor-based pagination:

ParameterDescription
limitNumber of results (1-100; default 10 for most lists — webhook deliveries default to 20)
starting_afterCursor: return results after this ID
ending_beforeCursor: return results before this ID. Supported on most list endpoints

List responses share a common envelope with object: "list" and a has_more flag indicating whether more results exist:

{
  "object": "list",
  "url": "/v1/verification_sessions",
  "has_more": true,
  "data": [...]
}

To paginate forward, pass the last item's id as starting_after:

curl "https://api.stile.id/v1/verification_sessions?limit=10&starting_after=vks_abc123" \
  -H "Authorization: Bearer stile_sk_..."

Expanding responses

Some endpoints support the expand[] parameter to include related objects inline. For example, both retrieving and listing verification sessions accept expand[]=results to embed each session's per-method verification results:

# Include verification results in the session response
curl "https://api.stile.id/v1/verification_sessions/vks_abc123?expand[]=results" \
  -H "Authorization: Bearer stile_sk_..."

The error object

All errors return a JSON body with this structure:

{
  "error": {
    "type": "invalid_request_error",
    "code": "parameter_invalid",
    "message": "No such verification_session: 'vks_unknown'",
    "param": "id",
    "request_id": "req_abc123"
  }
}
ParameterTypeDescription
typestringThe broad category of error: invalid_request_error, authentication_error, rate_limit_error, or api_error.
codestringA stable, machine-readable string identifying the exact error (e.g. parameter_invalid, resource_missing). Branch on this, not on message.
messagestringA human-readable explanation for debugging. Wording may change — don't parse it or show it to end users.
paramstringThe request parameter the error relates to, when applicable.
request_idstringUnique ID for this request. Include it when contacting support.

Error types

TypeMeaning
invalid_request_errorThe request was malformed or can't be processed as sent.
authentication_errorThe API key is missing, invalid, or lacks the required scope.
rate_limit_errorToo many requests — back off and retry.
api_errorSomething failed on Stile's side. Safe to retry with backoff.

HTTP status codes

StatusMeaning
200, 201Success.
400Bad request — malformed or missing parameters (e.g. parameter_invalid).
401Authentication failed — missing or invalid API key (api_key_invalid).
402Billing issue (billing_suspended, test_quota_exceeded).
403The key isn't allowed to do this (e.g. publishable_key_scope).
404Resource doesn't exist (resource_missing, session_not_found).
409Conflict with current state (e.g. session_terminal, idempotency_key_reuse).
422Request understood but rejected (e.g. use_case_prohibited, jurisdiction_unresolvable).
429Rate limited (rate_limit_exceeded) — retry after the Retry-After header.
500Server error — retry with backoff.

Retry 429 (after Retry-After) and 5xx responses with exponential backoff — min(500ms * 2^attempt + jitter, 30s) is a good schedule. Never auto-retry other 4xx errors; fix the request instead. See Error Handling for resource-specific codes and recovery strategies.

Idempotency

POST requests accept an Idempotency-Key header to prevent duplicates:

curl -X POST https://api.stile.id/v1/verification_sessions \
  -H "Authorization: Bearer stile_sk_..." \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: order_12345" \
  -d '{"type": "age", "workflow_id": "wf_YOUR_WORKFLOW_ID"}'

Pick a key tied to the operation you're protecting (an order ID, a user ID plus action). The semantics:

  • Same key, same body — the original response is replayed; no duplicate is created.
  • Same key, different body — the request is rejected with 409 idempotency_key_reuse.

The Node.js SDK accepts the same key as a per-request option: stile.verificationSessions.create(params, { idempotencyKey: "order_12345" }).

Rate limiting

Requests are rate-limited per API key over a per-minute rolling window — 1,000 requests/minute for secret keys, 100/minute for publishable keys.

Rate limit headers are included in every response:

X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 997
X-RateLimit-Reset: 1741564920

When exceeded, the API returns 429 with a Retry-After header. Wait the specified seconds before retrying.

Node.js SDK

If you prefer a typed client for Node.js / TypeScript, the @stile/node package wraps every endpoint documented here. Every SDK method maps 1:1 to an HTTP call — choose whichever you prefer.

API playground

The core endpoints in this reference have interactive counterparts in the API Playground section of these docs, generated from Stile's OpenAPI spec (/openapi.yaml). Use it to explore request and response schemas alongside the prose reference here.

Explore the reference

On this page