Documentation menu: API reference
API reference
Send pageviews, custom events, conversions and purchases to theBizness.ai from your website or your server. These are the public tracking endpoints. The dashboard's data APIs require a signed-in session and are not documented here.
Base URL
https://thebizness.ai/api
Authentication
Your domain for browsers, an ingest secret for servers
Format
JSON over POST
Endpoints
window.thebizness(...) or one of these endpoints.- Record a pageviewPOST
/api/events - Record a custom event, optionally with revenuePOST
/api/events/event - Record a conversion, optionally with an amountPOST
/api/conversions - Legacy: check a page URL against your conversion rulesPOST
/api/conversions/check
Authentication
Every request names a website with websiteId and proves where it comes from. There are two ways, and the way decides how the data is labeled in your dashboard.
From a visitor's browser
- The request must carry an
Originheader, or aRefererheader when there is no Origin, whose host is your website's domain or one of its subdomains. A leadingwww.is ignored. - Neither header:
403 origin_required. Another domain:403 origin_not_allowed. Nothing is stored. localhostand127.0.0.1are accepted only outside production, or when your website's own domain is localhost.- Revenue and conversions that arrive this way are client-reported and shown as unverified.
From your server
- Send
Authorization: Bearer <ingest secret>. The origin check is skipped. - Find the secret on the Configure page of your dashboard. You can show it there and rotate it. Rotating invalidates the old secret, within a minute on every server.
- A Bearer token that is not the website's secret is always
401 invalid_secret, even from your own domain. - Revenue and events recorded this way are verified.
- Keep the secret in an environment variable on your server. Never put it in browser code. Browsers cannot send it to these endpoints on purpose, because
Authorizationis not an allowed cross-origin header.
Send a sale from your server
Server-side reporting is the most reliable way to record revenue you collect outside Stripe, and it is the way to include the customer's email address with a sale. Read the visitor and session IDs from the buyer's thebizness_visitor_id and thebizness_session_id cookies, so the sale is linked to the visit that brought them. Without them the sale is still recorded, but it has no traffic source.
// Server-side only. The ingest secret belongs in an environment variable on your server.
// Never put it in browser code, a mobile app, a public repository or a front-end build.
async function reportPurchase({ orderId, amount, currency, email, visitorId, sessionId }) {
const response = await fetch('https://thebizness.ai/api/conversions', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${process.env.THEBIZNESS_INGEST_SECRET}`,
},
body: JSON.stringify({
websiteId: process.env.THEBIZNESS_WEBSITE_ID,
name: 'Purchase',
conversionUrl: 'https://yourdomain.com/checkout/success',
amount, // in currency units, e.g. 99.99 (not cents)
currency, // ISO code, e.g. 'USD'
orderId, // a repeated orderId is recorded once, so retries are safe
visitorId, // from the buyer's thebizness_visitor_id cookie
sessionId, // from the buyer's thebizness_session_id cookie
metadata: { email }, // the customer on the revenue record: send it from your server, never from the browser
}),
});
// 429 and 5xx mean "not stored, try again". Anything else is final.
if (response.status === 429 || response.status >= 500) {
throw new Error(`theBizness.ai is unavailable (${response.status}), retry later`);
}
const result = await response.json();
if (result.accepted === false) {
console.warn('theBizness.ai did not record the sale:', result.reason);
}
return result;
}import os
import requests
# Server-side only: keep the ingest secret in an environment variable.
response = requests.post(
"https://thebizness.ai/api/conversions",
headers={"Authorization": f"Bearer {os.environ['THEBIZNESS_INGEST_SECRET']}"},
json={
"websiteId": os.environ["THEBIZNESS_WEBSITE_ID"],
"name": "Purchase",
"conversionUrl": "https://yourdomain.com/checkout/success",
"amount": 99.99, # in currency units, e.g. 99.99 (not cents)
"currency": "USD",
"orderId": "order_123456", # a repeated orderId is recorded once, so retries are safe
"metadata": {"email": "[email protected]"},
},
timeout=10,
)
# 429 and 5xx mean "not stored, try again". Anything else is final.
if response.status_code == 429 or response.status_code >= 500:
raise RuntimeError(f"theBizness.ai is unavailable ({response.status_code}), retry later")
result = response.json()
if result.get("accepted") is False:
print("theBizness.ai did not record the sale:", result["reason"])curl https://thebizness.ai/api/conversions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $THEBIZNESS_INGEST_SECRET" \
-d '{
"websiteId": "your-website-id",
"name": "Purchase",
"conversionUrl": "https://yourdomain.com/checkout/success",
"amount": 99.99,
"currency": "USD",
"orderId": "order_123456",
"metadata": { "email": "[email protected]" }
}'Limits and plan enforcement
Request limits
- Request bodies are capped at 32 KB. Larger requests return 413.
eventDataandmetadataobjects over 4 KB are reduced, not stored whole.eventDatakeeps onlyamount,revenue,currency,orderId,email,productNameandproductId, so a large cart still records the sale.metadatakeeps onlyemail. Everything else is dropped, and kept strings are cut to 320 characters.- A revenue
amountsent from a browser is at most 1,000,000 per event, in the sale's currency. The cap is scaled up for currencies whose unit is worth much less than a dollar: about 10,000,000,000 for IDR and VND, 1,000,000,000 for KRW and NGN, and 100,000,000 for JPY, INR and HUF. A larger amount is rejected with 400, and the error names the cap for that currency, such asmust be at most 1,000,000 USD. A report sent with your ingest secret is only bounded by 1e12 (1,000,000,000,000) in every currency, so a large B2B sale is accepted. - The
amountmust be a plain number in major units, for example 99.99 (not cents, and not a formatted string such as "1,500.00" or "$49"). A value that cannot be read as an amount, or a negative one, still stores the event but records no sale, and the response then carriesrevenue: "ignored", revenueReason: "invalid_amount". - Text fields are trimmed and truncated. Names are capped at 100 to 200 characters, URLs at 2,048. Query strings are removed from stored URLs. Fragments are removed too, except hash-route fragments (
#/pathor#!/path), which are kept without their query. A hash route that contains=or&(also written %3D or %26), or that is longer than 100 characters, is dropped, and only the origin and path are stored. Sign-in redirect fragments such as#access_token=...are therefore never collected. Do not put identifiers in route paths. - Known bots are ignored: the pageview endpoint answers 202
botand stores nothing.
Rate limits
- Pageviews: 120 per minute for each visitor IP address and website.
- Custom events: 60 per minute for each visitor IP address and website.
- Revenue and conversions: 20 per minute for each visitor IP address and website.
- Legacy conversion checks: 120 per minute for each visitor IP address and website.
- All requests for one website: 10,000 per minute.
- Daily allowance for browser-reported events. Metered events reported from a visitor's browser (custom events, sales, conversions and URL-rule conversions) also have a daily allowance for each IP address and website: 10% of your plan's event limit, at least 100 and at most 2,500 per UTC day (Starter 100, Pro 500, Growth 2,500), counted across all servers. A purchase event with an amount counts as two. Beyond the allowance the answer is
429 rate_limited("Too many events from this address today") with a Retry-After to the end of the day, and nothing is counted against your plan. A conversion rule that matches a pageview over the allowance is not recorded, but the pageview is. Events you send from your server with the ingest secret, and revenue from Stripe webhooks, are never limited by it. - Requests for websites this server has not looked up in the last minute (unknown ids included): 120 per minute for each IP address. Known websites are served from a cache and do not count.
- If you proxy the script through your own domain, every visitor reaches us from your proxy's address, so the per-address limits apply to your site as a whole. Requests from your own server count against the same limits, from your server's address.
- Wrong origins, wrong secrets and unknown websites are refused before they count against the website's budget, but requests that need a lookup count toward the 120 per minute above.
Known limitation
The website ID is public, because it is in your page source. Anyone who knows it can submit browser-origin events by setting an Origin header, so the origin check is a filter and not authentication. The 120% hard cap and the daily allowance per address only raise the cost of doing that. Server-side reporting with the ingest secret is the tamper-proof path, and neither the hard cap nor the daily allowance applies to it. Use it for any count that matters, such as sales.
Plan enforcement
- Tracked events. Custom events and revenue or conversion events count toward your plan's limit for each billing period, across all your websites (Starter: 1,000, Pro: 5,000, Growth: 25,000). Pageviews are never counted. A visit that matches a conversion rule counts as one conversion. A purchase event that includes an amount is recorded as a custom event and as a sale, so it counts as two tracked events. An event without an amount counts as one.
- At 100% of the limit the dashboard and billing page show a warning. Everything is still recorded.
- Up to 120% of the limit (1,200 on Starter, 6,000 on Pro, 30,000 on Growth) nothing is dropped. Beyond it, custom events and revenue or conversions reported from a browser are refused with
202 plan_limituntil the billing period renews and usage starts again at zero. A purchase event with an amount needs room for both of its events, so it can be refused when fewer than two events remain below the cap. - Never dropped for the limit. Revenue that Stripe reports to us is always recorded. Events you send from your server with the ingest secret are recorded without the cap while your subscription is active.
- Inactive account. After a failed payment you keep full access for 7 days. If the subscription is then still unpaid, canceled, or missing, every endpoint answers
202 subscription_inactiveand stores nothing, pageviews included. Revenue that Stripe reports to us is still recorded. Your existing data is kept, and tracking resumes when the subscription is active again.
Responses
{ "accepted": false, "reason": "...", "error": "..." }. The tracking script logs the reason in the browser console only when you set data-debug="true".200acceptedRecorded. A repeated orderId returns duplicate: true and is not recorded again. A sale reported with a Stripe test-mode Checkout Session ID (cs_test_...) returns testMode: true: it is stored as test data and left out of your analytics.202subscription_inactiveThe website owner has no active subscription (or the grace period after a failed payment has ended). Nothing was stored, pageviews included.202plan_limitThe event is above the hard cap of the plan and was not stored. It applies to custom events and revenue or conversions reported from a browser.202botKnown bot traffic is not recorded.400invalid_requestA required field is missing or invalid, a currency is not a valid ISO 4217 code, or the body is not valid JSON.401invalid_secretThe Authorization header carries a Bearer token that is not this website's ingest secret.403origin_requiredA browser request had neither an Origin nor a Referer header, and no ingest secret.403origin_not_allowedThe Origin (or Referer) is not your website's domain or one of its subdomains.404unknown_websiteThe websiteId does not exist. Nothing is stored.413payload_too_largeThe request body is larger than 32 KB.429rate_limitedToo many requests, or, for events reported from a browser, too many from this address today. Wait for the number of seconds in the Retry-After header.500server_errorWe could not record the request. Nothing was stored, and retrying is safe.
Retry only 429 and 5xx responses. A 202 or 4xx answer will not change if you send the same request again.
Endpoint details
POST /api/events
POST https://thebizness.ai/api/events
Content-Type: application/json
Origin: https://yourdomain.com (sent by the browser)
{
"websiteId": "your-website-id",
"url": "https://yourdomain.com/pricing",
"visitorId": "b7c2f0e4-1d2a-4c8e-9f3b-5a6d7e8f9a0b",
"sessionId": "3e9f1a2b-4c5d-4e6f-8a7b-9c0d1e2f3a4b",
"referrer": "https://www.google.com/",
"utm_source": "google",
"utm_medium": "cpc",
"utm_campaign": "spring_sale"
}
// 200 OK
{ "success": true, "accepted": true }Fields
websiteIdRequired. Your website ID. An unknown ID returns 404 and nothing is stored.urlRequired. The full http or https URL of the page.visitorIdRequired. Persistent visitor ID (the thebizness_visitor_id cookie).sessionIdRequired. Session ID (the thebizness_session_id cookie).referrer, original_referrerOptional. Where the visitor came from.utm_source, utm_medium, utm_campaign, utm_term, utm_contentOptional. Campaign parameters.timezone, screenWidth, performanceTimingOptional. Used for country and device reporting.
POST /api/events/event
POST https://thebizness.ai/api/events/event
Content-Type: application/json
Origin: https://yourdomain.com (sent by the browser)
{
"websiteId": "your-website-id",
"eventName": "newsletter_signup",
"url": "https://yourdomain.com/blog",
"visitorId": "b7c2f0e4-1d2a-4c8e-9f3b-5a6d7e8f9a0b",
"sessionId": "3e9f1a2b-4c5d-4e6f-8a7b-9c0d1e2f3a4b",
"eventData": { "plan": "pro" }
}
// 200 OK
{ "success": true, "accepted": true }Fields
websiteIdRequired. Your website ID.eventNameRequired. Up to 100 characters.urlRequired. The full http or https URL of the page.visitorId, sessionIdRequired. Visitor and session IDs.eventDataOptional. A JSON object of up to 4 KB. A larger object keeps only the revenue keys listed under Request limits.domainOptional. Derived from url when omitted.
Revenue events
An event creates a revenue record only when its eventData has a positive amount (or revenue when amount is missing), up to 1,000,000 from a browser (more for currencies with a small unit, see Request limits; a report with the ingest secret is only bounded by 1e12). The amount must be a plain number in major units, such as 99.99: not cents and not a formatted string such as "1,500.00" or "$49". A value that cannot be read is stored as an event without a sale, and the response says revenue: "ignored", revenueReason: "invalid_amount". The event name alone never counts, and price is ignored, so events such as add_to_cart are not recorded as sales. Currency defaults to USD. A currency you send must be a valid ISO 4217 code (in any letter case): anything else, such as ZZZ, is rejected with 400 invalid_request. orderId and email are read from eventData. From a browser the record is unverified, client-reported revenue. With the ingest secret it is verified revenue. Send the customer's email from your server, not from the browser.
A purchase event that includes an amount is recorded as a custom event and as a sale, so it counts as two tracked events. An event without an amount counts as one.
A repeated orderId for the same website is recorded once. This also holds across the two endpoints: an order sent to both POST /api/events/event (with an amount) and POST /api/conversions (with an amount) under the same orderId is recorded once. If the orderId is a Stripe Checkout Session ID (cs_...) or payment intent ID (pi_...) that a connected Stripe account has already recorded, the report is skipped. If Stripe reports the sale afterwards, the client- or server-reported record is marked as superseded and left out of your totals, and the tracked event it used is given back. A different order ID (your own order number) cannot be matched to Stripe's record, so that sale would still be counted twice.
Test mode. If the orderId is the ID of a Stripe test-mode Checkout Session (cs_test_...), the sale is treated as test data: it is stored, left out of your analytics and not counted toward your plan usage, and the accepted answer includes testMode: true. The purchase event itself is still stored and counted as one tracked event. A payment intent ID (pi_...) looks the same in test and live mode, so a sale reported with one cannot be recognized as a test and is recorded as a normal sale. This matters for the recommended thank-you page snippet, which reports the Checkout Session ID: while you test with Stripe test keys, those purchases stay out of your numbers.
If the browser already reported an order and your server then reports the same orderId with the ingest secret, the sale is upgraded to verified in place: it keeps its place in your totals and takes the server's amount, currency, email and product. The answer is duplicate: true.
POST https://thebizness.ai/api/events/event
Content-Type: application/json
Origin: https://yourdomain.com (sent by the browser)
{
"websiteId": "your-website-id",
"eventName": "purchase",
"url": "https://yourdomain.com/thank-you",
"visitorId": "b7c2f0e4-1d2a-4c8e-9f3b-5a6d7e8f9a0b",
"sessionId": "3e9f1a2b-4c5d-4e6f-8a7b-9c0d1e2f3a4b",
"eventData": {
"orderId": "order_123456",
"amount": 99.99,
"currency": "USD"
}
}
// 200 OK. Recorded as unverified, client-reported revenue
{ "success": true, "accepted": true }POST /api/conversions
window.thebizness('conversion', …). The helper sends eventData.orderId (a string or number) as the orderId idempotency key, so pass one whenever the conversion has an amount. Server-side integrations call it directly with the ingest secret.POST https://thebizness.ai/api/conversions
Content-Type: application/json
Authorization: Bearer <your ingest secret> (server-side calls only)
{
"websiteId": "your-website-id",
"name": "Purchase",
"conversionUrl": "https://yourdomain.com/thank-you",
"amount": 99.99,
"currency": "USD",
"visitorId": "b7c2f0e4-1d2a-4c8e-9f3b-5a6d7e8f9a0b",
"sessionId": "3e9f1a2b-4c5d-4e6f-8a7b-9c0d1e2f3a4b",
"orderId": "order_123456",
"metadata": { "email": "[email protected]", "plan": "pro" }
}
// 200 OK. Recorded as verified revenue
{ "success": true, "accepted": true, "id": "conv_5b1f0c7a9e2d4a6f8c3b1d0e7a9f2c4b6d8e0a1c" }Fields
websiteIdRequired. Your website ID. An unknown ID returns 404 and nothing is stored.nameRequired. Conversion name, up to 200 characters.conversionUrlRequired. The absolute http(s) URL of the page where the conversion happened. Anything else returns 400 invalid_request and is not stored.amountOptional. A plain number in currency units, for example 99.99 (not cents, and not a formatted string such as "1,500.00" or "$49"). From a browser it is at most 1,000,000 (more for currencies with a small unit, such as JPY, KRW or IDR). A report with the ingest secret is only bounded by 1e12. Every conversion appears in your dashboard with its traffic source. Amounts above zero also count as revenue. A value that cannot be read records the conversion without a value and the response carries revenue: "ignored", revenueReason: "invalid_amount".currencyOptional. A valid ISO 4217 code, for example USD or EUR, in any letter case. Defaults to USD. Anything else, such as ZZZ, is rejected with 400 invalid_request.orderIdOptional. Your order or checkout ID, as a string or a number, up to 200 characters. A repeated orderId is recorded once: see below.visitorId, sessionIdOptional, but needed to link the conversion to traffic sources.metadataOptional. A JSON object of up to 4 KB. A larger object keeps only email. The email field is shown as the customer on the revenue record. Send it from your server only.
Without orderId, every successful POST creates a new conversion, so do not retry a request that already succeeded. With orderId, a repeated request is safe: the same websiteId and orderId are recorded once, and a repeat returns { success: true, accepted: true, id, duplicate: true } without a new conversion or revenue record. The same applies when the orderId is a Stripe Checkout Session ID that the Stripe connection has already recorded for the site. A zero-amount conversion does not count as the sale: if a later report for the same orderId carries an amount, it is recorded as the sale (its own sale record, verified when you send it with the ingest secret, and timestamped when it arrives), and the earlier conversion stays a conversion. A report without an amount that follows a sale is a duplicate. If the browser already reported the order and your server repeats it with the ingest secret, the sale is upgraded to verified in place and takes the server's amount, currency, email and product (the answer is also duplicate: true).
If the orderId is the ID of a Stripe test-mode Checkout Session (cs_test_...), the conversion is treated as test data: it is stored, left out of your analytics and not counted toward your plan usage, and the accepted answer includes testMode: true. A payment intent ID (pi_...) cannot be recognized as test mode, so a conversion reported with one is recorded as normal.
POST /api/conversions/check
POST https://thebizness.ai/api/conversions/check
Content-Type: text/plain
Origin: https://yourdomain.com (sent by the browser)
{
"websiteId": "your-website-id",
"url": "https://yourdomain.com/thank-you",
"visitorId": "b7c2f0e4-1d2a-4c8e-9f3b-5a6d7e8f9a0b",
"sessionId": "3e9f1a2b-4c5d-4e6f-8a7b-9c0d1e2f3a4b"
}
// 200 OK
{ "success": true, "accepted": true, "conversionsDetected": 1, "hasRules": true }Fields
websiteIdRequired. Your website ID.urlRequired. The current page URL.visitorId, sessionIdOptional. Used to avoid counting the same conversion twice.
Matching and de-duplication are the same as for pageviews (see Conversion rules below). The Content-Type header is optional for this endpoint, and the response only contains a count: conversionsDetected.
Conversion rules
How a rule matches
- Only the host and the path of the page are compared. The query string, the fragment and a trailing slash are ignored, and so is letter case.
- Hash routes (#/page) cannot be matched; use a path-based URL for the success page, or send a custom event or the conversion API from your app. A rule whose only distinguishing part is a query string or a #/route (
/#/thank-you,/?order=done) would match your home page once those are ignored, so the Configure page refuses to save it, and a rule saved earlier that looks like this is disabled instead of matching the home page. Change it to a path rule, or send the conversion explicitly withthebizness("conversion", ...)in a hash-routed app. An anchor on a real page is fine:/thank-you#doneis saved as/thank-you. - A rule written as a path (
/thank-you) matches that path on any host. A rule that starts with a host or a full URL (shop.example.com/thank-you) matches that host only. A leadingwww.is ignored. - The whole path has to match.
*stands for any run of characters, slashes included. So/thank-you/*matches/thank-you/123and/thank-you/a/b, but not/thank-youitself. - A rule can contain at most 3
*, and a rule that would match every page (just/*) is refused when you save it. - Existing rules. Rules used to match any page whose address merely contained the rule text, so
checkoutmatched/checkout/complete. A rule now has to match the whole path. If you relied on the old behavior, change the rule on the Configure page, for example to/checkout*. - A rule records a given visitor (or session, without a visitor ID) at most once every 30 minutes, so reloads and back-button visits do not count again.
- New and changed rules take effect within a minute, because each server keeps your rules for 60 seconds.
- A rule can carry an amount and a currency. Because the visitor's browser triggers it, the Configure page applies the same cap as a browser-reported sale: 1,000,000 in a USD-class currency, more for currencies with a small unit (for example 80,000,000 JPY or 1,500,000 IDR are fine). A larger amount is refused when you save the rule, with a message such as
Use an amount of 1,000,000 USD or less.For a larger sale, report it from your server with the ingest secret. - Each recorded conversion counts as one tracked event. A conversion triggered by a visitor's browser is client-reported.
Examples
/thank-youmatches/thank-you, /thank-you/ and /Thank-You, not/thank-you/123./thank-you/*matches/thank-you/123 and /thank-you/a/b, not/thank-you./checkout/*/completematches/checkout/42/complete, not/checkout/complete.https://shop.example.com/thank-youmatchesshop.example.com/thank-you, notwww.example.com/thank-you.shop.example.com/orders/*matchesshop.example.com/orders/9001, notexample.com/orders/9001.
Proxied installs
/api/events/conversions on that domain. Your proxy must forward the whole /api/events/* prefix, with the Origin or Referer header of the visitor's request intact, because the origin check reads it. It should also remove the Cookie and Authorization headers before forwarding. The Next.js guide lists the proxy recipes for Next.js middleware, nginx and Cloudflare Workers.