SMAO API Documentation
Organizations

Create organization and initial owner invitation

Creates a new customer organization under the specified partner (MSP), creates its owner invitation record, and attempts to send the invitation email.

What happens:

  1. The organization is created with the selected plan and linked to the partner via partnerId.
  2. The organization is marked as partner-managed - no Stripe billing is set up for the customer.
  3. An invitation record is created for owner.email. Email delivery is best-effort.
  4. Once the owner accepts, they have full product access but cannot manage billing or subscriptions.

The externalCustomerId must be unique across all your organizations (not just per partner). It is the idempotency key for this endpoint: retrying a create with the same value returns DUPLICATE_EXTERNAL_CUSTOMER_ID rather than creating a second organization. Sending the literal strings undefined or null is rejected with VALIDATION_ERROR — a serialised missing value would otherwise be stored verbatim and disable that protection for every later create.

POST
/organizations
AuthorizationBearer <token>

Long-lived distributor API key scoped to a channel partner on the shared smao instance.

In: header

organization
owner
externalCustomerIdstring

Your unique customer identifier on the distributor side. This is the idempotency key for organization creation: a retried request carrying the same value is rejected with DUPLICATE_EXTERNAL_CUSTOMER_ID instead of creating a second organization. The literal strings "undefined" and "null" are rejected with VALIDATION_ERROR, because they mean the value was missing on your side and would silently disable that protection.

Length1 <= length <= 255
externalContractId?string|null

Optional contract identifier on the distributor side. The literal strings "undefined" and "null" are rejected with VALIDATION_ERROR; send JSON null or omit the field instead.

Lengthlength <= 255
partnerIdstring

ID of the partner (MSP) this organization belongs to. Must reference an active partner.

Match^[a-fA-F0-9]{24}$
plan

Response Body

application/json

application/json

application/json

application/json

application/json

application/json

application/json

application/json

curl -X POST "https://api.smao.ai/api/distributor/v1/organizations" \  -H "Content-Type: application/json" \  -d '{    "organization": {      "name": "Acme GmbH",      "timezone": "Europe/Berlin"    },    "owner": {      "email": "[email protected]",      "firstName": "Alice",      "lastName": "Müller"    },    "externalCustomerId": "SYN-4711",    "partnerId": "665abc123def456789012345",    "plan": {      "slug": "advanced",      "commitment": "monthly"    }  }'
{
  "status": "success",
  "organization": {
    "id": "69c175970f3e28310926a1aa",
    "name": "Acme GmbH",
    "industry": "IT Services",
    "timezone": "Europe/Berlin",
    "channelPartner": "acme",
    "externalCustomerId": "SYN-4711",
    "externalContractId": "CON-2026-0001",
    "partnerId": "665abc123def456789012345",
    "ownerEmail": "[email protected]",
    "createdAt": "2026-03-23T17:17:11.605Z",
    "plan": {
      "id": "6967b1c7138bed3e7f6bf7e6",
      "slug": "advanced",
      "name": "Advanced",
      "commitment": "monthly",
      "currency": "EUR",
      "baseFee": 6900
    }
  },
  "owner": {
    "email": "[email protected]",
    "firstName": "Alice",
    "lastName": "Müller",
    "role": "admin",
    "status": "invited",
    "source": "distributor-api",
    "invitationId": "69c175970f3e28310926a1bb",
    "expiresAt": "2026-03-30T17:17:11.814Z"
  }
}
{
  "status": "error",
  "errorCode": "INVALID_DATE_RANGE",
  "message": "from and to must be valid YYYY-MM-DD values"
}
{
  "status": "error",
  "errorCode": "INVALID_DATE_RANGE",
  "message": "from and to must be valid YYYY-MM-DD values"
}
{
  "status": "error",
  "errorCode": "INVALID_DATE_RANGE",
  "message": "from and to must be valid YYYY-MM-DD values"
}
{
  "status": "error",
  "errorCode": "INVALID_DATE_RANGE",
  "message": "from and to must be valid YYYY-MM-DD values"
}

{
  "status": "error",
  "errorCode": "RATE_LIMITED",
  "message": "Too many requests for this API key."
}
{
  "status": "error",
  "errorCode": "INVALID_DATE_RANGE",
  "message": "from and to must be valid YYYY-MM-DD values"
}

{
  "status": "error",
  "errorCode": "RATE_LIMIT_UNAVAILABLE",
  "message": "Rate limit storage is unavailable. Try again shortly."
}