Billing API
REST endpoints for billing — request and response reference with examples.
Subscription plans and checkout. The public-plans and public-checkout endpoints require no authentication; the rest operate on the authenticated account. Pricing on www.actuallycare.com/pricing comes from these endpoints.
Base URL: https://api.actuallycare.com/v1 · Errors use the standard envelope — see Errors.
Get public plan pricing#
Returns the current subscription plans with pricing in cents. This endpoint requires no authentication and is the source of truth for pricing shown on the marketing site. Responses are cacheable (5 minutes in browsers, 1 hour at the CDN edge).
Responses#
| Status | Meaning |
|---|---|
200 | Available plans |
429 | Rate limit exceeded — too many requests in the current window. Wait for the window to reset (see Retry-After) before retrying. |
500 | Internal server error |
Example request#
curl "https://api.actuallycare.com/v1/billing/public-plans"const res = await fetch("https://api.actuallycare.com/v1/billing/public-plans");
const data = await res.json();import requests
resp = requests.get(
"https://api.actuallycare.com/v1/billing/public-plans",
)
data = resp.json()Example response (200)#
{
"success": true,
"data": {
"plans": [
{
"key": "agent",
"name": "ActuallyCare+ Agent",
"priceMonthly": 10000,
"priceYearly": 120000,
"trialDays": 30,
"seats": 1,
"features": [
"example features"
]
}
],
"currency": "usd",
"version": "example version"
}
}Start checkout without an account#
Creates a Stripe Checkout session for a new customer who doesn't have an account yet. Only the agent plan is available through this endpoint; team and brokerage subscriptions require an account and the authenticated checkout endpoint. Returns the Stripe-hosted checkout URL to redirect the visitor to.
Request body#
| Field | Type | Required | Description |
|---|---|---|---|
email | string | Yes | Email address for the new customer and Stripe Checkout session · Format: email |
planKey | enum | No | Subscription plan tier (only agent is available without an account) · One of: agent · Default: "agent" |
interval | enum | No | Billing interval (defaults to monthly) · One of: monthly, yearly · Default: "monthly" |
Responses#
| Status | Meaning |
|---|---|
200 | Checkout session created |
400 | Invalid request data |
429 | Rate limit exceeded — too many requests in the current window. Wait for the window to reset (see Retry-After) before retrying. |
500 | Internal server error |
Example request#
curl -X POST "https://api.actuallycare.com/v1/billing/public-checkout" \
-H "Content-Type: application/json" \
-d '{
"email": "[email protected]"
}'const res = await fetch("https://api.actuallycare.com/v1/billing/public-checkout", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
"email": "[email protected]"
}),
});
const data = await res.json();import requests
resp = requests.post(
"https://api.actuallycare.com/v1/billing/public-checkout",
json={
"email": "[email protected]"
},
)
data = resp.json()Example response (200)#
{
"success": true,
"data": {
"url": "example url",
"sessionId": "example sessionId"
}
}Get plans with checkout identifiers#
Returns the subscription plans for the in-app billing page. Same plans as the public endpoint, plus the identifiers needed to start an authenticated checkout. Prices here are in dollars.
Responses#
| Status | Meaning |
|---|---|
200 | Available plans |
401 | Authentication required |
429 | Rate limit exceeded — too many requests in the current window. Wait for the window to reset (see Retry-After) before retrying. |
500 | Internal server error |
Example request#
curl "https://api.actuallycare.com/v1/billing/plans" \
-H "Authorization: Bearer YOUR_JWT"const res = await fetch("https://api.actuallycare.com/v1/billing/plans", {
headers: {
"Authorization": "Bearer YOUR_JWT",
},
});
const data = await res.json();import requests
resp = requests.get(
"https://api.actuallycare.com/v1/billing/plans",
headers={"Authorization": "Bearer YOUR_JWT"},
)
data = resp.json()Example response (200)#
{
"success": true,
"data": [
{
"key": "agent",
"name": "ActuallyCare+ Agent",
"priceId": "price_1QxT4kGH2vSNn8LKcAgentMo",
"priceIds": {
"monthly": "price_1QxT4kGH2vSNn8LKcAgentMo",
"yearly": "price_1QxT5bGH2vSNn8LKcAgentYr"
},
"price": 100,
"priceMonthly": 100,
"priceYearly": 1000,
"seats": 1,
"escrowsPerMonth": -1,
"includes": "base",
"features": [
"1 agent seat",
"Unlimited data",
"Base AI included (~20 requests/session)"
]
}
]
}Get current subscription status#
Returns the authenticated user's subscription state at each scope (personal, team, brokerage) plus which scope currently provides coverage.
Responses#
| Status | Meaning |
|---|---|
200 | Subscription status per scope |
401 | Authentication required |
429 | Rate limit exceeded — too many requests in the current window. Wait for the window to reset (see Retry-After) before retrying. |
500 | Internal server error |
Example request#
curl "https://api.actuallycare.com/v1/billing/subscription" \
-H "Authorization: Bearer YOUR_JWT"const res = await fetch("https://api.actuallycare.com/v1/billing/subscription", {
headers: {
"Authorization": "Bearer YOUR_JWT",
},
});
const data = await res.json();import requests
resp = requests.get(
"https://api.actuallycare.com/v1/billing/subscription",
headers={"Authorization": "Bearer YOUR_JWT"},
)
data = resp.json()Example response (200)#
{
"success": true,
"data": {
"personal": {
"status": "active",
"tier": "agent",
"currentPeriodEnd": "2026-07-15T14:32:10.000Z",
"canceledAt": "2026-07-15T14:32:10.000Z"
},
"team": {},
"brokerage": {},
"effective_coverage": "brokerage"
}
}Start checkout for a subscription#
Creates a Stripe Checkout session for the authenticated user at the requested scope. Valid scope and plan combinations are personal+agent, team+team, brokerage+team, and brokerage+broker. Eligibility depends on your role and how your brokerage pays for seats; ineligible requests return 400 with a specific reason.
Request body#
| Field | Type | Required | Description |
|---|---|---|---|
scope | enum | Yes | Subscription scope the seat applies to (personal, team, or brokerage) · One of: personal, team, brokerage |
planKey | enum | Yes | Subscription plan tier to subscribe to · One of: agent, team, broker |
interval | enum | No | Billing interval (defaults to monthly) · One of: monthly, yearly · Default: "monthly" |
Responses#
| Status | Meaning |
|---|---|
200 | Checkout session created |
400 | Invalid scope/plan combination or eligibility check failed (the message explains why) |
401 | Authentication required |
404 | Team or brokerage not found |
429 | Rate limit exceeded — too many requests in the current window. Wait for the window to reset (see Retry-After) before retrying. |
500 | Internal server error |
Example request#
curl -X POST "https://api.actuallycare.com/v1/billing/checkout" \
-H "Authorization: Bearer YOUR_JWT" \
-H "Content-Type: application/json" \
-d '{
"scope": "personal",
"planKey": "agent"
}'const res = await fetch("https://api.actuallycare.com/v1/billing/checkout", {
method: "POST",
headers: {
"Authorization": "Bearer YOUR_JWT",
"Content-Type": "application/json",
},
body: JSON.stringify({
"scope": "personal",
"planKey": "agent"
}),
});
const data = await res.json();import requests
resp = requests.post(
"https://api.actuallycare.com/v1/billing/checkout",
headers={"Authorization": "Bearer YOUR_JWT"},
json={
"scope": "personal",
"planKey": "agent"
},
)
data = resp.json()Example response (200)#
{
"success": true,
"data": {
"url": "example url",
"sessionId": "example sessionId"
}
}Open the billing portal#
Creates a Stripe customer portal session where the user can manage payment methods, view invoices, and change or cancel their subscription. Requires an existing billing account (returns 400 if the user has never subscribed).
Responses#
| Status | Meaning |
|---|---|
200 | Portal session created |
400 | No billing account found — subscribe to a plan first |
401 | Authentication required |
429 | Rate limit exceeded — too many requests in the current window. Wait for the window to reset (see Retry-After) before retrying. |
500 | Internal server error |
Example request#
curl -X POST "https://api.actuallycare.com/v1/billing/portal" \
-H "Authorization: Bearer YOUR_JWT"const res = await fetch("https://api.actuallycare.com/v1/billing/portal", {
method: "POST",
headers: {
"Authorization": "Bearer YOUR_JWT",
},
});
const data = await res.json();import requests
resp = requests.post(
"https://api.actuallycare.com/v1/billing/portal",
headers={"Authorization": "Bearer YOUR_JWT"},
)
data = resp.json()Example response (200)#
{
"success": true,
"data": {
"url": "example url"
}
}