Quickstart
Provision an MSP, assign a license, create a customer, and read usage.
This walks the full flow: key → health check → plans → MSP → customer →
usage and billing. Production base URL is
https://api.smao.ai/api/distributor/v2; use https://api2.smao.ai/api/distributor/v2
against the development environment.
1. Obtain a distributor API key
Distributor keys are issued by SMAO during onboarding — there is no self-service issuance. The raw key (~43 characters, base64url) is shown exactly once; only a hash is stored server-side, so put it in a secret manager immediately. Keys expire after 365 days by default — request a replacement before expiry and switch over during an agreed overlap window. Details in Authentication.
All endpoints except /health require the key as a Bearer token:
export SMAO_KEY="your-distributor-key"
export BASE="https://api.smao.ai/api/distributor/v2"2. Sanity-check connectivity
/health is unauthenticated:
curl "$BASE/health"{ "status": "success", "service": "distributor-api", "version": "2.0.0", "timestamp": "2026-07-01T12:00:00.000Z" }3. List assignable plans
curl "$BASE/plans" \
-H "Authorization: Bearer $SMAO_KEY"Pick a plan slug from the response — you need it for every license you
create. See listDistributorPlans.
4. Create an MSP organization
An MSP is created together with its purchased license and an owner. A dashboard
invitation record is created; email delivery is best-effort, and the response
includes owner.invitationId.
curl -X POST "$BASE/organizations" \
-H "Authorization: Bearer $SMAO_KEY" \
-H "Content-Type: application/json" \
-d '{
"type": "msp",
"externalOrganizationId": "acme-msp-1000",
"organization": {
"name": "MSP Hamburg GmbH",
"industry": "IT Services",
"timezone": "Europe/Berlin"
},
"owner": {
"email": "[email protected]",
"firstName": "Ada",
"lastName": "Admin"
},
"license": {
"externalLicenseId": "lic-msp-1000",
"status": "available",
"plan": { "slug": "business", "commitment": "monthly" }
}
}'Returns 201 with the created organization. Save organization.id — it is
the parentOrganizationId for customers below this MSP.
5. Create a customer under the MSP
A customer needs an already available license owned by its parent MSP.
First upsert one:
curl -X PATCH "$BASE/organizations/665abc123def456789012345/licenses" \
-H "Authorization: Bearer $SMAO_KEY" \
-H "Content-Type: application/json" \
-d '{
"externalLicenseId": "lic-customer-4711",
"plan": { "slug": "business", "commitment": "monthly" },
"status": "available"
}'Then create the customer (replace parentOrganizationId with your MSP's
organization.id from step 4):
curl -X POST "$BASE/organizations" \
-H "Authorization: Bearer $SMAO_KEY" \
-H "Content-Type: application/json" \
-d '{
"type": "customer",
"externalOrganizationId": "acme-customer-4711",
"parentOrganizationId": "665abc123def456789012345",
"externalLicenseId": "lic-customer-4711",
"organization": {
"name": "Kunde 1.1 GmbH",
"industry": "Manufacturing",
"timezone": "Europe/Berlin"
}
}'No customer admin is invited automatically, and owner is not accepted for a
customer request.
There is no idempotency-key header. Re-sending a create with the same
externalOrganizationId returns 409 with
DUPLICATE_EXTERNAL_ORGANIZATION_ID; fetch the existing organization to
reconcile. Repeating an identical license upsert returns its current state. A
license that is not available (or not owned by the parent MSP) fails with
LICENSE_NOT_AVAILABLE. Fetch current state before retrying any other mutation.
6. Read usage and billing
Aggregate usage across all your organizations — from/to are required
(YYYY-MM-DD, up to 365 days, no future dates). Add
parentOrganizationId=<mspOrgId> to scope to one MSP and its direct
customers:
curl "$BASE/usage?from=2026-06-01&to=2026-06-30" \
-H "Authorization: Bearer $SMAO_KEY"Billing summary for one organization — usage priced against the current license plan, window limited to 31 days:
curl "$BASE/organizations/665abc123def456789012345/billing-summary?from=2026-06-01&to=2026-06-30" \
-H "Authorization: Bearer $SMAO_KEY"The summary uses the currently assigned license plan; historical plan-window
proration is not performed. Monetary values such as baseFee, costPerUnit,
overageCost, and totalOverageCost use the smallest unit of the accompanying
currency (for example, cents for EUR). See
getDistributorUsage and
getDistributorOrganizationBillingSummary.
7. Poll — there are no webhooks
The API pushes nothing. Poll /organizations, /usage and
billing-summary on your own schedule, and stay under your rate limit
(default 60 requests/minute per key). Authenticated responses carry
X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, and
Retry-After. On 429 RATE_LIMITED or 503 RATE_LIMIT_UNAVAILABLE, back off
and retry.
Full error envelope and codes: Errors.