Import Opening Credit Balance
curl --request POST \
--url https://api.nozle.app/api/v1/customers/{customer_id}/credit-balance-imports \
--header 'Authorization: Bearer <token>'import requests
url = "https://api.nozle.app/api/v1/customers/{customer_id}/credit-balance-imports"
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/api/v1/customers/{customer_id}/credit-balance-imports', 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/api/v1/customers/{customer_id}/credit-balance-imports"
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/api/v1/customers/{customer_id}/credit-balance-imports")
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/api/v1/customers/{customer_id}/credit-balance-imports",
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/api/v1/customers/{customer_id}/credit-balance-imports")
.header("Authorization", "Bearer <token>")
.asString();Product Credits
Import Opening Credit Balance
Seed the remaining plan credits for an externally billed subscription without an invoice
POST
/
customers
/
{customer_id}
/
credit-balance-imports
Import Opening Credit Balance
curl --request POST \
--url https://api.nozle.app/api/v1/customers/{customer_id}/credit-balance-imports \
--header 'Authorization: Bearer <token>'import requests
url = "https://api.nozle.app/api/v1/customers/{customer_id}/credit-balance-imports"
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/api/v1/customers/{customer_id}/credit-balance-imports', 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/api/v1/customers/{customer_id}/credit-balance-imports"
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/api/v1/customers/{customer_id}/credit-balance-imports")
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/api/v1/customers/{customer_id}/credit-balance-imports",
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/api/v1/customers/{customer_id}/credit-balance-imports")
.header("Authorization", "Bearer <token>")
.asString();Call this Engine API after importing the externally billed subscription. It records the customer’s current spendable balance without charging them again.
Base URL:
For an Entity-scoped balance, include the exact Entity ID associated with the imported subscription. Copy
The import preserves both facts:
It does not create an invoice or publish a usage event. It also marks the current plan grant as already represented, preventing the scheduler from adding another 500 credits during cutover.
If the plan grants more than one Credit System, import each one. Nozle freezes the applicable grant definitions when the billing state is imported, so later catalog edits cannot change the amounts required during an in-progress cutover. Automatic plan grants resume only after every balance from that frozen snapshot has been imported.
https://api.nozle.app/api/v1
Auth: secret key with credit_system:write when API permissions are enabled. Publishable keys are rejected. Idempotency-Key is required.
Import a customer balance
POST /customers/workspace_123/credit-balance-imports
Authorization: Bearer sk_nozle_...
Idempotency-Key: workspace-123-ai-credits-cutover-v1
Content-Type: application/json
{
"external_subscription_id": "merchant_subscription_123",
"credit_system_code": "ai_credits",
"granted_amount": "500",
"remaining_amount": "320",
"dry_run": false
}
external_subscription_id from the Core billing-state import response; do not substitute the request’s external_reference.
{
"external_entity_id": "user_42",
"external_subscription_id": "entity-sub-123",
"credit_system_code": "ai_credits",
"granted_amount": "500",
"remaining_amount": "320",
"dry_run": false
}
granted_amount must exactly match Nozle’s computed grant for the imported current period. This normally equals the plan grant-rule amount, but an Entity rule using prorated joining can produce a smaller current-period grant. remaining_amount must be between zero and the computed granted amount. Send amounts as decimal strings and use a dry run to validate the amount before importing.
Response
{
"credit_balance_import": {
"operation_id": "operation_uuid",
"balance_source_id": "source_uuid",
"external_customer_id": "workspace_123",
"external_subscription_id": "merchant_subscription_123",
"credit_system_code": "ai_credits",
"granted_amount": "500.000000000000",
"remaining_amount": "320.000000000000",
"current_period_start": "2026-08-15T00:00:00Z",
"expires_at": "2026-09-15T00:00:00Z",
"invoice_created": false,
"dry_run": false,
"replayed": false
}
}
Plan allowance 500 credits
Already consumed 180 credits
Opening balance 320 credits
Dry run and retries
Setdry_run to true to validate the customer, subscription, Entity, Credit System, grant rule, period, and amounts without writing ledger state.
Retry an uncertain response with the same idempotency key and identical payload. A replay returns the original imported values, even if the customer has consumed credits since the import. Reusing the key with a different request returns 409.
Do not run the merchant’s local credit deduction and Nozle
.track() as simultaneous authorities. After the import is accepted, switch the gate cleanly: use .can() for the advisory check and .track() for the authoritative deduction.