Start a paid-plan checkout
Create an invoice and payment session for a paid Receptionist plan, and get back the URL to send the dealer to. The checkout is for the dealer group and rooftop named in the body; the rooftop in the token is not used.
The subscription is written as PENDING on the chosen plan. It moves to ACTIVE only when a payment-success callback arrives, which happens outside this call.
Auth: Bearer token of a signed-in user; the token must carry a dealer group and rooftop · Rate limit: not documented · Processing: synchronous, waits for the billing service
Before you call it
- Get
plan_keyfrom List Receptionist plans. Use a plan withkind: "PAID". - If the rooftop already has an active, trialing or past-due subscription on a paid plan for this product, the call returns
409. A free-plan subscription, or an earlier checkout left pending, does not block it. - Pass the rooftop you are billing in
enterprise_idandteam_id. Do not rely on the token for this. The route does not check that the caller belongs to them. success_urlandcancel_urlmust be valid URLs. On success, the billing service adds invoice details tosuccess_urlas query parameters.- Unknown body fields are rejected with
422. - The amount billed today is prorated to month end for monthly plans.
- The onboarding console does not call this route today. Its unused checkout helper targets
POST /agent-marketplace/billing/products/receptionist_agent/subscription/checkout, which does not exist. The route on this page is the one the backend serves.
Request body
- Headers: set
Authorization: Bearer YOUR_TOKENandContent-Type: application/json. - Pick the plan: set
agent_keytoreceptionist_agentandplan_keyto the chosen plan. - Set the rest:
success_url,cancel_url,enterprise_idandteam_id. - Send it, then send the dealer to
checkout_urlwith a full page navigation.
| Parameter | Type | Required | Description |
|---|---|---|---|
agent_key | string | Required | Billing product key, receptionist_agent. Minimum length 1. |
plan_key | string | Required | Plan key from the plan list. Minimum length 1. |
success_url | string | Required | URL to return to after payment. Must be a URL; a top-level domain is not required. |
cancel_url | string | Required | URL to return to if the dealer cancels. Same rule. |
enterprise_id | string | Required | Dealer group being billed. Minimum length 1. |
team_id | string | Required | Rooftop being billed. Minimum length 1. |
Sample request
curl -X POST "https://api.spyne.ai/agent-marketplace/billing/checkout" \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"agent_key": "receptionist_agent",
"plan_key": "<planKey>",
"success_url": "https://console.example.com/reception?enterprise_id=<enterpriseId>&team_id=<teamId>",
"cancel_url": "https://console.example.com/reception?enterprise_id=<enterpriseId>&team_id=<teamId>",
"enterprise_id": "<enterpriseId>",
"team_id": "<teamId>"
}'import requests
res = requests.post(
"https://api.spyne.ai/agent-marketplace/billing/checkout",
headers={"Authorization": "Bearer YOUR_TOKEN"},
json={
"agent_key": "receptionist_agent",
"plan_key": "<planKey>",
"success_url": "https://console.example.com/reception",
"cancel_url": "https://console.example.com/reception",
"enterprise_id": "<enterpriseId>",
"team_id": "<teamId>",
},
)
print(res.status_code, res.json())const res = await fetch('https://api.spyne.ai/agent-marketplace/billing/checkout', {
method: 'POST',
headers: {
Authorization: 'Bearer YOUR_TOKEN',
'Content-Type': 'application/json',
},
body: JSON.stringify({
agent_key: 'receptionist_agent',
plan_key: '<planKey>',
success_url: 'https://console.example.com/reception',
cancel_url: 'https://console.example.com/reception',
enterprise_id: '<enterpriseId>',
team_id: '<teamId>',
}),
});
console.log(res.status, await res.json());Sample response
{
"checkout_url": "<checkoutUrl>",
"checkout_session_id": "<checkoutSessionId>",
"invoice_url": "<invoiceUrl>",
"invoice_number": "<invoiceNumber>",
"total_amount": "<amount>",
"currency": "usd",
"invoice_id": "<invoiceId>"
}{
"code": "BILLING_CHECKOUT_FAILED",
"message": "Could not start checkout. Please try again.",
"statusCode": 400
}{
"message": "Invalid or expired Bearer token",
"error": "Unauthorized",
"statusCode": 401,
"code": "UNAUTHORIZED"
}{
"code": "TENANT_UNRESOLVED",
"message": "Your account has no enterprise or rooftop associated with it, so this request cannot be scoped.",
"statusCode": 403
}{
"code": "BILLING_PLAN_NOT_FOUND",
"message": "No plan '<planKey>' on product 'receptionist_agent'.",
"statusCode": 404
}{
"code": "BILLING_SUBSCRIPTION_ALREADY_ACTIVE",
"message": "This rooftop already has an active subscription for this product.",
"statusCode": 409
}{
"code": "VALIDATION_ERROR",
"message": "1 field(s) failed validation.",
"errors": [
{ "field": "agent_key", "code": "<code>", "message": "<message>" }
],
"statusCode": 422
}Response parameters (201)
| Parameter | Type | Description |
|---|---|---|
checkout_url | string | Send the dealer here with a full page navigation. |
checkout_session_id | string | Payment session ID. |
invoice_url | string or null | Invoice raised for this checkout. |
invoice_number | string or null | Invoice number. |
total_amount | number | Amount billed today. |
currency | string | Currency code. |
invoice_id | string or null | Invoice ID. |
Errors
| Status | Code | When it happens |
|---|---|---|
400 | BILLING_CHECKOUT_REJECTED | The billing service rejected the request, for example because a payment is already in progress for the dealer group. Its message is passed through. |
400 | BILLING_CHECKOUT_FAILED | The payment session could not be created. |
400 | BILLING_ZOHO_CUSTOMER_FAILED | The billing customer record could not be created. |
400 | BILLING_CREATE_PAYMENTS_FAILED | The payment step failed. |
400 | BILLING_UPSTREAM_ERROR | The billing service could not be reached. |
401 | UNAUTHORIZED | Missing or invalid Bearer token. |
403 | TENANT_UNRESOLVED | The token has no dealer group or rooftop. |
404 | BILLING_PLAN_NOT_FOUND | No such plan on this product. |
409 | BILLING_SUBSCRIPTION_ALREADY_ACTIVE | The rooftop already has a paid subscription for this product, or a parallel checkout won a race. |
422 | VALIDATION_ERROR | A field is missing or invalid, or the body has an unknown field. All failing fields are listed. |
500 | INTERNAL_ERROR | Unexpected error. |
What happens next
After payment, the dealer lands on success_url with invoice details in the query string. The subscription stays PENDING until the payment-success callback marks it ACTIVE. You can read it with GET /agent-marketplace/billing/products/receptionist_agent/subscription.
Then buy the phone number.
Updated about 1 hour ago
