Turnstile ingests usage events through a single API endpoint. Before sending usage programmatically, you need three things in place: a billable metric configured with a matching event key, an API key, and the Turnstile customer ID you’re recording usage against.
Configure a billable metric's event key
Go to Catalog > Billable metrics and create or edit a metric.
Add an Event key.
Event keys can be anything, but they must be unique.
Whatever value you put here is what you'll send as event_type in the API call.
Once set, the event key cannot be changed.
Example event keys: api_calls, contracts_reviewed
Create or Save changes.
Important: The metric also needs to be attached to the customer's product/pricing as usual. If an incoming event_type doesn't match any event key configured on the customer's active subscription, the usage event is still stored, but nothing gets billed, and no error is returned.
Get an API key
In your Turnstile account, head to Settings > API keys.
Select +Create new key.
Give it a name and select at least one scope (any of the read scopes will work).
Click Generate key.
The key is shown exactly once on the next screen. Copy or download it (recommended) now, since it can't be recovered afterward.
Find the customer ID
Go to Customers > All customers.
Open the customer you want to record usage for.
Copy the value after /customers/ to use as turnstile_customer_id.
The customer ID is the UUID in the page's URL: https://app.turnstile.ai/customers/{customerId}
Send a usage event
Endpoints
Production ingestion:
POST https://api.tryturnstile.com/usage/ingestValidation-only test:
POST https://api.tryturnstile.com/test/usage/ingest
Send Content-Type: application/json and Authorization: Bearer YOUR_API_KEY.
Test endpoint behavior: The /test/ endpoint validates and logs the payload but does not insert a usage event into the database. Because it shares the ingestion Lambda, include a Bearer value containing at least 10 characters.
Request body
Use the Turnstile customer UUID as customer_alias.turnstile_customer_id. A top-level customer_id field is not part of the current contract.
{
"customer_alias": {
"turnstile_customer_id": "CUSTOMER_UUID"
},
"timestamp": "2026-07-29T18:30:00Z",
"event_type": "api_requests",
"quantity": 42,
"properties": {
"region": "us-west-2",
"model": "gpt-5",
"request_type": "batch",
"anything_you_want": "any string value"
}
}
Fields
customer_alias: Required object. Supply turnstile_customer_id.timestamp: Required ISO-8601 timestamp. Use whole seconds, without milliseconds; for example 2026-07-29T18:30:00Z.event_type: Required string identifying the usage event or billable metric.quantity: Required integer.properties: Optional object containing arbitrary property names. Every property value must be a string.
Production curl example
curl https://api.tryturnstile.com/usage/ingest \
-X POST \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"customer_alias": {
"turnstile_customer_id": "CUSTOMER_UUID"
},
"timestamp": "2026-07-29T18:30:00Z",
"event_type": "api_requests",
"quantity": 42,
"properties": {
"region": "us-west-2",
"model": "gpt-5",
"request_type": "batch"
}
}'
Arbitrary properties and persistence
The production endpoint stores the complete properties object in the usage_event.properties JSONB column. Property keys are unrestricted; values are currently restricted to strings.
For example, all of these keys are accepted:
"properties": {
"workspace": "acme-production",
"region": "eu-west-1",
"feature": "document-export",
"source": "scheduled-job",
"your_own_property_name": "your own string value"
}A note on properties: Values sent in properties are stored on the usage event but cannot be retrieved afterward in the UI or API, even internally. Treat this field as write-only bookkeeping rather than something retrievable on demand.
Test curl example
curl https://api.tryturnstile.com/test/usage/ingest \
-X POST \
-H "Authorization: Bearer TEST_VALUE_123" \
-H "Content-Type: application/json" \
-d '{
"customer_alias": {
"turnstile_customer_id": "CUSTOMER_UUID"
},
"timestamp": "2026-07-29T18:30:00Z",
"event_type": "api_requests",
"quantity": 42,
"properties": {
"region": "us-west-2"
}
}'
A successful API Gateway response is currently 200 {}. Production ingestion is queued, so this response means the request was accepted by the gateway—not that downstream persistence has already completed.