Brand Partner API¶
A small, predictable REST API for creating mintbot orders, polling their status, and receiving lifecycle events. JSON in, JSON out. Bearer-token auth. Idempotent writes. Signed webhooks.
:material-key: API access dashboard :material-arrow-down: Jump to webhooks
At a glance¶
| Base URL | https://mint.mintbot.ai/api/mintoffice/v1 |
| Auth | Authorization: Bearer mo_live_… |
| Idempotency | Idempotency-Key: <uuid> on every POST |
| Content type | application/json |
| Rate limit | 120 req / 60 s per partner |
| Webhooks | Signed with HMAC-SHA256, retried up to 7 times |
Authentication¶
Every request sends a partner API key in the Authorization header. Generate or rotate the key in the dashboard.
Authorization: Bearer mo_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Content-Type: application/json
Rotation has no grace window
Rotating the key revokes the previous one atomically. Plan to swap the value in your config before clicking Rotate.
Idempotency¶
Every POST requires an Idempotency-Key header. Any UUID per distinct request works.
- We cache the response for 24 hours. A retry with the same key replays the original response with
Idempotent-Replay: true. - Reusing the same key with a different body returns
409 idempotency_key_mismatch.
Orders¶
Create order¶
POST /orders
Creates an order and returns a Stripe Checkout URL. Once payment confirms, mintbot provisions the agent and the partner-cut revenue event is recorded automatically.
Request
{
"tier": "s1",
"duration_days": 30,
"credit_usd": 10,
"language": "en",
"external_id": "your-side-id",
"success_url": "https://your.app/thanks?id={ORDER_ID}",
"cancel_url": "https://your.app/cart",
"webhook_url": "https://your.app/mintbot-webhook"
}
tier- One of
trial,s1,s2,s4. duration_days- Integer in the range
1–365. credit_usd- Optional. Pre-funded chat credit bundled with the order.
language- Optional. Affects Stripe Checkout locale and welcome-email language.
external_id- Optional. Echoed back on every webhook and order response — use it to thread mintbot orders to rows in your own system.
success_url·cancel_url- Stripe Checkout return URLs.
{ORDER_ID}is substituted server-side. webhook_url- Optional. Per-order override of the partner-level webhook URL.
Response — 201 Created
{
"id": 42,
"tier": "s1",
"duration_days": 30,
"credit_usd": 10,
"amount_cents": 1200,
"currency": "usd",
"status": "awaiting_payment",
"checkout_url": "https://checkout.stripe.com/c/pay/cs_test_…",
"panel_url": null,
"expires_at": null,
"language": "en",
"external_id": "your-side-id",
"created_at": "2026-05-16 09:58:00",
"paid_at": null
}
Example
curl -X POST https://mint.mintbot.ai/api/mintoffice/v1/orders \
-H "Authorization: Bearer $MINTBOT_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"tier": "s1",
"duration_days": 30,
"credit_usd": 10,
"success_url": "https://your.app/thanks?id={ORDER_ID}",
"cancel_url": "https://your.app/cart"
}'
import os, uuid, requests
r = requests.post(
"https://mint.mintbot.ai/api/mintoffice/v1/orders",
headers={
"Authorization": f"Bearer {os.environ['MINTBOT_API_KEY']}",
"Idempotency-Key": str(uuid.uuid4()),
},
json={
"tier": "s1",
"duration_days": 30,
"credit_usd": 10,
"success_url": "https://your.app/thanks?id={ORDER_ID}",
"cancel_url": "https://your.app/cart",
},
timeout=10,
)
r.raise_for_status()
order = r.json()
redirect_to = order["checkout_url"]
Get order¶
GET /orders/{id}
Fetches a single order by its mintbot id. Returns the same shape as POST /orders.
curl https://mint.mintbot.ai/api/mintoffice/v1/orders/42 \
-H "Authorization: Bearer $MINTBOT_API_KEY"
List orders¶
GET /orders
Cursor-paginated list, newest first.
Query parameters
status- Optional. Filter by
awaiting_payment,completed,deployed,deploy_failed, orexpired. cursor- Optional. Pass
next_cursorfrom the previous page to continue.
Response
{
"items": [ /* OrderResponse … */ ],
"next_cursor": "37"
}
next_cursor is null when there are no more pages.
Renew order¶
POST /orders/{id}/renew
Extends an existing agent for another duration_days. Infra-only — no fresh chat credit is bundled. Returns a new order id and a fresh Stripe Checkout URL.
Request
{
"duration_days": 30,
"external_id": "your-side-id",
"success_url": "https://your.app/thanks?id={ORDER_ID}",
"cancel_url": "https://your.app/account"
}
Revenue¶
Read revenue¶
GET /revenue
Totals plus the latest 200 ledger events.
Query parameters
include_paid- Optional, default
true. Set tofalseto see only events that haven't been paid out yet.
Response
{
"currency": "usd",
"gross_cents": 12000,
"partner_cut_cents": 2000,
"mintbot_cut_cents": 10000,
"unpaid_cents": 800,
"events": [
{
"id": 7,
"order_id": 42,
"kind": "order_paid",
"gross_cents": 1200,
"partner_cut_cents": 200,
"mintbot_cut_cents": 1000,
"currency": "usd",
"created_at": "2026-05-16 09:58:00",
"payout_id": null,
"payout_at": null
}
]
}
Partner profile¶
Get profile¶
GET /partner
Echoes your partner profile plus the unpaid balance — handy for surfacing earnings inside your own admin UI without storing them yourself.
Response
{
"id": 12,
"email": "you@kliendifirma.com",
"pricing_currency": "usd",
"balance_unpaid_cents": 800,
"webhook_url": "https://your.app/mintbot-webhook",
"api_key_prefix": "mo_live_a12b"
}
Webhooks¶
When something happens to an order, we POST a signed JSON event to your configured webhook URL.
Event types¶
| Event | When it fires |
|---|---|
order.created |
Stripe Checkout session minted, awaiting payment. |
order.paid |
Stripe confirmed payment. Revenue event recorded. |
order.cancelled |
Order timed out or was explicitly cancelled. |
agent.provisioning_started |
Deploy pipeline started for this order. |
agent.ready |
Deploy succeeded. Payload contains panel_url and expires_at. |
agent.failed |
Deploy pipeline errored. Check the error field for the step that broke. |
agent.expired |
Agent TTL elapsed. The partner may renew via POST /orders/{id}/renew. |
Delivery & retries¶
- Up to 7 attempts on an exponential schedule:
0s, 30s, 2m, 10m, 1h, 6h, 24h. - After the last attempt, the delivery is marked
exhaustedand stops retrying. - Respond with a
2xxstatus within 10 seconds to acknowledge.
Request headers¶
Content-Type: application/json
User-Agent: mintbot-webhook/1.0
X-Mintbot-Signature: t=<unix_ts>,v1=<hex_hmac_sha256>
X-Mintbot-Event-Id: evt_42_order.paid_1747371234567
X-Mintbot-Event-Type: order.paid
Sample payload — order.paid¶
{
"id": 42,
"tier": "s1",
"duration_days": 30,
"credit_usd": 10,
"amount_cents": 1200,
"currency": "usd",
"status": "completed",
"external_id": "your-side-id",
"paid_at": "2026-05-16 10:02:14"
}
Verifying the signature¶
The signature is HMAC-SHA256(secret, "{timestamp}.{raw_body}"). Always verify the raw request body — re-serialising your parsed JSON will break the comparison.
import hmac, hashlib, time
def verify(secret: str, body: bytes, header: str, tolerance: int = 300) -> bool:
try:
ts_part, v1_part = header.split(",", 1)
ts = int(ts_part.split("=", 1)[1])
sig = v1_part.split("=", 1)[1]
except Exception:
return False
if abs(int(time.time()) - ts) > tolerance:
return False
expected = hmac.new(
secret.encode(),
f"{ts}.".encode() + body,
hashlib.sha256,
).hexdigest()
return hmac.compare_digest(expected, sig)
const crypto = require("crypto");
function verify(secret, rawBody, header, toleranceSec = 300) {
const [tsPart, v1Part] = header.split(",");
const ts = Number(tsPart.split("=")[1]);
const sig = v1Part.split("=")[1];
if (Math.abs(Date.now() / 1000 - ts) > toleranceSec) return false;
const expected = crypto
.createHmac("sha256", secret)
.update(`${ts}.`)
.update(rawBody)
.digest("hex");
return crypto.timingSafeEqual(
Buffer.from(expected, "hex"),
Buffer.from(sig, "hex"),
);
}
Idempotent receivers
Use X-Mintbot-Event-Id as your dedup key — retries reuse the same id, so a row-level INSERT … ON CONFLICT DO NOTHING on that column keeps your handler safe.
Errors¶
Every error response carries a stable code you can branch on programmatically. The message field is a human-readable hint and may change between releases. The request_id echoes the X-Request-Id response header — include it in support tickets.
{
"error": {
"code": "invalid_api_key",
"message": "API key is unknown or revoked.",
"request_id": "f0c2d6c4-…"
}
}
| Code | Meaning |
|---|---|
unauthenticated |
Missing or malformed Authorization header. |
invalid_api_key |
API key is unknown or has been rotated out. |
rate_limited |
Per-partner or per-IP rate limit exceeded — see Retry-After. |
missing_idempotency_key |
POST request without Idempotency-Key. |
idempotency_key_mismatch |
Same key reused with a different request body. |
validation_error |
Request body failed schema validation. |
not_found |
Resource does not exist or does not belong to your partner. |
payment_gateway_error |
Stripe rejected the Checkout session creation. |
Rate limits¶
- 120 requests / 60 s rolling window, per partner. Burst and steady-state share the same bucket.
- 60 requests / 60 s separate per-IP bucket for unauthenticated and failed-auth requests — keeps a bearer brute-force from chewing through the partner quota.
- Excess requests return
429 rate_limitedwith aRetry-Afterheader (seconds to back off).
Need help?¶
These docs are written for the partners who actually use the API. If something is missing, confusing, or stale, mention it to your mintbot agent — they'll forward the feedback and we'll update the page.