curl --request POST \
--url https://api.unprice.dev/v1/budgets/report \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"key": "customer:acme",
"leaseKey": "lease_01J9Z3",
"seq": 1,
"captured": "0.05",
"final": false,
"extend": "0.05"
}
'import requests
url = "https://api.unprice.dev/v1/budgets/report"
payload = {
"key": "customer:acme",
"leaseKey": "lease_01J9Z3",
"seq": 1,
"captured": "0.05",
"final": False,
"extend": "0.05"
}
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({
key: 'customer:acme',
leaseKey: 'lease_01J9Z3',
seq: 1,
captured: '0.05',
final: false,
extend: '0.05'
})
};
fetch('https://api.unprice.dev/v1/budgets/report', 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://api.unprice.dev/v1/budgets/report",
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([
'key' => 'customer:acme',
'leaseKey' => 'lease_01J9Z3',
'seq' => 1,
'captured' => '0.05',
'final' => false,
'extend' => '0.05'
]),
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;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.unprice.dev/v1/budgets/report"
payload := strings.NewReader("{\n \"key\": \"customer:acme\",\n \"leaseKey\": \"lease_01J9Z3\",\n \"seq\": 1,\n \"captured\": \"0.05\",\n \"final\": false,\n \"extend\": \"0.05\"\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))
}HttpResponse<String> response = Unirest.post("https://api.unprice.dev/v1/budgets/report")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"key\": \"customer:acme\",\n \"leaseKey\": \"lease_01J9Z3\",\n \"seq\": 1,\n \"captured\": \"0.05\",\n \"final\": false,\n \"extend\": \"0.05\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.unprice.dev/v1/budgets/report")
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 \"key\": \"customer:acme\",\n \"leaseKey\": \"lease_01J9Z3\",\n \"seq\": 1,\n \"captured\": \"0.05\",\n \"final\": false,\n \"extend\": \"0.05\"\n}"
response = http.request(request)
puts response.read_body{
"lease": {
"leaseKey": "<string>",
"seq": 1,
"granted": "<string>",
"captured": "<string>",
"expiresAt": 123,
"status": "open"
},
"terms": {
"holds": {
"defaultTtlMs": 123,
"maxTtlMs": 123
},
"closesAt": 123
},
"extension": {
"requested": "<string>",
"granted": "<string>",
"denied": {
"kind": "budget_exceeded",
"blocked": {
"limitId": "<string>",
"window": "month",
"remaining": "<string>",
"held": "<string>",
"resetsAt": 123,
"remedy": "release_holds"
},
"balance": {
"remaining": "<string>",
"held": "<string>",
"limits": [
{
"id": "<string>",
"window": "month",
"enforce": "hard",
"amount": "<string>",
"consumed": "<string>",
"held": "<string>",
"remaining": "<string>",
"resetsAt": 123
}
],
"asOf": 123
}
}
},
"released": "<string>",
"crossed": [
"<string>"
],
"replayed": true,
"balance": {
"remaining": "<string>",
"held": "<string>",
"limits": [
{
"id": "<string>",
"window": "month",
"enforce": "hard",
"amount": "<string>",
"consumed": "<string>",
"held": "<string>",
"remaining": "<string>",
"resetsAt": 123
}
],
"asOf": 123
}
}{
"error": {
"code": "BAD_REQUEST",
"docs": "https://docs.unprice.dev/api-reference/errors/code/BAD_REQUEST",
"message": "<string>",
"requestId": "req_1234",
"details": {
"kind": "invalid_config",
"issues": [
{
"path": "config.plans[0].version.features[1].featureSlug",
"message": "<string>"
}
]
}
}
}{
"error": {
"code": "UNAUTHORIZED",
"docs": "https://docs.unprice.dev/api-reference/errors/code/UNAUTHORIZED",
"message": "<string>",
"requestId": "req_1234",
"details": {
"kind": "invalid_config",
"issues": [
{
"path": "config.plans[0].version.features[1].featureSlug",
"message": "<string>"
}
]
}
}
}{
"error": {
"code": "FORBIDDEN",
"docs": "https://docs.unprice.dev/api-reference/errors/code/FORBIDDEN",
"message": "<string>",
"requestId": "req_1234",
"details": {
"kind": "invalid_config",
"issues": [
{
"path": "config.plans[0].version.features[1].featureSlug",
"message": "<string>"
}
]
}
}
}{
"error": {
"code": "NOT_FOUND",
"docs": "https://docs.unprice.dev/api-reference/errors/code/NOT_FOUND",
"message": "<string>",
"requestId": "req_1234",
"details": {
"kind": "invalid_config",
"issues": [
{
"path": "config.plans[0].version.features[1].featureSlug",
"message": "<string>"
}
]
}
}
}{
"error": {
"code": "CONFLICT",
"docs": "https://docs.unprice.dev/api-reference/errors/code/CONFLICT",
"message": "<string>",
"requestId": "req_1234",
"details": {
"kind": "invalid_config",
"issues": [
{
"path": "config.plans[0].version.features[1].featureSlug",
"message": "<string>"
}
]
}
}
}{
"error": {
"code": "PRECONDITION_FAILED",
"docs": "https://docs.unprice.dev/api-reference/errors/code/PRECONDITION_FAILED",
"message": "<string>",
"requestId": "req_1234",
"details": {
"kind": "invalid_config",
"issues": [
{
"path": "config.plans[0].version.features[1].featureSlug",
"message": "<string>"
}
]
}
}
}{
"error": {
"code": "PAYLOAD_TOO_LARGE",
"docs": "https://docs.unprice.dev/api-reference/errors/code/PAYLOAD_TOO_LARGE",
"message": "<string>",
"requestId": "req_1234",
"details": {
"kind": "invalid_config",
"issues": [
{
"path": "config.plans[0].version.features[1].featureSlug",
"message": "<string>"
}
]
}
}
}{
"error": {
"code": "TOO_MANY_REQUESTS",
"docs": "https://docs.unprice.dev/api-reference/errors/code/TOO_MANY_REQUESTS",
"message": "<string>",
"requestId": "req_1234",
"details": {
"kind": "invalid_config",
"issues": [
{
"path": "config.plans[0].version.features[1].featureSlug",
"message": "<string>"
}
]
}
}
}{
"error": {
"code": "INTERNAL_SERVER_ERROR",
"docs": "https://docs.unprice.dev/api-reference/errors/code/INTERNAL_SERVER_ERROR",
"message": "<string>",
"requestId": "req_1234",
"details": {
"kind": "invalid_config",
"issues": [
{
"path": "config.plans[0].version.features[1].featureSlug",
"message": "<string>"
}
]
}
}
}report a lease's usage, and extend or close it
Report the cumulative usage spent from a lease. A report that is not final may ask for more capacity: what fits is granted, and a short extension is part of the answer, never an error, so the usage always counts. Reports renew the lease once less than half its ttlMs remains. A final report closes the lease and returns the unused grant. Reports are numbered from 1; resending the last one replays its answer, and any other number returns 409. After expiresAt only a final report is accepted.
curl --request POST \
--url https://api.unprice.dev/v1/budgets/report \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"key": "customer:acme",
"leaseKey": "lease_01J9Z3",
"seq": 1,
"captured": "0.05",
"final": false,
"extend": "0.05"
}
'import requests
url = "https://api.unprice.dev/v1/budgets/report"
payload = {
"key": "customer:acme",
"leaseKey": "lease_01J9Z3",
"seq": 1,
"captured": "0.05",
"final": False,
"extend": "0.05"
}
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({
key: 'customer:acme',
leaseKey: 'lease_01J9Z3',
seq: 1,
captured: '0.05',
final: false,
extend: '0.05'
})
};
fetch('https://api.unprice.dev/v1/budgets/report', 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://api.unprice.dev/v1/budgets/report",
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([
'key' => 'customer:acme',
'leaseKey' => 'lease_01J9Z3',
'seq' => 1,
'captured' => '0.05',
'final' => false,
'extend' => '0.05'
]),
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;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.unprice.dev/v1/budgets/report"
payload := strings.NewReader("{\n \"key\": \"customer:acme\",\n \"leaseKey\": \"lease_01J9Z3\",\n \"seq\": 1,\n \"captured\": \"0.05\",\n \"final\": false,\n \"extend\": \"0.05\"\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))
}HttpResponse<String> response = Unirest.post("https://api.unprice.dev/v1/budgets/report")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"key\": \"customer:acme\",\n \"leaseKey\": \"lease_01J9Z3\",\n \"seq\": 1,\n \"captured\": \"0.05\",\n \"final\": false,\n \"extend\": \"0.05\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.unprice.dev/v1/budgets/report")
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 \"key\": \"customer:acme\",\n \"leaseKey\": \"lease_01J9Z3\",\n \"seq\": 1,\n \"captured\": \"0.05\",\n \"final\": false,\n \"extend\": \"0.05\"\n}"
response = http.request(request)
puts response.read_body{
"lease": {
"leaseKey": "<string>",
"seq": 1,
"granted": "<string>",
"captured": "<string>",
"expiresAt": 123,
"status": "open"
},
"terms": {
"holds": {
"defaultTtlMs": 123,
"maxTtlMs": 123
},
"closesAt": 123
},
"extension": {
"requested": "<string>",
"granted": "<string>",
"denied": {
"kind": "budget_exceeded",
"blocked": {
"limitId": "<string>",
"window": "month",
"remaining": "<string>",
"held": "<string>",
"resetsAt": 123,
"remedy": "release_holds"
},
"balance": {
"remaining": "<string>",
"held": "<string>",
"limits": [
{
"id": "<string>",
"window": "month",
"enforce": "hard",
"amount": "<string>",
"consumed": "<string>",
"held": "<string>",
"remaining": "<string>",
"resetsAt": 123
}
],
"asOf": 123
}
}
},
"released": "<string>",
"crossed": [
"<string>"
],
"replayed": true,
"balance": {
"remaining": "<string>",
"held": "<string>",
"limits": [
{
"id": "<string>",
"window": "month",
"enforce": "hard",
"amount": "<string>",
"consumed": "<string>",
"held": "<string>",
"remaining": "<string>",
"resetsAt": 123
}
],
"asOf": 123
}
}{
"error": {
"code": "BAD_REQUEST",
"docs": "https://docs.unprice.dev/api-reference/errors/code/BAD_REQUEST",
"message": "<string>",
"requestId": "req_1234",
"details": {
"kind": "invalid_config",
"issues": [
{
"path": "config.plans[0].version.features[1].featureSlug",
"message": "<string>"
}
]
}
}
}{
"error": {
"code": "UNAUTHORIZED",
"docs": "https://docs.unprice.dev/api-reference/errors/code/UNAUTHORIZED",
"message": "<string>",
"requestId": "req_1234",
"details": {
"kind": "invalid_config",
"issues": [
{
"path": "config.plans[0].version.features[1].featureSlug",
"message": "<string>"
}
]
}
}
}{
"error": {
"code": "FORBIDDEN",
"docs": "https://docs.unprice.dev/api-reference/errors/code/FORBIDDEN",
"message": "<string>",
"requestId": "req_1234",
"details": {
"kind": "invalid_config",
"issues": [
{
"path": "config.plans[0].version.features[1].featureSlug",
"message": "<string>"
}
]
}
}
}{
"error": {
"code": "NOT_FOUND",
"docs": "https://docs.unprice.dev/api-reference/errors/code/NOT_FOUND",
"message": "<string>",
"requestId": "req_1234",
"details": {
"kind": "invalid_config",
"issues": [
{
"path": "config.plans[0].version.features[1].featureSlug",
"message": "<string>"
}
]
}
}
}{
"error": {
"code": "CONFLICT",
"docs": "https://docs.unprice.dev/api-reference/errors/code/CONFLICT",
"message": "<string>",
"requestId": "req_1234",
"details": {
"kind": "invalid_config",
"issues": [
{
"path": "config.plans[0].version.features[1].featureSlug",
"message": "<string>"
}
]
}
}
}{
"error": {
"code": "PRECONDITION_FAILED",
"docs": "https://docs.unprice.dev/api-reference/errors/code/PRECONDITION_FAILED",
"message": "<string>",
"requestId": "req_1234",
"details": {
"kind": "invalid_config",
"issues": [
{
"path": "config.plans[0].version.features[1].featureSlug",
"message": "<string>"
}
]
}
}
}{
"error": {
"code": "PAYLOAD_TOO_LARGE",
"docs": "https://docs.unprice.dev/api-reference/errors/code/PAYLOAD_TOO_LARGE",
"message": "<string>",
"requestId": "req_1234",
"details": {
"kind": "invalid_config",
"issues": [
{
"path": "config.plans[0].version.features[1].featureSlug",
"message": "<string>"
}
]
}
}
}{
"error": {
"code": "TOO_MANY_REQUESTS",
"docs": "https://docs.unprice.dev/api-reference/errors/code/TOO_MANY_REQUESTS",
"message": "<string>",
"requestId": "req_1234",
"details": {
"kind": "invalid_config",
"issues": [
{
"path": "config.plans[0].version.features[1].featureSlug",
"message": "<string>"
}
]
}
}
}{
"error": {
"code": "INTERNAL_SERVER_ERROR",
"docs": "https://docs.unprice.dev/api-reference/errors/code/INTERNAL_SERVER_ERROR",
"message": "<string>",
"requestId": "req_1234",
"details": {
"kind": "invalid_config",
"issues": [
{
"path": "config.plans[0].version.features[1].featureSlug",
"message": "<string>"
}
]
}
}
}Authorizations
Bearer authentication header of the form Bearer <token>, where <token> is your auth token.
Body
The report
- Option 1
- Option 2
The budget's key, chosen by you and unique within the project. Up to 128 letters, digits, and : / _ . @ -, starting with a letter or digit.
^[A-Za-z0-9][A-Za-z0-9:/_.@-]{0,127}$"customer:acme"
Up to 256 characters, one per lease, chosen by the client. Retrying a grant with the same key, amount, and ttlMs replays it; another amount or ttlMs returns 409. Lease keys and idempotency keys are separate.
1 - 256"lease_01J9Z3"
1 for the first report on a lease, then one more each time. Resending the last seq with the same body replays its answer; any other seq returns 409.
x > 01
Usage spent from the lease so far, cumulative: never below the last report and never above the grant. A lost report is covered by the next.
^\d{1,19}(\.\d{1,8})?$"0.05"
false More capacity to add to the lease. Near a hard limit less is granted, and the capture still applies.
^\d{1,19}(\.\d{1,8})?$"0.05"
Response
The lease after the report
Show child attributes
Show child attributes
The budget's hold TTL bounds and close time, so holds spent locally follow them
Show child attributes
Show child attributes
Show child attributes
Show child attributes
A final report's unused grant, returned; 0 otherwise
Limit ids whose consumed amount first exceeded the limit's amount. A hard limit crosses only after recorded usage took its room. Reservations do not trigger crossings. Replays preserve the original result.
True when this answer replays an earlier request instead of applying it again
Show child attributes
Show child attributes