Skip to content

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 1365.
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, or expired.
cursor
Optional. Pass next_cursor from 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 to false to 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 exhausted and stops retrying.
  • Respond with a 2xx status 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_limited with a Retry-After header (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.