Check Entitlement
curl --request GET \
--url https://api.nozle.app/engine/api/v1/can \
--header 'Authorization: Bearer <token>'import requests
url = "https://api.nozle.app/engine/api/v1/can"
headers = {"Authorization": "Bearer <token>"}
response = requests.get(url, headers=headers)
print(response.text)const options = {method: 'GET', headers: {Authorization: 'Bearer <token>'}};
fetch('https://api.nozle.app/engine/api/v1/can', 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/can"
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))
}require 'uri'
require 'net/http'
url = URI("https://api.nozle.app/engine/api/v1/can")
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<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.nozle.app/engine/api/v1/can",
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;
}HttpResponse<String> response = Unirest.get("https://api.nozle.app/engine/api/v1/can")
.header("Authorization", "Bearer <token>")
.asString();{
"allowed": true,
"reason": "<string>",
"feature_type": "<string>",
"configuration": {},
"used": 123,
"limit": {},
"remaining": {},
"overage": true,
"economics": {}
}Entitlements
Check Entitlement
GET
/
engine
/
api
/
v1
/
can
Check Entitlement
curl --request GET \
--url https://api.nozle.app/engine/api/v1/can \
--header 'Authorization: Bearer <token>'import requests
url = "https://api.nozle.app/engine/api/v1/can"
headers = {"Authorization": "Bearer <token>"}
response = requests.get(url, headers=headers)
print(response.text)const options = {method: 'GET', headers: {Authorization: 'Bearer <token>'}};
fetch('https://api.nozle.app/engine/api/v1/can', 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/can"
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))
}require 'uri'
require 'net/http'
url = URI("https://api.nozle.app/engine/api/v1/can")
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<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.nozle.app/engine/api/v1/can",
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;
}HttpResponse<String> response = Unirest.get("https://api.nozle.app/engine/api/v1/can")
.header("Authorization", "Bearer <token>")
.asString();{
"allowed": true,
"reason": "<string>",
"feature_type": "<string>",
"configuration": {},
"used": 123,
"limit": {},
"remaining": {},
"overage": true,
"economics": {}
}Base URL:
Without
The
A denial is an HTTP
A failed usage read is never treated as zero usage, so a
https://api.nozle.app/engine/api/v1.
Check whether a customer is entitled to use a feature under their current plan. This is the primary gating endpoint — call it before executing any metered or limited operation.
Auth: secret key only. The key requires customer_usage:read. Publishable keys are always rejected.
A subscription on an override plan (a plan made from a catalog plan for one customer, for example a negotiated price or a subscription created from a quote) gets the entitlements and usage limits of the catalog plan it was made from. Its usage is still priced with the override plan’s own charges.
string
required
External customer ID derived by the trusted merchant backend.
string
required
Feature key to check (matches a feature code)
string
External ID of one current subscription of the customer.
/can evaluates that subscription: its plan, entitlements, limits and usage. An unknown or ended subscription answers no_subscription.string
External ID of an Entity.
/can evaluates the Entity’s own subscription, else the customer’s own subscription (not owned by an Entity). With subscription_id too, the subscription must belong to that Entity.subscription_id or entity_id, /can evaluates the customer’s only current subscription. A customer with several current subscriptions is evaluated on its own subscription (not owned by an Entity) when there is exactly one; otherwise the answer is allowed: false with the reason subscription_required. Before Engine 2.0.0, the most recently started subscription was used. See which subscription is checked.
string
URL-encoded JSON object of event properties. The economics estimate uses it to evaluate Cost Sheet conditions and quantities, and, for a sum Feature, reads the call’s units from the Feature’s
field_name. A value that is not a JSON object returns 400.boolean
required
Whether the customer can use this feature
string
Present only when access is denied; omitted when
allowed is true. One of feature_not_found, no_subscription, subscription_required, no_entitlement, usage_limit_exceeded, or economic_policy_denied.string
Type of the feature. Not returned when the feature or the subscription is missing, or when a metered feature has neither an entitlement nor a charge on the plan.
object
Configuration values granted by the plan for a non-metered feature. Omitted when there are none.
integer
required
Units consumed in the current billing period (the running total for a recurring Feature), as a whole number (a fractional total is truncated). Also the real usage of a metered Feature that is priced by a charge and has no entitlement.
0 for a denial that is decided before usage is read, and for features that are not metered. The period is the one Nozle invoices the usage in, in the customer’s time zone. See usage and billing periods.number | null
required
Maximum units allowed by the plan.
null when the plan sets no cap, the feature is not metered, or a metered Feature has no entitlement.number | null
required
Units still available.
null when limit is null.boolean
true when usage has reached a soft limit. The request is still allowed. Omitted otherwise.object
Optional point-in-time economics estimate from the published Cost Sheet. Monetary values are exact decimal strings in
currency, not integer cents.economics.status value is estimated, not_configured, unavailable, or stale. includes identifies what was calculated and excludes explains costs that cannot be known before the operation runs. When a margin policy applies, the separate policy object reports its version, thresholds, stale-data fallback, decision, and reason. Estimated revenue is the unit price × the units of this call: 1 for a count Feature, the field_name value in metadata for a sum Feature. Other aggregations, or a sum call without that value, make the estimate unavailable, and the policy’s on_unavailable decides. See economics.
Denial reasons
reason | Meaning |
|---|---|
feature_not_found | No feature with this code exists in the organization. |
no_subscription | The customer has no active subscription (none started, ended, or cancelled), or the customer does not exist. Also an unknown or ended subscription_id or entity_id. |
subscription_required | The customer has several current subscriptions and not exactly one of them is the customer’s own (not Entity-owned). Send subscription_id or entity_id. |
no_entitlement | The customer’s plan does not include the feature. |
usage_limit_exceeded | A hard limit is reached. A soft limit never denies; it sets overage to true. |
economic_policy_denied | A margin policy in deny mode rejected the estimated cost or margin. The policy object has the detail. |
200 response with allowed: false.
Errors
| Status | Body | Meaning |
|---|---|---|
400 | {"error": "customer_id and feature required"} | customer_id or feature is missing. |
400 | {"error": "metadata must be a JSON object"} | metadata is not a JSON object. |
401 | {"error": "..."} | The Authorization header is missing or malformed, or the key is unknown. |
403 | {"error": "..."} | A publishable key, or a secret key without customer_usage:read. |
503 | {"error": "usage lookup failed", "code": "usage_unavailable", "request_id": "..."} | The usage counter could not be read. The response has Retry-After: 1. Retry after that many seconds, and quote request_id to support. |
503 | {"error": "subscription access unavailable"} | The customer’s subscription could not be read. Retry later. |
503 | {"error": "API key verification is unavailable"} | The key could not be verified. Retry later. |
503 is not an access decision. Branch on code, not on the error text.
When
allowed is false, the reason field tells you why — use it to show contextual upgrade prompts in your UI. The entitlement decision is authoritative for access at that instant. The economics object is an estimate only and does not create usage, alter a pricing period, or write a financial ledger entry.