Cost Events
curl --request POST \
--url https://api.nozle.app/engine/api/v1/cost-events \
--header 'Authorization: Bearer <token>'import requests
url = "https://api.nozle.app/engine/api/v1/cost-events"
headers = {"Authorization": "Bearer <token>"}
response = requests.post(url, headers=headers)
print(response.text)const options = {method: 'POST', headers: {Authorization: 'Bearer <token>'}};
fetch('https://api.nozle.app/engine/api/v1/cost-events', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://api.nozle.app/engine/api/v1/cost-events"
req, _ := http.NewRequest("POST", url, nil)
req.Header.Add("Authorization", "Bearer <token>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}require 'uri'
require 'net/http'
url = URI("https://api.nozle.app/engine/api/v1/cost-events")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
response = http.request(request)
puts response.read_body<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.nozle.app/engine/api/v1/cost-events",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}HttpResponse<String> response = Unirest.post("https://api.nozle.app/engine/api/v1/cost-events")
.header("Authorization", "Bearer <token>")
.asString();Margin Intelligence
Cost Events
Send detailed internal cost usage from a trusted backend
POST
/
engine
/
api
/
v1
/
cost-events
Cost Events
curl --request POST \
--url https://api.nozle.app/engine/api/v1/cost-events \
--header 'Authorization: Bearer <token>'import requests
url = "https://api.nozle.app/engine/api/v1/cost-events"
headers = {"Authorization": "Bearer <token>"}
response = requests.post(url, headers=headers)
print(response.text)const options = {method: 'POST', headers: {Authorization: 'Bearer <token>'}};
fetch('https://api.nozle.app/engine/api/v1/cost-events', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://api.nozle.app/engine/api/v1/cost-events"
req, _ := http.NewRequest("POST", url, nil)
req.Header.Add("Authorization", "Bearer <token>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}require 'uri'
require 'net/http'
url = URI("https://api.nozle.app/engine/api/v1/cost-events")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
response = http.request(request)
puts response.read_body<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.nozle.app/engine/api/v1/cost-events",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}HttpResponse<String> response = Unirest.post("https://api.nozle.app/engine/api/v1/cost-events")
.header("Authorization", "Bearer <token>")
.asString();Base URL:
The provider response remains authoritative to the application. Tracking failures never replace a successful provider response.
https://api.nozle.app/engine/api/v1. The Core host does not serve this route and returns 404.
Auth: secret key only. With API key permissions, the key needs event:write; without it the call returns 403 with code: permission_denied.
Use Cost Events for internal usage that needs more detail than the customer-facing Feature Event, such as artificial intelligence tokens, emails, storage operations, or third-party requests.
The Cost Model creation flow includes a provider-neutral Artificial Intelligence tokens template. You can enter rates manually or select an effective-dated provider price catalogue version. Nozle creates one ai_tokens meter and separate exact rules for each configured token category.
The supported Version One categories are input, cached_input, cache_write, output, and reasoning. Provider catalogue references are retained on the Cost Rule so negotiated overrides remain traceable to their base prices. Unknown provider, model, or token-category combinations enter the Uncosted queue instead of silently becoming zero-cost usage.
{
"cost_event_id": "cost_01994d7e",
"cost_meter_code": "ai_tokens",
"parent_transaction_id": "feature_01994d7e",
"external_customer_id": "customer_123",
"request_id": "provider_request_987",
"operation_key": "planning",
"properties": {
"tokens": 900,
"provider": "openai",
"model": "configured-model-a",
"type": "input"
},
"timestamp": 1788345001
}
timestamp is Unix seconds (a JSON number such as 1788345001 or 1788345001.5, or a numeric string such as "1788345001") or an RFC 3339 string with a time zone offset or Z. Omit it to use the time Nozle receives the event. A date without a time, a time without an offset, or another JSON type returns 400 invalid_request with timestamp must be Unix seconds (a number or a numeric string) or an RFC 3339 timestamp with an offset or Z; zero or negative seconds return 422 with timestamp must be greater than zero. Strings are accepted from the Engine release that includes this change; before it, only a number is accepted and any string returns 400 invalid request body. An RFC 3339 value is stored as Unix seconds.
At least one attribution field is required:
parent_transaction_idlinks the cost to an existing or future Feature Event in the same organization.external_customer_idrecords a customer-direct cost that is not linked to a Feature.- Both may be provided; asynchronous processing verifies that they resolve to the same customer.
202 Accepted with {"status": "accepted", "cost_event_id": "..."} after Kafka durably acknowledges the message; cost calculation and analytics happen asynchronously. Once rated, the cost appears in the cost intelligence report and in the margin views (/margin/* and the dashboard margin views), attributed to the customer and to the parent Feature Event’s Feature and plan. A Cost Event never adds revenue. See Margin Intelligence.
If cost_event_id is omitted, Nozle generates and returns a version 7 universally unique identifier. Reuse the same identifier and payload when retrying a Cost Event. Posting the same cost_event_id again also returns 202; the duplicate is removed when the event is processed.
The endpoint does not check
cost_meter_code when it accepts the event. An unknown meter code still returns 202. Processing then marks the event uncosted with the failure code unknown_cost_event_meter (“No active Cost Event Meter matches the event code”), and it appears in the Uncosted queue of the cost intelligence report. It adds no cost to the totals until you correct the meter and rate it again. Copy the code exactly from the Cost Model.Provider usage capture
The Node.js, Python, and Go SDKs capture OpenAI and Anthropic usage automatically from completed responses and streams. Each records a parent Feature Event and optional detailed Cost Events. SetcostMeterCode in Node.js, cost_meter_code in Python, or CostMeterCode in Go. The parent transaction_id is generated before delivery, so child Cost Events may safely arrive before the Feature Event.
Node.js and Python wrap the provider client methods. Go wraps the HTTP client passed to the official provider SDK using WrapOpenAIHTTPClient or WrapAnthropicHTTPClient. Go’s CaptureOpenAIUsage and CaptureAnthropicUsage methods also support manual or queued delivery.
For providers without a wrapper, or for cost-only tracking, send Cost Events yourself: costEvents.track in Node.js, cost_events.track in Python, or TrackCostEvent in Go. A Cost Event never bills the customer.
Each non-zero category is emitted separately with canonical properties:
{
"tokens": 900,
"provider": "openai",
"model": "configured-model-a",
"type": "cached_input"
}