curl --request GET \
--url https://nano-gpt.com/api/v1/usage/requests/{request_id} \
--header 'Authorization: Bearer <token>'import requests
url = "https://nano-gpt.com/api/v1/usage/requests/{request_id}"
headers = {"Authorization": "Bearer <token>"}
response = requests.get(url, headers=headers)
print(response.text)const options = {method: 'GET', headers: {Authorization: 'Bearer <token>'}};
fetch('https://nano-gpt.com/api/v1/usage/requests/{request_id}', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://nano-gpt.com/api/v1/usage/requests/{request_id}",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
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;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://nano-gpt.com/api/v1/usage/requests/{request_id}"
req, _ := http.NewRequest("GET", 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))
}HttpResponse<String> response = Unirest.get("https://nano-gpt.com/api/v1/usage/requests/{request_id}")
.header("Authorization", "Bearer <token>")
.asString();require 'uri'
require 'net/http'
url = URI("https://nano-gpt.com/api/v1/usage/requests/{request_id}")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
request["Authorization"] = 'Bearer <token>'
response = http.request(request)
puts response.read_body{
"object": "request_billing",
"request_id": "req_example",
"created_at": "2026-09-17T10:00:01.000000Z",
"expires_at": "2026-09-18T10:00:01.000000Z",
"as_of": "2026-09-17T10:00:05.000000Z",
"cost": "0.00123456",
"currency": "USD",
"cost_scope": "primary_charge",
"usage": {
"input_tokens": 123,
"output_tokens": 45,
"reasoning_tokens": null
}
}{
"error": {
"message": "<string>",
"type": "<string>",
"code": "<string>"
}
}{
"error": {
"message": "<string>",
"type": "<string>",
"code": "<string>"
}
}{
"error": {
"message": "<string>",
"type": "<string>",
"code": "<string>"
}
}{
"error": {
"message": "<string>",
"type": "<string>",
"code": "<string>"
}
}{
"error": {
"message": "<string>",
"type": "<string>",
"code": "<string>"
}
}{
"error": {
"message": "<string>",
"type": "<string>",
"code": "<string>"
}
}{
"error": {
"message": "<string>",
"type": "<string>",
"code": "<string>"
}
}Request Billing
Look up a recorded primary charge and token usage by request ID for 24 hours.
curl --request GET \
--url https://nano-gpt.com/api/v1/usage/requests/{request_id} \
--header 'Authorization: Bearer <token>'import requests
url = "https://nano-gpt.com/api/v1/usage/requests/{request_id}"
headers = {"Authorization": "Bearer <token>"}
response = requests.get(url, headers=headers)
print(response.text)const options = {method: 'GET', headers: {Authorization: 'Bearer <token>'}};
fetch('https://nano-gpt.com/api/v1/usage/requests/{request_id}', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://nano-gpt.com/api/v1/usage/requests/{request_id}",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
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;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://nano-gpt.com/api/v1/usage/requests/{request_id}"
req, _ := http.NewRequest("GET", 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))
}HttpResponse<String> response = Unirest.get("https://nano-gpt.com/api/v1/usage/requests/{request_id}")
.header("Authorization", "Bearer <token>")
.asString();require 'uri'
require 'net/http'
url = URI("https://nano-gpt.com/api/v1/usage/requests/{request_id}")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
request["Authorization"] = 'Bearer <token>'
response = http.request(request)
puts response.read_body{
"object": "request_billing",
"request_id": "req_example",
"created_at": "2026-09-17T10:00:01.000000Z",
"expires_at": "2026-09-18T10:00:01.000000Z",
"as_of": "2026-09-17T10:00:05.000000Z",
"cost": "0.00123456",
"currency": "USD",
"cost_scope": "primary_charge",
"usage": {
"input_tokens": 123,
"output_tokens": 45,
"reasoning_tokens": null
}
}{
"error": {
"message": "<string>",
"type": "<string>",
"code": "<string>"
}
}{
"error": {
"message": "<string>",
"type": "<string>",
"code": "<string>"
}
}{
"error": {
"message": "<string>",
"type": "<string>",
"code": "<string>"
}
}{
"error": {
"message": "<string>",
"type": "<string>",
"code": "<string>"
}
}{
"error": {
"message": "<string>",
"type": "<string>",
"code": "<string>"
}
}{
"error": {
"message": "<string>",
"type": "<string>",
"code": "<string>"
}
}{
"error": {
"message": "<string>",
"type": "<string>",
"code": "<string>"
}
}GET /api/v1/usage/requests/{request_id} returns the recorded cost and token
usage for one primary charge from the last 24 hours. Capture X-Request-ID
from the inference response headers before reading SSE and use the same API
key for the lookup. No dates or other query parameters are accepted.
For chat completions, omit the incoming X-Request-ID header and capture the
server-generated ID from each response. Save it as soon as the response headers
arrive, before consuming the stream; it is the ID to use for this lookup.
curl 'https://nano-gpt.com/api/v1/usage/requests/req_example' \
-H "Authorization: Bearer $NANOGPT_API_KEY"
{
"object": "request_billing",
"request_id": "req_example",
"created_at": "2026-09-17T10:00:01.000000Z",
"expires_at": "2026-09-18T10:00:01.000000Z",
"as_of": "2026-09-17T10:00:05.000000Z",
"cost": "0.00123456",
"currency": "USD",
"cost_scope": "primary_charge",
"usage": {
"input_tokens": 123,
"output_tokens": 45,
"reasoning_tokens": null
}
}
What cost means
cost is the exact amount recorded on the matching primary charge, expressed
as a decimal string in currency (USD or XNO). The lookup does not
recalculate prices. A recorded zero is returned as zero; a missing charge is
never converted to zero.
Refunds and separately billed extras, such as a separate search charge, are
excluded. Components already incorporated in the primary charge’s amount remain
included. This is the original recorded primary cost, not an all-in total or a
net amount after refunds. A later refund does not reduce this field. A completed
transaction record is also not proof that balance collection has finished.
Tokens are recorded accounting values and may have been estimated for an
interrupted stream. Missing, invalid or unsafe-sized counts are null.
Reasoning tokens may already be included in output tokens; do not add them again.
No prompts, responses, provider routing, internal identifiers or arbitrary
transaction metadata are returned.
Availability and request IDs
The window is rolling, including across UTC midnight. A charge is eligible when its accounting timestamp is no more than the database’s current time and is strictly less than 24 hours old.expires_at is that timestamp plus 24 hours;
access ends at that instant. This timestamp can precede insertion when delayed
billing is replayed. Replay does not reset the lookup window. Financial records
are retained; this endpoint simply stops exposing older charges.
Billing can be asynchronous, especially after a disconnect, so the charge might
not be available immediately. A charge recorded only after its window expires
cannot be recovered through this endpoint. Expired, unavailable and inaccessible
records all return the same 404; this does not prove that a request was free.
If you supply your own X-Request-ID, use a unique value for every execution,
including a retry that sends a new inference request. The header is a correlation
ID, not an idempotency key: reusing it does not prevent another charge. After a
disconnect, look up the original ID to recover its cost; retrying the lookup does
not rerun inference. If you also retry inference, keep the two execution IDs
separate. If more than one eligible primary charge matches within the window,
the endpoint returns 409 ambiguous_request_id, without choosing one,
summing different attempts, or returning their details. Uniqueness is checked
within the window only; reusing an expired ID can resolve to a newer execution.
Only completed primary charges with the exact stored public request ID are
covered. Other endpoints are supported only when they persist that identity and
key/issuer attribution. Related child IDs and archived records are not followed.
Authentication
The inference key can read only its own charges attributed to its originating account in the same NanoGPT tenant. Team-paid charges remain visible to the originating actor’s key; team ownership or membership does not grant broader access. Missing or inconsistent historical attribution can make a charge unavailable. Revoked/expired keys, deleted/merged issuers, and different keys cannot retrieve the original key’s charges. Rotation does not transfer access. Authentication is fresh and existing key Origin restrictions apply. UseAuthorization or
x-api-key; cookies, management credentials and partner tokens grant no access.
Conflicting credentials are rejected; equivalent supported prefix/case forms
are accepted.
Polling and errors
Make the first lookup about two seconds after a disconnect or completion. A 404 includesRetry-After: 30: wait at least 30 seconds, plus a small random
delay, before the next lookup. Honor Retry-After on 429 and 503 responses too;
do not cap a larger server-requested delay at 30 seconds. For a network failure
without a response, back off with jitter from two seconds up to 30 seconds.
Stop on 200. Stop and correct the request or seek support on 400, 401, 403 or 409;
do not automatically rerun inference to resolve a billing lookup error. This API
does not expose a finalized flag and clients do not need to keep polling for
later refunds. Clients are responsible for polling and honoring retry delays.
The shared budgets are 60 lookups/minute per key and 300/minute per trusted
IP, including unsuccessful lookups. Coordinate polling across concurrent
requests. If no charge appears, stop retries no later than 24 hours after the
original request and retain its ID for support. This is a retry limit, not a
promise that every billing record will become available within 24 hours.
| Status | Meaning |
|---|---|
| 200 | One recorded primary charge, with exact cost, currency, tokens and expiry. |
| 400 | Invalid request ID, unsupported query parameters, or conflicting credentials. |
| 401 / 403 | Credential, issuer or key-policy rejection; stop and correct access. |
| 404 | No eligible charge visible to this key. May be delayed, expired, inaccessible or absent; never assume zero cost. Wait at least the indicated 30 seconds before retrying. |
| 409 | Multiple eligible primary charges match this ID; do not automatically retry or infer a total. |
| 429 | Quota exceeded; honor Retry-After. |
| 503 | The billing lookup is temporarily unavailable; honor Retry-After. |
Cache-Control: private, no-store, max-age=0, private/no-store
CDN headers and Vary: Authorization, x-api-key.
Capture the ID before reading a stream
This JavaScript example saves the ID when the response headers arrive. Keep it even if reading the body fails.model and messages are the values from your
normal chat request, and apiKey is your NanoGPT API key.
const response = await fetch('https://nano-gpt.com/api/v1/chat/completions', {
method: 'POST',
headers: {
Authorization: `Bearer ${apiKey}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ model, messages, stream: true }),
});
const requestId = response.headers.get('x-request-id');
// Save requestId before consuming response.body with your SSE parser.
// If the stream disconnects, keep that ID and follow the polling guidance above.
if (requestId) {
const lookup = await fetch(
`https://nano-gpt.com/api/v1/usage/requests/${encodeURIComponent(requestId)}`,
{ headers: { Authorization: `Bearer ${apiKey}` } },
);
if (lookup.ok) {
const billing = await lookup.json();
// Preserve billing.cost as a decimal string, or use a decimal library.
} else {
// Handle lookup.status and lookup.headers.get('retry-after') as above.
}
}
Retry-After may contain seconds or an HTTP date; respect its full delay.
For aggregate spend and refunds over a date range, use Usage.Authorizations
Bearer authentication header of the form Bearer <token>, where <token> is your auth token.
Path Parameters
X-Request-ID captured from response headers before disconnect. Prefer omitting the incoming header so the server generates one. This is not an idempotency key: use a distinct ID for each inference execution, including retries; repeated IDs may return 409.
1 - 128^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$Response
One recorded primary charge from the last 24 hours. All responses use private/no-store and Vary: Authorization, x-api-key.
"request_billing"1 - 128^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$Ledger accounting timestamp; not necessarily insertion time.
24 hours after the charge accounting timestamp. The record is unavailable at or after this instant; financial records are retained.
Database statement snapshot time.
Exact nonnegative amount recorded on the primary charge. Does not subtract refunds or add separately billed extras; not proof of balance collection.
121^\d{1,80}(?:\.\d{1,40})?$USD, XNO "primary_charge"Show child attributes
Show child attributes