SMAO API Documentation

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.

On this page