Check & Deduct Credits
curl --request POST \
--url https://api.nozle.app/engine/api/v1/check-and-deduct \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"customer_id": "<string>",
"credits": 123
}
'import requests
url = "https://api.nozle.app/engine/api/v1/check-and-deduct"
payload = {
"customer_id": "<string>",
"credits": 123
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({customer_id: '<string>', credits: 123})
};
fetch('https://api.nozle.app/engine/api/v1/check-and-deduct', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.nozle.app/engine/api/v1/check-and-deduct"
payload := strings.NewReader("{\n \"customer_id\": \"<string>\",\n \"credits\": 123\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
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/check-and-deduct")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"customer_id\": \"<string>\",\n \"credits\": 123\n}"
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/check-and-deduct",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'customer_id' => '<string>',
'credits' => 123
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$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/check-and-deduct")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"customer_id\": \"<string>\",\n \"credits\": 123\n}")
.asString();{
"allowed": true,
"remaining": 123
}Product Credits
Check & Deduct Credits
POST
/
engine
/
api
/
v1
/
check-and-deduct
Check & Deduct Credits
curl --request POST \
--url https://api.nozle.app/engine/api/v1/check-and-deduct \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"customer_id": "<string>",
"credits": 123
}
'import requests
url = "https://api.nozle.app/engine/api/v1/check-and-deduct"
payload = {
"customer_id": "<string>",
"credits": 123
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({customer_id: '<string>', credits: 123})
};
fetch('https://api.nozle.app/engine/api/v1/check-and-deduct', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.nozle.app/engine/api/v1/check-and-deduct"
payload := strings.NewReader("{\n \"customer_id\": \"<string>\",\n \"credits\": 123\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
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/check-and-deduct")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"customer_id\": \"<string>\",\n \"credits\": 123\n}"
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/check-and-deduct",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'customer_id' => '<string>',
'credits' => 123
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$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/check-and-deduct")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"customer_id\": \"<string>\",\n \"credits\": 123\n}")
.asString();{
"allowed": true,
"remaining": 123
}Base URL:
Check whether a customer’s wallet has enough credits and deduct them. The Engine locks the customer’s newest active wallet for the check-and-deduct calls, so concurrent calls cannot overspend it, then spends the credits through Core’s wallet transaction API (
Auth:
https://api.nozle.app/engine/api/v1.
Deprecated. Use Track and Consume Credits (
POST /usage/track) instead. This is a legacy wallet adapter. It does not apply Feature conversion rates or the product-credit source ledger. New integrations must use Track and Consume Credits.Responses carry Deprecation: true and Link: </api/v1/usage/track>; rel="successor-version". The endpoint still works.POST /wallet_transactions with voided_credits). Core records a voided outbound wallet transaction, updates the wallet’s balances, sends wallet.updated and wallet_transaction.created and runs wallet alerts.
Not idempotent. Every call that returns
allowed: true deducts, and a retry after a timeout or a 5xx can deduct again. An Idempotency-Key header is ignored. For usage you may retry, use Track and Consume Credits, which requires an Idempotency-Key.sk_ only. Publishable keys are rejected with 403. With API key permissions, the key needs credit_system:write and wallet_transaction:write; without either the call returns 403 with code: permission_denied before any deduction.
string
required
External customer ID
number
required
Number of credits to deduct (must be positive)
boolean
required
Whether sufficient balance existed to fulfill the deduction
number
required
The wallet’s
credits_balance after the deduction. When allowed is false it is the current balance, unchanged.When
allowed is false, no credits are deducted and no wallet transaction is written. The request still returns 200, so check allowed rather than the status code. A request that asks for exactly the remaining balance is allowed and leaves remaining at 0.Earlier versions of this endpoint took a
feature field. It was never used. You can still send it: an old value is ignored and has no effect.Example
curl -X POST https://api.nozle.app/engine/api/v1/check-and-deduct \
-H "Authorization: Bearer sk_..." \
-H "Content-Type: application/json" \
-d '{"customer_id": "cust_123", "credits": 5}'
{ "allowed": true, "remaining": 95 }
Errors
| Status | Body | Meaning |
|---|---|---|
200 | { "allowed": false, "remaining": 2 } | The wallet balance is lower than credits. Nothing was deducted. |
400 | { "error": "customer_id and positive credits are required" } | customer_id is empty or missing, or credits is zero, negative or missing. |
400 | { "error": "invalid request body" } | The body is not valid JSON, or a field has the wrong type, for example credits sent as a string. |
404 | { "code": "wallet_not_found", "error": "No active wallet found for this customer." } | The customer does not exist, or has no active wallet. |
403 | { "code": "permission_denied", ... } | API key permissions are on and the key lacks credit_system:write or wallet_transaction:write. Nothing was deducted. |
500 | { "error": "could not deduct wallet credits" } | The deduction failed, for example Core refused the wallet transaction. Nothing was deducted. |
The deducted amount is
credits × the wallet’s rate, rounded to the wallet currency. The deduction lowers credits_balance and the ongoing credit balance by credits, lowers balance_cents and the ongoing balance by that amount, and adds both to the consumed credits and consumed amount. Core records a voided outbound wallet transaction with the money amount and the credits. See Credit balance.