CORTEX

Billing

Plan catalog, per-organization subscription lifecycle, billing profiles, saved Stripe cards, and invoices across every tenant.

Common responses

These responses apply across endpoints in this section unless an endpoint documents an additional response.

StatusMeaningBody
200Request succeeded.Endpoint response object.
201Resource created.Created resource object.
400Invalid request.Error envelope.
401Missing or invalid bearer token.Error envelope.
404Resource not found or not visible to the current organization.Error envelope.

Plans

List plans

Return the entire plan catalog, sorted by name ascending. The catalog is small and global — no pagination or filtering is applied. Use this to populate plan-picker UIs and to validate `planId` values before calling change-plan.

GET/v1/admin/plans
Example requestbash
curl https://api.cortex.cognit-dx.com/v1/admin/plans \
  -H "Authorization: Bearer $CORTEX_TOKEN"

Response codes

StatusMeaningBody
401Missing or invalid credential.`{ error, code, request_id }`
403Admin access required — caller lacks the system admin role or the credential cannot reach admin routes.`{ error, code, request_id }`
200 OKjson
{
  "data": [
    {
      "id": "9a5e0f10-3c0a-4e88-8a89-d6e0b71b9d22",
      "name": "Free",
      "slug": "free",
      "seat_price_monthly": 0,
      "seat_price_yearly": null,
      "currency": "AED",
      "created_at": "2026-05-18T10:24:31.000Z",
      "updated_at": "2026-05-18T10:24:31.000Z"
    },
    {
      "id": "e3a7f2c8-1b9d-4e6a-87c5-3f9c2b1e6d54",
      "name": "Pro",
      "slug": "pro",
      "seat_price_monthly": 9900,
      "seat_price_yearly": 99000,
      "currency": "AED",
      "created_at": "2026-05-18T10:24:31.000Z",
      "updated_at": "2026-05-18T10:24:31.000Z"
    }
  ],
  "total": 0,
  "has_more": false,
  "next_cursor": null
}

Create plan

Add a plan to the catalog. `name` is the display label; `slug` is the stable lowercase identifier used in lookups and subscriptions. Optional fields set seat pricing directly on the row.

POST/v1/admin/plans

Request body

NameTypeRequiredDescription
namestringRequiredDisplay name shown in dashboards (e.g. `Free`, `Pro`). 1–50 chars.
slugstringRequiredStable lowercase identifier (e.g. `free`, `pro`). 1–50 chars; letters, numbers, and hyphens only. Must be unique.
seat_price_monthlyintegerOptionalMonthly seat price in minor currency units. Defaults to 0.
seat_price_yearlyinteger | nullOptionalOptional yearly seat price in minor currency units.
currencystringOptionalISO currency code. Defaults to `USD`.
Example requestbash
curl https://api.cortex.cognit-dx.com/v1/admin/plans \
  -H "Authorization: Bearer $CORTEX_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Pro",
    "slug": "pro",
    "seat_price_monthly": 9900,
    "seat_price_yearly": 99000
  }'

Response codes

StatusMeaningBody
201Plan created.The new Plan row.
409A plan with that slug already exists.`{ error }`
403Admin access required — caller lacks the system admin role or the credential cannot reach admin routes.`{ error, code, request_id }`
201 Createdjson
{
  "id": "e3a7f2c8-1b9d-4e6a-87c5-3f9c2b1e6d54",
  "name": "Pro",
  "slug": "pro",
  "seat_price_monthly": 9900,
  "seat_price_yearly": 99000,
  "currency": "AED",
  "created_at": "2026-05-18T10:24:31.000Z",
  "updated_at": "2026-05-18T10:24:31.000Z"
}

Get plan

Return a single plan from the catalog by ID.

GET/v1/admin/plans/:id

Path parameters

NameTypeRequiredDescription
idstringRequiredThe plan's UUID.
Example requestbash
curl https://api.cortex.cognit-dx.com/v1/admin/plans/9a5e0f10-3c0a-4e88-8a89-d6e0b71b9d22 \
  -H "Authorization: Bearer $CORTEX_TOKEN"

Response codes

StatusMeaningBody
404Plan not found.`{ error }`
403Admin access required — caller lacks the system admin role or the credential cannot reach admin routes.`{ error, code, request_id }`
200 OKjson
{
  "id": "9a5e0f10-3c0a-4e88-8a89-d6e0b71b9d22",
  "name": "Free",
  "slug": "free",
  "seat_price_monthly": 0,
  "seat_price_yearly": null,
  "currency": "AED",
  "created_at": "2026-05-18T10:24:31.000Z",
  "updated_at": "2026-05-18T10:24:31.000Z"
}

Update plan

Patch a plan's name and/or seat pricing fields. The seeded `free` plan slug cannot be changed.

PATCH/v1/admin/plans/:id

Path parameters

NameTypeRequiredDescription
idstringRequiredThe plan's UUID.

Request body

NameTypeRequiredDescription
namestringOptionalNew plan display name. 1–50 chars. Cannot rename the `free` plan slug.
slugstringOptionalNew plan slug. 1–50 chars; lowercase letters, numbers, and hyphens only. Cannot change the protected `free` slug.
seat_price_monthlyintegerOptionalMonthly seat price in minor currency units.
seat_price_yearlyinteger | nullOptionalYearly seat price in minor currency units.
currencystringOptionalISO currency code.
Example requestbash
curl -X PATCH https://api.cortex.cognit-dx.com/v1/admin/plans/e3a7f2c8-1b9d-4e6a-87c5-3f9c2b1e6d54 \
  -H "Authorization: Bearer $CORTEX_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "seat_price_monthly": 12000
  }'

Response codes

StatusMeaningBody
404Plan not found.`{ error }`
409A plan with that name already exists, or attempted to rename the protected `free` plan.`{ error }`
403Admin access required — caller lacks the system admin role or the credential cannot reach admin routes.`{ error, code, request_id }`
200 OKjson
{
  "id": "e3a7f2c8-1b9d-4e6a-87c5-3f9c2b1e6d54",
  "name": "Pro",
  "slug": "pro",
  "seat_price_monthly": 12000,
  "seat_price_yearly": 99000,
  "currency": "AED",
  "created_at": "2026-05-18T10:24:31.000Z",
  "updated_at": "2026-05-18T10:24:31.000Z"
}

Delete plan

Permanently remove a plan from the catalog. Two guards apply: (1) the seeded `free` plan is protected and cannot be deleted; (2) the plan cannot be deleted while any organization holds a live subscription on it (`trialing`/`active`/`past_due` status). Move those orgs to a different plan first via change-plan, then retry.

DELETE/v1/admin/plans/:id

Path parameters

NameTypeRequiredDescription
idstringRequiredThe plan's UUID.
Example requestbash
curl -X DELETE https://api.cortex.cognit-dx.com/v1/admin/plans/e3a7f2c8-1b9d-4e6a-87c5-3f9c2b1e6d54 \
  -H "Authorization: Bearer $CORTEX_TOKEN"

Response codes

StatusMeaningBody
204Plan deleted. No response body.—
404Plan not found.`{ error }`
409Plan is protected (`free`), or one or more organizations have a live subscription on it.`{ error }`
403Admin access required — caller lacks the system admin role or the credential cannot reach admin routes.`{ error, code, request_id }`
204 No Contenttext
(empty response body)

Subscriptions

Get organization subscription

Return the organization's live subscription joined with the plan name. "Live" means status is one of `trialing`, `active`, or `past_due`. Returns 404 when the org has only canceled subscriptions in its history — those rows are kept for audit but don't count as the org being subscribed.

GET/v1/admin/organizations/:id/subscription

Path parameters

NameTypeRequiredDescription
idstringRequiredThe organization's UUID.
Example requestbash
curl https://api.cortex.cognit-dx.com/v1/admin/organizations/f1ba15e0-ebe8-4187-afe6-03ccb25b8815/subscription \
  -H "Authorization: Bearer $CORTEX_TOKEN"

Response codes

StatusMeaningBody
404Organization not found or has no live subscription.`{ error, code }`
403Admin access required — caller lacks the system admin role or the credential cannot reach admin routes.`{ error, code, request_id }`
200 OKjson
{
  "id": "4b8a2f17-9d3e-4c5a-bc18-6e2f9d3a7b1c",
  "organization_id": "f1ba15e0-ebe8-4187-afe6-03ccb25b8815",
  "plan_id": "9a5e0f10-3c0a-4e88-8a89-d6e0b71b9d22",
  "plan_name": "Free",
  "plan_slug": "free",
  "status": "active",
  "billing_source": "manual",
  "stripe_subscription_id": null,
  "trial_ends_at": null,
  "started_at": "2026-05-18T10:24:31.000Z",
  "current_period_start": null,
  "current_period_end": null,
  "cancel_at_period_end": false,
  "canceled_at": null,
  "created_at": "2026-05-18T10:24:31.000Z",
  "updated_at": "2026-05-18T10:24:31.000Z"
}

Change subscription plan

Switch an organization to a different plan. Atomically cancels the current live subscription (kept in the table as audit history) and inserts a new active subscription on the requested plan. Returns the new subscription row.

POST/v1/admin/organizations/:id/subscription/change-plan

Path parameters

NameTypeRequiredDescription
idstringRequiredThe organization's UUID.

Request body

NameTypeRequiredDescription
planIdstringRequiredUUID of the destination plan. Must exist in the catalog (see /admin/plans).
Example requestbash
curl -X POST https://api.cortex.cognit-dx.com/v1/admin/organizations/f1ba15e0-ebe8-4187-afe6-03ccb25b8815/subscription/change-plan \
  -H "Authorization: Bearer $CORTEX_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"plan_id":"e3a7f2c8-1b9d-4e6a-87c5-3f9c2b1e6d54"}'

Response codes

StatusMeaningBody
404Organization or destination plan not found.`{ error, code: 'not_found' | 'plan_not_found' }`
409The organization is already on the requested plan.`{ error, code: 'same_plan' }`
403Admin access required — caller lacks the system admin role or the credential cannot reach admin routes.`{ error, code, request_id }`
200 OKjson
{
  "id": "9c3e7a5d-2b8f-4c1a-9e6d-5b3f8c2a7d14",
  "organization_id": "f1ba15e0-ebe8-4187-afe6-03ccb25b8815",
  "plan_id": "e3a7f2c8-1b9d-4e6a-87c5-3f9c2b1e6d54",
  "plan_name": "Pro",
  "plan_slug": "pro",
  "status": "active",
  "billing_source": "manual",
  "stripe_subscription_id": null,
  "trial_ends_at": null,
  "started_at": "2026-05-18T11:00:00.000Z",
  "current_period_start": null,
  "current_period_end": null,
  "cancel_at_period_end": false,
  "canceled_at": null,
  "created_at": "2026-05-18T11:00:00.000Z",
  "updated_at": "2026-05-18T11:00:00.000Z"
}

Cancel subscription

Cancel the organization's live subscription immediately. After this call the org has no live subscription — runtime requests will be rejected by the policy layer with HTTP 402 `no_active_subscription` until a new subscription is created via change-plan. The canceled row stays in the table for audit. The response is the bare `Subscription` row (no `planName` — unlike get/change-plan, this endpoint does not join through plans).

POST/v1/admin/organizations/:id/subscription/cancel

Path parameters

NameTypeRequiredDescription
idstringRequiredThe organization's UUID.
Example requestbash
curl -X POST https://api.cortex.cognit-dx.com/v1/admin/organizations/f1ba15e0-ebe8-4187-afe6-03ccb25b8815/subscription/cancel \
  -H "Authorization: Bearer $CORTEX_TOKEN"

Response codes

StatusMeaningBody
404Organization not found.`{ error, code: 'not_found' }`
409No live subscription to cancel.`{ error, code: 'no_active_subscription' }`
403Admin access required — caller lacks the system admin role or the credential cannot reach admin routes.`{ error, code, request_id }`
200 OKjson
{
  "id": "4b8a2f17-9d3e-4c5a-bc18-6e2f9d3a7b1c",
  "organization_id": "f1ba15e0-ebe8-4187-afe6-03ccb25b8815",
  "plan_id": "9a5e0f10-3c0a-4e88-8a89-d6e0b71b9d22",
  "status": "canceled",
  "trial_ends_at": null,
  "started_at": "2026-05-18T10:24:31.000Z",
  "current_period_start": null,
  "current_period_end": null,
  "cancel_at_period_end": false,
  "canceled_at": "2026-05-18T11:15:00.000Z",
  "created_at": "2026-05-18T10:24:31.000Z",
  "updated_at": "2026-05-18T11:15:00.000Z"
}

Billing profiles

List billing profiles

List all billing profiles for the organization. Each profile holds a legal name, billing email and address, tax id, and default payment method. One profile per org may be marked `is_default`.

GET/v1/admin/organizations/:id/billing-profiles

Path parameters

NameTypeRequiredDescription
idstringRequiredThe organization's UUID.
Example requestbash
curl https://api.cortex.cognit-dx.com/v1/admin/organizations/f1ba15e0-ebe8-4187-afe6-03ccb25b8815/billing-profiles \
  -H "Authorization: Bearer $CORTEX_TOKEN"

Response codes

StatusMeaningBody
404Organization not found.`{ error, code: 'not_found' }`
403Admin access required.`{ error, code, request_id }`
200 OKjson
{
  "data": [
    {
      "id": "8e5c1f93-6a2d-4b78-9f1c-3a5e7b2c8f14",
      "organization_id": "f1ba15e0-ebe8-4187-afe6-03ccb25b8815",
      "name": "Acme HQ",
      "is_default": true,
      "legal_name": "Acme Inc.",
      "tax_id": "US-12-3456789",
      "registration_number": "C-4821990",
      "billing_email": "ap@acme.test",
      "billing_contact_name": "Dana Reyes",
      "billing_contact_phone": "+1 415 555 0142",
      "address_line1": "100 Market St",
      "address_line2": "Suite 400",
      "address_country": "US",
      "address_city": "San Francisco",
      "address_postal_code": "94103",
      "currency": "USD",
      "payment_terms": "net_30",
      "default_payment_method": "wire",
      "created_at": "2026-05-18T10:24:31.000Z",
      "updated_at": "2026-05-18T10:24:31.000Z"
    }
  ]
}

Get billing profile by ID

Return a single billing profile by ID. Returns 404 if the profile does not belong to the organization.

GET/v1/admin/organizations/:id/billing-profiles/:profileId

Path parameters

NameTypeRequiredDescription
idstringRequiredThe organization's UUID.
profileIdstringRequiredThe billing profile's UUID.
Example requestbash
curl https://api.cortex.cognit-dx.com/v1/admin/organizations/f1ba15e0-ebe8-4187-afe6-03ccb25b8815/billing-profiles/b2c8d3a1-4567-89ab-cdef-012345678901 \
  -H "Authorization: Bearer $CORTEX_TOKEN"

Response codes

StatusMeaningBody
404Organization or billing profile not found.`{ error, code: 'not_found' | 'no_billing_profile' }`
403Admin access required.`{ error, code, request_id }`
200 OKjson
{
  "id": "8e5c1f93-6a2d-4b78-9f1c-3a5e7b2c8f14",
  "organization_id": "f1ba15e0-ebe8-4187-afe6-03ccb25b8815",
  "name": "Acme HQ",
  "is_default": true,
  "legal_name": "Acme Inc.",
  "tax_id": "US-12-3456789",
  "registration_number": "C-4821990",
  "billing_email": "ap@acme.test",
  "billing_contact_name": "Dana Reyes",
  "billing_contact_phone": "+1 415 555 0142",
  "address_line1": "100 Market St",
  "address_line2": "Suite 400",
  "address_country": "US",
  "address_city": "San Francisco",
  "address_postal_code": "94103",
  "currency": "USD",
  "payment_terms": "net_30",
  "default_payment_method": "wire",
  "created_at": "2026-05-18T10:24:31.000Z",
  "updated_at": "2026-05-18T10:24:31.000Z"
}

Get default billing profile

Return the organization's default billing profile. Returns 404 (`no_billing_profile`) if no profile exists.

GET/v1/admin/organizations/:id/billing-profile

Path parameters

NameTypeRequiredDescription
idstringRequiredThe organization's UUID.
Example requestbash
curl https://api.cortex.cognit-dx.com/v1/admin/organizations/f1ba15e0-ebe8-4187-afe6-03ccb25b8815/billing-profile \
  -H "Authorization: Bearer $CORTEX_TOKEN"

Response codes

StatusMeaningBody
404Organization not found, or organization has no billing profile.`{ error, code: 'not_found' | 'no_billing_profile' }`
403Admin access required.`{ error, code, request_id }`
200 OKjson
{
  "id": "8e5c1f93-6a2d-4b78-9f1c-3a5e7b2c8f14",
  "organization_id": "f1ba15e0-ebe8-4187-afe6-03ccb25b8815",
  "name": "Acme HQ",
  "is_default": true,
  "legal_name": "Acme Inc.",
  "tax_id": "US-12-3456789",
  "registration_number": "C-4821990",
  "billing_email": "ap@acme.test",
  "billing_contact_name": "Dana Reyes",
  "billing_contact_phone": "+1 415 555 0142",
  "address_line1": "100 Market St",
  "address_line2": "Suite 400",
  "address_country": "US",
  "address_city": "San Francisco",
  "address_postal_code": "94103",
  "currency": "USD",
  "payment_terms": "net_30",
  "default_payment_method": "wire",
  "created_at": "2026-05-18T10:24:31.000Z",
  "updated_at": "2026-05-18T10:24:31.000Z"
}

Create billing profile

Create a billing profile. An organization may have multiple profiles (e.g. regional legal entities). The first profile becomes the default unless `isDefault` is set. Only `name` and `legalName` are required. Every optional field accepts `null` to clear it, and an empty string is stored as `null` rather than as `""`.

POST/v1/admin/organizations/:id/billing-profiles

Path parameters

NameTypeRequiredDescription
idstringRequiredThe organization's UUID.

Request body

NameTypeRequiredDescription
namestringRequiredThe name you use for this profile, e.g. `Acme HQ`. 1–120 characters.
legalNamestringRequiredRegistered legal entity name as it should appear on invoices. 1–255 characters.
taxIdstring | nullOptionalTax / VAT registration number. Maximum 64 characters.
registrationNumberstring | nullOptionalCompany registration number for the legal entity. Maximum 64 characters.
billingEmailstring | nullOptionalWhere invoices and billing notices are delivered. Lowercased server-side. Maximum 255 characters.
billingContactNamestring | nullOptionalPerson to contact about billing. Maximum 255 characters.
billingContactPhonestring | nullOptionalBilling contact phone number. Length-bounded only — no format is enforced, so international numbers pass through as sent. Maximum 32 characters.
addressLine1string | nullOptionalInvoice address, first line. Maximum 255 characters.
addressLine2string | nullOptionalInvoice address, second line. Maximum 255 characters.
addressCountrystring | nullOptionalInvoice address country as an ISO 3166-1 alpha-2 code, e.g. `AE`. Uppercased server-side. Exactly 2 characters; not checked against a country list.
addressCitystring | nullOptionalInvoice address city. Maximum 120 characters.
addressPostalCodestring | nullOptionalInvoice address postal code. Maximum 32 characters.
isDefaultbooleanOptionalSet true to make this the organization's default profile, demoting the current default.
defaultPaymentMethodstringOptionalOne of `wire`, `check`. Defaults to `wire` on create.
currencystringOptionalInvoicing currency — `AED` or `USD`. Defaults to `AED`.
paymentTermsstringOptionalOne of `due_on_receipt`, `net_15`, `net_30`, `net_60`. Defaults to `net_30`.
Example requestbash
curl https://api.cortex.cognit-dx.com/v1/admin/organizations/f1ba15e0-ebe8-4187-afe6-03ccb25b8815/billing-profiles \
  -H "Authorization: Bearer $CORTEX_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Acme HQ",
    "legal_name": "Acme Inc.",
    "tax_id": "US-12-3456789",
    "registration_number": "C-4821990",
    "billing_email": "ap@acme.test",
    "billing_contact_name": "Dana Reyes",
    "billing_contact_phone": "+1 415 555 0142",
    "address_line1": "100 Market St",
    "address_line2": "Suite 400",
    "address_country": "US",
    "address_city": "San Francisco",
    "address_postal_code": "94103",
    "currency": "USD",
    "payment_terms": "net_30"
  }'

Response codes

StatusMeaningBody
201Billing profile created.The new BillingProfile row.
404Organization not found.`{ error, code: 'not_found' }`
403Admin access required.`{ error, code, request_id }`
201 Createdjson
{
  "id": "8e5c1f93-6a2d-4b78-9f1c-3a5e7b2c8f14",
  "organization_id": "f1ba15e0-ebe8-4187-afe6-03ccb25b8815",
  "name": "Acme HQ",
  "is_default": true,
  "legal_name": "Acme Inc.",
  "tax_id": "US-12-3456789",
  "registration_number": "C-4821990",
  "billing_email": "ap@acme.test",
  "billing_contact_name": "Dana Reyes",
  "billing_contact_phone": "+1 415 555 0142",
  "address_line1": "100 Market St",
  "address_line2": "Suite 400",
  "address_country": "US",
  "address_city": "San Francisco",
  "address_postal_code": "94103",
  "currency": "USD",
  "payment_terms": "net_30",
  "default_payment_method": "wire",
  "created_at": "2026-05-18T10:24:31.000Z",
  "updated_at": "2026-05-18T10:24:31.000Z"
}

Update billing profile

Patch editable fields on a billing profile. An absent field is left untouched, so a single address line can be changed without resending the rest. Every optional field accepts `null` to clear it, and an empty string is stored as `null` rather than as `""`. `name` and `legalName` can be changed but not cleared. The body is strict — passing an unknown field returns 400. Set `isDefault: true` to promote a profile to the org default.

PATCH/v1/admin/organizations/:id/billing-profiles/:profileId

Path parameters

NameTypeRequiredDescription
idstringRequiredThe organization's UUID.
profileIdstringRequiredThe billing profile's UUID.

Request body

NameTypeRequiredDescription
namestringOptionalNew profile name. 1–120 characters. Cannot be set to null or blank.
legalNamestringOptionalNew legal entity name. 1–255 characters. Cannot be set to null or blank.
taxIdstring | nullOptionalTax / VAT registration number. Maximum 64 characters.
registrationNumberstring | nullOptionalCompany registration number for the legal entity. Maximum 64 characters.
billingEmailstring | nullOptionalWhere invoices and billing notices are delivered. Lowercased server-side. Maximum 255 characters.
billingContactNamestring | nullOptionalPerson to contact about billing. Maximum 255 characters.
billingContactPhonestring | nullOptionalBilling contact phone number. Length-bounded only — no format is enforced, so international numbers pass through as sent. Maximum 32 characters.
addressLine1string | nullOptionalInvoice address, first line. Maximum 255 characters.
addressLine2string | nullOptionalInvoice address, second line. Maximum 255 characters.
addressCountrystring | nullOptionalInvoice address country as an ISO 3166-1 alpha-2 code, e.g. `AE`. Uppercased server-side. Exactly 2 characters; not checked against a country list.
addressCitystring | nullOptionalInvoice address city. Maximum 120 characters.
addressPostalCodestring | nullOptionalInvoice address postal code. Maximum 32 characters.
isDefaultbooleanOptionalSet true to make this the organization's default profile, demoting the current default.
defaultPaymentMethodstringOptionalOne of `wire`, `check`. Defaults to `wire` on create.
currencystringOptionalInvoicing currency — `AED` or `USD`.
paymentTermsstringOptionalOne of `due_on_receipt`, `net_15`, `net_30`, `net_60`.
Example requestbash
curl -X PATCH https://api.cortex.cognit-dx.com/v1/admin/organizations/f1ba15e0-ebe8-4187-afe6-03ccb25b8815/billing-profiles/b2c8d3a1-4567-89ab-cdef-012345678901 \
  -H "Authorization: Bearer $CORTEX_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "address_city": "Dubai",
    "address_country": "AE"
  }'

Response codes

StatusMeaningBody
400Strict-body violation — unknown field passed.`{ error, code: 'invalid_request' }`
404Organization or billing profile not found.`{ error, code: 'not_found' | 'no_billing_profile' }`
403Admin access required — caller lacks the system admin role or the credential cannot reach admin routes.`{ error, code, request_id }`
200 OKjson
{
  "id": "8e5c1f93-6a2d-4b78-9f1c-3a5e7b2c8f14",
  "organization_id": "f1ba15e0-ebe8-4187-afe6-03ccb25b8815",
  "name": "Acme HQ",
  "is_default": true,
  "legal_name": "Acme Inc.",
  "tax_id": "US-12-3456789",
  "registration_number": "C-4821990",
  "billing_email": "ap@acme.test",
  "billing_contact_name": "Dana Reyes",
  "billing_contact_phone": "+1 415 555 0142",
  "address_line1": "100 Market St",
  "address_line2": "Suite 400",
  "address_country": "AE",
  "address_city": "Dubai",
  "address_postal_code": "94103",
  "currency": "USD",
  "payment_terms": "net_30",
  "default_payment_method": "wire",
  "created_at": "2026-05-18T10:24:31.000Z",
  "updated_at": "2026-05-18T10:24:31.000Z"
}

Delete billing profile

Delete a billing profile. If the deleted profile was the organization's default, the oldest remaining profile is promoted to default automatically. Invoices that referenced the profile keep the `billing_snapshot` they were issued with and have their `billing_profile_id` set to null, so no invoice loses its billing identity.

DELETE/v1/admin/organizations/:id/billing-profiles/:profileId

Path parameters

NameTypeRequiredDescription
idstringRequiredThe organization's UUID.
profileIdstringRequiredThe billing profile's UUID.
Example requestbash
curl -X DELETE https://api.cortex.cognit-dx.com/v1/admin/organizations/f1ba15e0-ebe8-4187-afe6-03ccb25b8815/billing-profiles/b2c8d3a1-4567-89ab-cdef-012345678901 \
  -H "Authorization: Bearer $CORTEX_TOKEN"

Response codes

StatusMeaningBody
204Billing profile deleted. No response body.—
404Organization not found, or the profile does not belong to it.`{ error, code: 'not_found' | 'billing_profile_not_found' }`
403Admin access required.`{ error, code, request_id }`

Payment methods

List payment methods

List the organization's saved Stripe cards, oldest first, excluding removed ones. Only `provider: "stripe"` rows appear here — the manual `wire`/`check` rails attached to a billing profile are not payment methods an operator manages, and are filtered out. Exactly one live card carries `is_default: true` whenever the organization has any. The response also carries `stripe_publishable_key`, the key a browser client needs to mount Stripe Elements. It is served here rather than baked into a client bundle so it can change without a rebuild, and it is `null` when card payments are not configured on the deployment — a client should hide its add-card affordance in that case.

GET/v1/admin/organizations/:id/payment-methods

Path parameters

NameTypeRequiredDescription
idstringRequiredThe organization's UUID.
Example requestbash
curl https://api.cortex.cognit-dx.com/v1/admin/organizations/f1ba15e0-ebe8-4187-afe6-03ccb25b8815/payment-methods \
  -H "Authorization: Bearer $CORTEX_TOKEN"

Response codes

StatusMeaningBody
200The organization's saved cards.`{ data: PaymentMethod[], stripe_publishable_key }`
404Organization not found.`{ error, code: 'not_found' }`
403Admin access required.`{ error, code, request_id }`
200 OKjson
{
  "data": [
    {
      "id": "c3d9e4b2-5678-9abc-def0-123456789012",
      "organization_id": "f1ba15e0-ebe8-4187-afe6-03ccb25b8815",
      "type": "card",
      "provider": "stripe",
      "provider_payment_method_id": "pm_1PfAbc2eZvKYlo2CQxYz1234",
      "is_default": true,
      "display_label": "Visa •••• 4242",
      "metadata": {
        "brand": "visa",
        "last4": "4242",
        "exp_month": 12,
        "exp_year": 2034,
        "funding": "credit",
        "cardholder_name": "Dana Reyes"
      },
      "created_at": "2026-05-18T10:24:31.000Z"
    }
  ],
  "stripe_publishable_key": "pk_live_51AbCdEfGhIjKlMnOpQrStUv"
}

Start card setup

Create a Stripe SetupIntent so a browser can collect card details directly with Stripe. Card numbers never reach this API, which is what keeps the flow out of PCI scope. This follows Stripe's deferred-intent flow: mount the Payment Element using the `stripe_publishable_key` from the list endpoint, and when the operator submits, call this endpoint and confirm the intent with `stripe.confirmSetup` using the returned `client_secret`, then post the resulting `pm_…` id to `POST /payment-methods`. The intent is created with `usage: "off_session"` so the saved card can be charged later for a subscription. The organization's Stripe customer is created on first use and recorded on the organization.

POST/v1/admin/organizations/:id/payment-methods/setup-intent

Path parameters

NameTypeRequiredDescription
idstringRequiredThe organization's UUID.
Example requestbash
curl -X POST https://api.cortex.cognit-dx.com/v1/admin/organizations/f1ba15e0-ebe8-4187-afe6-03ccb25b8815/payment-methods/setup-intent \
  -H "Authorization: Bearer $CORTEX_TOKEN"

Response codes

StatusMeaningBody
201SetupIntent created.`{ client_secret }`
404Organization not found.`{ error, code: 'not_found' }`
502Stripe rejected the request.`{ error, code: 'stripe_error' }`
503Card payments are not configured on this deployment.`{ error, code: 'stripe_not_configured' }`
201 Createdjson
{
  "client_secret": "seti_1PfAbc2eZvKYlo2C_secret_QxYz1234"
}

Save a payment method

Persist a card the client has already set up with Stripe. Pass the `pm_…` id from the confirmed SetupIntent. The card's brand, last four digits, expiry, and cardholder name are read back from Stripe server-side rather than taken from the request, so a client cannot misreport which card was saved; the body is strict and only accepts the two fields below. The card is attached to the organization's Stripe customer if it is not already. The first card an organization saves becomes its default regardless of `isDefault`, so a subscription always has something to charge. Idempotent on `paymentMethodId`: re-posting a card the organization already holds returns the existing row with `200` instead of creating a duplicate, so a retry after a lost response is safe. `isDefault` is still honored on the repeat.

POST/v1/admin/organizations/:id/payment-methods

Path parameters

NameTypeRequiredDescription
idstringRequiredThe organization's UUID.

Request body

NameTypeRequiredDescription
paymentMethodIdstringRequiredThe Stripe payment method id (`pm_…`) from the confirmed SetupIntent.
isDefaultbooleanOptionalPromote this card to the organization default, demoting the current one. Ignored for the first card, which is always the default.
Example requestbash
curl -X POST https://api.cortex.cognit-dx.com/v1/admin/organizations/f1ba15e0-ebe8-4187-afe6-03ccb25b8815/payment-methods \
  -H "Authorization: Bearer $CORTEX_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "payment_method_id": "pm_1PfAbc2eZvKYlo2CQxYz1234",
    "is_default": true
  }'

Response codes

StatusMeaningBody
201Payment method saved.The new PaymentMethod row.
200The organization already held this payment method — nothing was created.The existing PaymentMethod row.
400Not a card payment method, or a strict-body violation.`{ error, code: 'unsupported_payment_method' | 'validation_error' }`
404Organization not found.`{ error, code: 'not_found' }`
409The payment method is attached to a different Stripe customer.`{ error, code: 'payment_method_conflict' }`
502Stripe rejected the request — for example an unknown payment method id.`{ error, code: 'stripe_error' }`
503Card payments are not configured on this deployment.`{ error, code: 'stripe_not_configured' }`
201 Createdjson
{
  "id": "c3d9e4b2-5678-9abc-def0-123456789012",
  "organization_id": "f1ba15e0-ebe8-4187-afe6-03ccb25b8815",
  "type": "card",
  "provider": "stripe",
  "provider_payment_method_id": "pm_1PfAbc2eZvKYlo2CQxYz1234",
  "is_default": true,
  "display_label": "Visa •••• 4242",
  "metadata": {
    "brand": "visa",
    "last4": "4242",
    "exp_month": 12,
    "exp_year": 2034,
    "funding": "credit",
    "cardholder_name": "Dana Reyes"
  },
  "created_at": "2026-05-18T10:24:31.000Z"
}

Set the default payment method

Promote a card to the organization's default, demoting the current one, and point the Stripe customer's `invoice_settings.default_payment_method` at it so subscriptions bill the same card the API reports. Promote-only: `isDefault` must be `true`, and `false` returns 400 rather than being ignored — demoting the last default would leave an organization holding cards with nothing chargeable, so a card is demoted by promoting a different one. The body is strict.

PATCH/v1/admin/organizations/:id/payment-methods/:paymentMethodId

Path parameters

NameTypeRequiredDescription
idstringRequiredThe organization's UUID.
paymentMethodIdstringRequiredThe payment method's UUID (not the Stripe `pm_…` id).

Request body

NameTypeRequiredDescription
isDefaultbooleanRequiredMust be `true`. Promotes this card to the organization default.
Example requestbash
curl -X PATCH https://api.cortex.cognit-dx.com/v1/admin/organizations/f1ba15e0-ebe8-4187-afe6-03ccb25b8815/payment-methods/c3d9e4b2-5678-9abc-def0-123456789012 \
  -H "Authorization: Bearer $CORTEX_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "is_default": true }'

Response codes

StatusMeaningBody
200Payment method promoted.The updated PaymentMethod row.
400`isDefault` was not `true`, or an unknown field was passed.`{ error, code: 'validation_error' }`
404Organization not found, or the card does not belong to it.`{ error, code: 'not_found' | 'payment_method_not_found' }`
403Admin access required.`{ error, code, request_id }`
200 OKjson
{
  "id": "c3d9e4b2-5678-9abc-def0-123456789012",
  "organization_id": "f1ba15e0-ebe8-4187-afe6-03ccb25b8815",
  "type": "card",
  "provider": "stripe",
  "provider_payment_method_id": "pm_1PfAbc2eZvKYlo2CQxYz1234",
  "is_default": true,
  "display_label": "Visa •••• 4242",
  "metadata": {
    "brand": "visa",
    "last4": "4242",
    "exp_month": 12,
    "exp_year": 2034,
    "funding": "credit",
    "cardholder_name": "Dana Reyes"
  },
  "created_at": "2026-05-18T10:24:31.000Z"
}

Remove a payment method

Detach the card from Stripe so it can no longer be charged, then delete the row. References to it (`invoice_payments.payment_method_id`, `billing_profiles.default_payment_method_id`) are set to null. If the removed card was the default, the oldest remaining card is promoted and the Stripe customer's invoice default follows it; if no card remains, that default is cleared rather than left pointing at the removed card. A card Stripe no longer recognises is still removed locally, so it cannot become unremovable.

DELETE/v1/admin/organizations/:id/payment-methods/:paymentMethodId

Path parameters

NameTypeRequiredDescription
idstringRequiredThe organization's UUID.
paymentMethodIdstringRequiredThe payment method's UUID (not the Stripe `pm_…` id).
Example requestbash
curl -X DELETE https://api.cortex.cognit-dx.com/v1/admin/organizations/f1ba15e0-ebe8-4187-afe6-03ccb25b8815/payment-methods/c3d9e4b2-5678-9abc-def0-123456789012 \
  -H "Authorization: Bearer $CORTEX_TOKEN"

Response codes

StatusMeaningBody
204Payment method removed. No response body.—
404Organization not found, or the card does not belong to it.`{ error, code: 'not_found' | 'payment_method_not_found' }`
403Admin access required.`{ error, code, request_id }`

Invoices

List invoices

List invoices for an organization, newest first. Optionally filter by status (`draft`, `open`, `paid`, `void`, `uncollectible`). No pagination — invoice volume per org stays bounded for the lifetime of this endpoint shape.

GET/v1/admin/organizations/:id/invoices

Path parameters

NameTypeRequiredDescription
idstringRequiredThe organization's UUID.

Query parameters

NameTypeRequiredDescription
statusstringOptionalFilter by invoice status. One of `draft`, `open`, `paid`, `void`, `uncollectible`. Omit to return all.
Example requestbash
curl "https://api.cortex.cognit-dx.com/v1/admin/organizations/f1ba15e0-ebe8-4187-afe6-03ccb25b8815/invoices?status=open" \
  -H "Authorization: Bearer $CORTEX_TOKEN"

Response codes

StatusMeaningBody
404Organization not found.`{ error, code: 'not_found' }`
403Admin access required — caller lacks the system admin role or the credential cannot reach admin routes.`{ error, code, request_id }`
200 OKjson
{
  "data": [
    {
      "id": "2a7f8b1c-5e3d-49a6-b8c2-7d1f5e9c3a48",
      "organization_id": "f1ba15e0-ebe8-4187-afe6-03ccb25b8815",
      "subscription_id": "4b8a2f17-9d3e-4c5a-bc18-6e2f9d3a7b1c",
      "billing_profile_id": "8e5c1f93-6a2d-4b78-9f1c-3a5e7b2c8f14",
      "status": "draft",
      "total_usd_minor": 12500,
      "total_minor": 0,
      "currency": "AED",
      "fx_rate": null,
      "due_date": "2026-06-15T00:00:00.000Z",
      "issued_at": null,
      "paid_at": null,
      "billing_snapshot": {},
      "subscription_snapshot": {},
      "amount_paid_minor": 0,
      "amount_due_minor": 0,
      "payments": [],
      "line_items": [
        {
          "description": "Pro plan – May 2026",
          "amount_minor": 10000,
          "currency": "USD"
        },
        {
          "description": "Overage – 2,500 additional messages",
          "amount_minor": 2500,
          "currency": "USD"
        }
      ],
      "notes": null,
      "created_at": "2026-05-18T10:24:31.000Z",
      "updated_at": "2026-05-18T10:24:31.000Z"
    }
  ],
  "total": 0,
  "has_more": false,
  "next_cursor": null
}

Create invoice

Create a new invoice in `draft` status. Line items and totals are always in **USD** minor units (cents). The invoice's `currency` is taken from the resolved billing profile — it is never accepted from the request body. For an `AED` profile, finalizing applies the global USD→AED rate: `total_minor` becomes the AED amount due and `fx_rate` is frozen. For a `USD` profile, no conversion happens and `total_minor` equals `total_usd_minor`. Payments are recorded in the invoice's currency, matching `total_minor`. An organization with no billing profile falls back to `AED`.

POST/v1/admin/organizations/:id/invoices

Path parameters

NameTypeRequiredDescription
idstringRequiredThe organization's UUID.

Request body

NameTypeRequiredDescription
subscriptionIdstringOptionalOptional UUID of the subscription this invoice covers. When set, must be a subscription on the same organization.
billingProfileIdstring | nullOptionalWhich billing profile to invoice against. Defaults to the organization's default profile; pass null for none. Determines the invoice's currency and payment terms.
totalUsdMinornumberOptionalTotal in USD minor units (cents). Defaults to 0; auto-summed from `lineItems` when provided.
dueDatestringOptionalISO-8601 datetime when payment is due. Optional — leave it out and finalizing derives the due date from the billing profile's `payment_terms`.
line_itemsarrayOptionalArray of line items in USD. Shape: `{ type?: string, description: string, amount_minor: number, quantity?: number }`. Use `type: wallet_topup` for prepaid wallet credit.
notesstringOptionalOperator notes attached to the invoice. Maximum 2,000 chars. Pass null to clear.
Example requestbash
curl https://api.cortex.cognit-dx.com/v1/admin/organizations/f1ba15e0-ebe8-4187-afe6-03ccb25b8815/invoices \
  -H "Authorization: Bearer $CORTEX_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "subscription_id": "4b8a2f17-9d3e-4c5a-bc18-6e2f9d3a7b1c",
    "total_usd_minor": 12500,
    "due_date": "2026-06-15T00:00:00Z",
    "line_items": [
      { "type": "seat_subscription", "description": "Pro plan – May 2026", "amount_minor": 10000 },
      { "description": "Overage – 2,500 additional messages", "amount_minor": 2500 }
    ]
  }'

Response codes

StatusMeaningBody
201Invoice created in `draft` status.The new Invoice row.
404Organization or referenced subscription not found.`{ error, code }`
403Admin access required — caller lacks the system admin role or the credential cannot reach admin routes.`{ error, code, request_id }`
201 Createdjson
{
  "id": "2a7f8b1c-5e3d-49a6-b8c2-7d1f5e9c3a48",
  "organization_id": "f1ba15e0-ebe8-4187-afe6-03ccb25b8815",
  "subscription_id": "4b8a2f17-9d3e-4c5a-bc18-6e2f9d3a7b1c",
  "billing_profile_id": "8e5c1f93-6a2d-4b78-9f1c-3a5e7b2c8f14",
  "status": "draft",
  "total_usd_minor": 12500,
  "total_minor": 0,
  "currency": "AED",
  "fx_rate": null,
  "due_date": "2026-06-15T00:00:00.000Z",
  "issued_at": null,
  "paid_at": null,
  "billing_snapshot": {},
  "subscription_snapshot": {},
  "amount_paid_minor": 0,
  "amount_due_minor": 0,
  "payments": [],
  "line_items": [
    {
      "description": "Pro plan – May 2026",
      "amount_minor": 10000,
      "currency": "USD"
    },
    {
      "description": "Overage – 2,500 additional messages",
      "amount_minor": 2500,
      "currency": "USD"
    }
  ],
  "notes": null,
  "created_at": "2026-05-18T10:24:31.000Z",
  "updated_at": "2026-05-18T10:24:31.000Z"
}

Get invoice

Return a single invoice by ID. Lives at the top level (not nested under the org) because invoice IDs are globally unique and operators typically arrive at this endpoint from an invoice link in a support ticket without knowing the org.

GET/v1/admin/invoices/:id

Path parameters

NameTypeRequiredDescription
idstringRequiredThe invoice's UUID.
Example requestbash
curl https://api.cortex.cognit-dx.com/v1/admin/invoices/2a7f8b1c-5e3d-49a6-b8c2-7d1f5e9c3a48 \
  -H "Authorization: Bearer $CORTEX_TOKEN"

Response codes

StatusMeaningBody
404Invoice not found.`{ error, code: 'not_found' }`
403Admin access required — caller lacks the system admin role or the credential cannot reach admin routes.`{ error, code, request_id }`
200 OKjson
{
  "id": "2a7f8b1c-5e3d-49a6-b8c2-7d1f5e9c3a48",
  "organization_id": "f1ba15e0-ebe8-4187-afe6-03ccb25b8815",
  "subscription_id": "4b8a2f17-9d3e-4c5a-bc18-6e2f9d3a7b1c",
  "billing_profile_id": "8e5c1f93-6a2d-4b78-9f1c-3a5e7b2c8f14",
  "status": "draft",
  "total_usd_minor": 12500,
  "total_minor": 0,
  "currency": "AED",
  "fx_rate": null,
  "due_date": "2026-06-15T00:00:00.000Z",
  "issued_at": null,
  "paid_at": null,
  "billing_snapshot": {},
  "subscription_snapshot": {},
  "amount_paid_minor": 0,
  "amount_due_minor": 0,
  "payments": [],
  "line_items": [
    {
      "description": "Pro plan – May 2026",
      "amount_minor": 10000,
      "currency": "USD"
    },
    {
      "description": "Overage – 2,500 additional messages",
      "amount_minor": 2500,
      "currency": "USD"
    }
  ],
  "notes": null,
  "created_at": "2026-05-18T10:24:31.000Z",
  "updated_at": "2026-05-18T10:24:31.000Z"
}

Update invoice

Patch an invoice. **Only allowed while the invoice is `draft`**. Reassigning `billingProfileId` also moves the invoice's `currency` to the new profile's currency, so a draft never disagrees with the profile it will be issued against.

PATCH/v1/admin/invoices/:id

Path parameters

NameTypeRequiredDescription
idstringRequiredThe invoice's UUID.

Request body

NameTypeRequiredDescription
billingProfileIdstring | nullOptionalReassign the invoice to another billing profile on the same organization, or null for none. The invoice's `currency` follows the new profile (`AED` when set to null).
totalUsdMinornumberOptionalUpdated total in USD minor units (cents).
dueDatestringOptionalISO-8601 datetime. Setting this suppresses the payment-terms derivation at finalize.
lineItemsarrayOptionalReplacement line items array.
notesstringOptionalOperator notes.
Example requestbash
curl -X PATCH https://api.cortex.cognit-dx.com/v1/admin/invoices/2a7f8b1c-5e3d-49a6-b8c2-7d1f5e9c3a48 \
  -H "Authorization: Bearer $CORTEX_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"total_usd_minor": 15000, "notes": "Pro-rated for partial month"}'

Response codes

StatusMeaningBody
404Invoice not found.`{ error, code: 'not_found' }`
409Invoice is not in `draft` (only drafts are editable).`{ error, code: 'invoice_not_editable' }`
403Admin access required — caller lacks the system admin role or the credential cannot reach admin routes.`{ error, code, request_id }`
200 OKjson
{
  "id": "2a7f8b1c-5e3d-49a6-b8c2-7d1f5e9c3a48",
  "organization_id": "f1ba15e0-ebe8-4187-afe6-03ccb25b8815",
  "subscription_id": "4b8a2f17-9d3e-4c5a-bc18-6e2f9d3a7b1c",
  "billing_profile_id": "8e5c1f93-6a2d-4b78-9f1c-3a5e7b2c8f14",
  "status": "draft",
  "total_usd_minor": 15000,
  "total_minor": 0,
  "currency": "AED",
  "fx_rate": null,
  "due_date": "2026-06-15T00:00:00.000Z",
  "issued_at": null,
  "paid_at": null,
  "billing_snapshot": {},
  "subscription_snapshot": {},
  "amount_paid_minor": 0,
  "amount_due_minor": 0,
  "payments": [],
  "line_items": [
    {
      "description": "Pro plan – May 2026",
      "amount_minor": 10000,
      "currency": "USD"
    },
    {
      "description": "Overage – 2,500 additional messages",
      "amount_minor": 2500,
      "currency": "USD"
    }
  ],
  "notes": "Pro-rated for partial month",
  "created_at": "2026-05-18T10:24:31.000Z",
  "updated_at": "2026-05-18T10:24:31.000Z"
}

Finalize invoice

Transition a `draft` invoice to `open`. Copies `billing_snapshot` and `subscription_snapshot` at issue time, and stamps `issued_at`. When the invoice has no explicit `due_date`, one is derived from the billing profile's `payment_terms`, counting from `issued_at` — `net_30` lands 30 days out, `due_on_receipt` on `issued_at` itself. An explicit `due_date` is always kept as-is, and an invoice with no billing profile gets no derived due date. `total_minor` is set in the invoice's own currency. For `AED` the global USD→AED rate is applied and stored in `fx_rate`; for `USD` no conversion happens, so `total_minor` equals `total_usd_minor` and `fx_rate` is `1` — a USD invoice therefore does not need an exchange rate configured. After finalization, record payments with `POST /invoices/:id/payments`.

POST/v1/admin/invoices/:id/finalize

Path parameters

NameTypeRequiredDescription
idstringRequiredThe invoice's UUID.
Example requestbash
curl -X POST https://api.cortex.cognit-dx.com/v1/admin/invoices/2a7f8b1c-5e3d-49a6-b8c2-7d1f5e9c3a48/finalize \
  -H "Authorization: Bearer $CORTEX_TOKEN"

Response codes

StatusMeaningBody
404Invoice not found.`{ error, code: 'not_found' }`
409Invoice is not in `draft` status.`{ error, code: 'invoice_not_draft' }`
409The invoice's currency is neither `AED` nor `USD`, so no total can be computed.`{ error, code: 'unsupported_invoice_currency' }`
503An `AED` invoice was finalized with no USD/AED exchange rate configured.`{ error, code: 'fx_rate_not_configured' }`
403Admin access required — caller lacks the system admin role or the credential cannot reach admin routes.`{ error, code, request_id }`
200 OKjson
{
  "id": "2a7f8b1c-5e3d-49a6-b8c2-7d1f5e9c3a48",
  "organization_id": "f1ba15e0-ebe8-4187-afe6-03ccb25b8815",
  "subscription_id": "4b8a2f17-9d3e-4c5a-bc18-6e2f9d3a7b1c",
  "billing_profile_id": "8e5c1f93-6a2d-4b78-9f1c-3a5e7b2c8f14",
  "status": "open",
  "total_usd_minor": 12500,
  "total_minor": 0,
  "currency": "AED",
  "fx_rate": null,
  "due_date": "2026-06-15T00:00:00.000Z",
  "issued_at": "2026-05-18T11:00:00.000Z",
  "paid_at": null,
  "billing_snapshot": {},
  "subscription_snapshot": {},
  "amount_paid_minor": 0,
  "amount_due_minor": 0,
  "payments": [],
  "line_items": [
    {
      "description": "Pro plan – May 2026",
      "amount_minor": 10000,
      "currency": "USD"
    },
    {
      "description": "Overage – 2,500 additional messages",
      "amount_minor": 2500,
      "currency": "USD"
    }
  ],
  "notes": null,
  "created_at": "2026-05-18T10:24:31.000Z",
  "updated_at": "2026-05-18T10:24:31.000Z"
}

List invoice payments

List all payments recorded against an invoice, sorted newest first.

GET/v1/admin/invoices/:id/payments

Path parameters

NameTypeRequiredDescription
idstringRequiredThe invoice's UUID.
Example requestbash
curl https://api.cortex.cognit-dx.com/v1/admin/invoices/2a7f8b1c-5e3d-49a6-b8c2-7d1f5e9c3a48/payments \
  -H "Authorization: Bearer $CORTEX_TOKEN"

Response codes

StatusMeaningBody
404Invoice not found.`{ error, code: 'not_found' }`
403Admin access required.`{ error, code, request_id }`
200 OKjson
{
  "data": [
    {
      "id": "9c4e2a1b-7f3d-4e5a-b6c8-1d2f3a4b5c6d",
      "invoice_id": "2a7f8b1c-5e3d-49a6-b8c2-7d1f5e9c3a48",
      "amount_minor": 5000,
      "currency": "AED",
      "status": "succeeded",
      "notes": "wire-2026-05-19",
      "paid_at": "2026-05-19T14:32:00.000Z"
    }
  ]
}

Record invoice payment

Record a payment against an `open` or `partially_paid` invoice. Multiple payments are allowed until the invoice total is reached. Sets `partially_paid` when the sum of succeeded payments is below `total_minor`, and `paid` when fully settled.

POST/v1/admin/invoices/:id/payments

Path parameters

NameTypeRequiredDescription
idstringRequiredThe invoice's UUID.

Request body

NameTypeRequiredDescription
amountMinornumberRequiredPayment amount in AED minor units (fils). Must be positive and cannot exceed the remaining balance.
currencystringOptionalISO 4217 code. Defaults to invoice currency (`AED`).
paymentMethodIdstringOptionalOptional saved payment method UUID. A snapshot is stored on the payment row.
providerPaymentIdstringOptionalExternal reference (wire ref, Stripe charge id, etc.).
notesstringOptionalOperator notes. Maximum 500 chars.
Example requestbash
curl -X POST https://api.cortex.cognit-dx.com/v1/admin/invoices/2a7f8b1c-5e3d-49a6-b8c2-7d1f5e9c3a48/payments \
  -H "Authorization: Bearer $CORTEX_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"amount_minor": 5000, "notes": "wire-2026-05-19"}'

Response codes

StatusMeaningBody
201Payment recorded.`{ payment, invoice }` with updated `amount_paid_minor` / `amount_due_minor`.
404Invoice or payment method not found.`{ error, code: 'not_found' | 'payment_method_not_found' }`
409Invoice is not payable or overpayment.`{ error, code: 'invoice_not_payable' | 'overpayment' }`
403Admin access required.`{ error, code, request_id }`
201 Createdjson
{
  "payment": {
    "id": "9c4e2a1b-7f3d-4e5a-b6c8-1d2f3a4b5c6d",
    "invoice_id": "2a7f8b1c-5e3d-49a6-b8c2-7d1f5e9c3a48",
    "amount_minor": 5000,
    "currency": "AED",
    "status": "succeeded",
    "notes": "wire-2026-05-19",
    "paid_at": "2026-05-19T14:32:00.000Z"
  },
  "invoice": {
    "id": "2a7f8b1c-5e3d-49a6-b8c2-7d1f5e9c3a48",
    "organization_id": "f1ba15e0-ebe8-4187-afe6-03ccb25b8815",
    "subscription_id": "4b8a2f17-9d3e-4c5a-bc18-6e2f9d3a7b1c",
    "billing_profile_id": "8e5c1f93-6a2d-4b78-9f1c-3a5e7b2c8f14",
    "status": "partially_paid",
    "total_usd_minor": 12500,
    "total_minor": 0,
    "currency": "AED",
    "fx_rate": null,
    "due_date": "2026-06-15T00:00:00.000Z",
    "issued_at": "2026-05-18T11:00:00.000Z",
    "paid_at": null,
    "billing_snapshot": {},
    "subscription_snapshot": {},
    "amount_paid_minor": 5000,
    "amount_due_minor": 7500,
    "payments": [],
    "line_items": [
      {
        "description": "Pro plan – May 2026",
        "amount_minor": 10000,
        "currency": "USD"
      },
      {
        "description": "Overage – 2,500 additional messages",
        "amount_minor": 2500,
        "currency": "USD"
      }
    ],
    "notes": null,
    "created_at": "2026-05-18T10:24:31.000Z",
    "updated_at": "2026-05-18T10:24:31.000Z"
  }
}

Void invoice

Cancel an invoice without payment. Allowed in `draft`, `open`, and `partially_paid` states. A fully `paid` invoice cannot be voided.

POST/v1/admin/invoices/:id/void

Path parameters

NameTypeRequiredDescription
idstringRequiredThe invoice's UUID.
Example requestbash
curl -X POST https://api.cortex.cognit-dx.com/v1/admin/invoices/2a7f8b1c-5e3d-49a6-b8c2-7d1f5e9c3a48/void \
  -H "Authorization: Bearer $CORTEX_TOKEN"

Response codes

StatusMeaningBody
404Invoice not found.`{ error, code: 'not_found' }`
409Invoice is paid or already void.`{ error, code: 'invoice_not_voidable' }`
403Admin access required — caller lacks the system admin role or the credential cannot reach admin routes.`{ error, code, request_id }`
200 OKjson
{
  "id": "2a7f8b1c-5e3d-49a6-b8c2-7d1f5e9c3a48",
  "organization_id": "f1ba15e0-ebe8-4187-afe6-03ccb25b8815",
  "subscription_id": "4b8a2f17-9d3e-4c5a-bc18-6e2f9d3a7b1c",
  "billing_profile_id": "8e5c1f93-6a2d-4b78-9f1c-3a5e7b2c8f14",
  "status": "void",
  "total_usd_minor": 12500,
  "total_minor": 0,
  "currency": "AED",
  "fx_rate": null,
  "due_date": "2026-06-15T00:00:00.000Z",
  "issued_at": null,
  "paid_at": null,
  "billing_snapshot": {},
  "subscription_snapshot": {},
  "amount_paid_minor": 0,
  "amount_due_minor": 0,
  "payments": [],
  "line_items": [
    {
      "description": "Pro plan – May 2026",
      "amount_minor": 10000,
      "currency": "USD"
    },
    {
      "description": "Overage – 2,500 additional messages",
      "amount_minor": 2500,
      "currency": "USD"
    }
  ],
  "notes": null,
  "created_at": "2026-05-18T10:24:31.000Z",
  "updated_at": "2026-05-18T10:24:31.000Z"
}

Wallet

Get wallet

Return the organization's prepaid wallet: `balance_minor` (integer minor units; may be negative from bounded concurrent overspend), `currency`, `enforcement_enabled`, and `low_balance_threshold_minor`. Returns 404 (`no_wallet`) when the org has no wallet — an org with no wallet is treated as unlimited (no debiting, no enforcement).

GET/v1/admin/organizations/:id/wallet

Path parameters

NameTypeRequiredDescription
idstringRequiredThe organization's UUID.
Example requestbash
curl https://api.cortex.cognit-dx.com/v1/admin/organizations/f1ba15e0-ebe8-4187-afe6-03ccb25b8815/wallet \
  -H "Authorization: Bearer $CORTEX_TOKEN"

Response codes

StatusMeaningBody
404Organization not found, or organization has no wallet.`{ error, code: 'not_found' | 'no_wallet' }`
403Admin access required.`{ error, code, request_id }`

Create wallet

Create the organization's prepaid wallet (1:1 — calling twice returns 409). Starts at a zero balance. Optional body: `currency` (default `usd`), `enforcementEnabled` (default false), `lowBalanceThresholdMinor`. Top up with the top-up endpoint.

POST/v1/admin/organizations/:id/wallet

Path parameters

NameTypeRequiredDescription
idstringRequiredThe organization's UUID.

Request body

NameTypeRequiredDescription
currencystringOptionalISO currency code; default `usd`.
enforcementEnabledbooleanOptionalWhether a future-phase enforcement blocks at a zero balance. Default false.
lowBalanceThresholdMinornumberOptionalOptional alert threshold in minor currency units.
Example requestbash
curl https://api.cortex.cognit-dx.com/v1/admin/organizations/:id/wallet \
  -H "Authorization: Bearer $CORTEX_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
  "currency": "<currency>",
  "enforcementEnabled": false,
  "lowBalanceThresholdMinor": 0
}'

Response codes

StatusMeaningBody
409Organization already has a wallet.`{ error, code: 'wallet_exists' }`
403Admin access required.`{ error, code, request_id }`

Update wallet (enforcement / threshold)

Update wallet settings on an existing wallet: `enforcementEnabled` (the master switch — when true, runs are refused at start and stopped mid-run once the balance hits zero or a spend limit is reached; when false the org is unlimited) and `lowBalanceThresholdMinor` (alert threshold; does not block). Does not change the balance — use top-up/adjust for that. Returns the updated wallet, or 404 if none exists.

PATCH/v1/admin/organizations/:id/wallet

Path parameters

NameTypeRequiredDescription
idstringRequiredThe organization's UUID.

Request body

NameTypeRequiredDescription
enforcementEnabledbooleanOptionalTurn enforcement on/off.
lowBalanceThresholdMinornumberOptionalAlert threshold in minor units (nullable).
Example requestbash
curl https://api.cortex.cognit-dx.com/v1/admin/organizations/:id/wallet \
  -H "Authorization: Bearer $CORTEX_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
  "enforcementEnabled": false,
  "lowBalanceThresholdMinor": 0
}'

Response codes

StatusMeaningBody
404Organization not found, or organization has no wallet.`{ error, code: 'not_found' | 'no_wallet' }`
400Invalid body.`{ error, code: 'validation_error' }`
403Admin access required.`{ error, code, request_id }`

List wallet transactions

List the wallet's ledger entries, newest first. Each entry has a signed `amount_minor` (negative = debit, positive = credit), the `balance_after_minor`, a `type` (`topup` | `debit` | `adjustment` | `refund`), and for debits the `usage_event_id` it corresponds to. Query: `limit` (default 100, max 500).

GET/v1/admin/organizations/:id/wallet/transactions

Path parameters

NameTypeRequiredDescription
idstringRequiredThe organization's UUID.

Query parameters

NameTypeRequiredDescription
limitnumberOptionalMax rows to return (default 100, max 500).
Example requestbash
curl https://api.cortex.cognit-dx.com/v1/admin/organizations/:id/wallet/transactions \
  -H "Authorization: Bearer $CORTEX_TOKEN"

Response codes

StatusMeaningBody
404Organization not found.`{ error, code: 'not_found' }`
403Admin access required.`{ error, code, request_id }`

Top up wallet

Add credit to the wallet. Body: `amountMinor` (positive integer) and optional `description`. Appends a `topup` ledger entry stamped with the operator's user id and returns the updated wallet. Returns 404 (`no_wallet`) if the wallet does not exist yet.

POST/v1/admin/organizations/:id/wallet/topup

Path parameters

NameTypeRequiredDescription
idstringRequiredThe organization's UUID.

Request body

NameTypeRequiredDescription
amountMinornumberOptionalPositive integer in minor currency units to credit.
descriptionstringOptionalOptional note for the ledger entry.
Example requestbash
curl https://api.cortex.cognit-dx.com/v1/admin/organizations/:id/wallet/topup \
  -H "Authorization: Bearer $CORTEX_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
  "amountMinor": 0,
  "description": "<description>"
}'

Response codes

StatusMeaningBody
404Organization not found, or organization has no wallet.`{ error, code: 'not_found' | 'no_wallet' }`
400amountMinor must be a positive integer.`{ error, code: 'validation_error' }`
403Admin access required.`{ error, code, request_id }`

Adjust wallet

Apply a manual signed correction to the balance. Body: `amountMinor` (non-zero integer; negative lowers the balance) and a required `description`. Appends an `adjustment` ledger entry and returns the updated wallet.

POST/v1/admin/organizations/:id/wallet/adjust

Path parameters

NameTypeRequiredDescription
idstringRequiredThe organization's UUID.

Request body

NameTypeRequiredDescription
amountMinornumberOptionalNon-zero integer in minor units; negative lowers the balance.
descriptionstringOptionalRequired note explaining the adjustment.
Example requestbash
curl https://api.cortex.cognit-dx.com/v1/admin/organizations/:id/wallet/adjust \
  -H "Authorization: Bearer $CORTEX_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
  "amountMinor": 0,
  "description": "<description>"
}'

Response codes

StatusMeaningBody
404Organization not found, or organization has no wallet.`{ error, code: 'not_found' | 'no_wallet' }`
400amountMinor must be non-zero; description required.`{ error, code: 'validation_error' }`
403Admin access required.`{ error, code, request_id }`

Spend limits

List spend limits

List the organization's monthly spend limits, enriched with the current calendar-month spend from the rollup counters. Each has a `scope` (`organization` | `user` | `department`), a `scope_id` (null for organization scope), an `amount_minor` cap in USD, a `period` (`calendar_month`), `is_enabled`, plus `spent_minor`, `remaining_minor`, and `percent_used` for the period bounded by the top-level `period_start`/`period_end`. Limits are sub-caps within the prepaid wallet and are enforced only when the org has an enforcement-enabled wallet.

GET/v1/admin/organizations/:id/spend-limits

Path parameters

NameTypeRequiredDescription
idstringRequiredThe organization's UUID.
Example requestbash
curl https://api.cortex.cognit-dx.com/v1/admin/organizations/:id/spend-limits \
  -H "Authorization: Bearer $CORTEX_TOKEN"

Response codes

StatusMeaningBody
404Organization not found.`{ error, code: 'not_found' }`
403Admin access required.`{ error, code, request_id }`
200 OKjson
{
  "period_start": "2026-08-01",
  "period_end": "2026-08-31",
  "limits": [
    {
      "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
      "scope": "organization",
      "scope_id": null,
      "amount_minor": 50000,
      "period": "calendar_month",
      "is_enabled": true,
      "spent_minor": 18420,
      "remaining_minor": 31580,
      "percent_used": 36.84
    }
  ]
}

Create or update a spend limit

Upsert the single limit for a (scope, scope_id) within the org — there is one limit per scope target per period. `scopeId` is required for `user`/`department` scope and must be omitted for `organization` scope. Returns the limit.

PUT/v1/admin/organizations/:id/spend-limits

Path parameters

NameTypeRequiredDescription
idstringRequiredThe organization's UUID.

Request body

NameTypeRequiredDescription
scopestringOptional`organization` | `user` | `department`.
scopeIdstringOptionalThe user/department UUID (required for user/department scope; omit for organization).
amountCentsnumberOptionalPositive integer monthly cap in cents.
isEnabledbooleanOptionalWhether the limit is active. Default true.
Example requestbash
curl https://api.cortex.cognit-dx.com/v1/admin/organizations/:id/spend-limits \
  -H "Authorization: Bearer $CORTEX_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
  "scope": "<scope>",
  "scopeId": "<scopeId>",
  "amountCents": 0,
  "isEnabled": false
}'

Response codes

StatusMeaningBody
400Invalid scope/scopeId combination or non-positive amount.`{ error, code: 'validation_error' }`
404Organization not found.`{ error, code: 'not_found' }`
403Admin access required.`{ error, code, request_id }`

Delete a spend limit

Delete a spend limit by id. Returns 204 on success, 404 if the limit does not belong to the organization.

DELETE/v1/admin/organizations/:id/spend-limits/:limitId

Path parameters

NameTypeRequiredDescription
idstringRequiredThe organization's UUID.
limitIdstringRequiredThe spend limit's UUID.
Example requestbash
curl https://api.cortex.cognit-dx.com/v1/admin/organizations/:id/spend-limits/:limitId \
  -H "Authorization: Bearer $CORTEX_TOKEN"

Response codes

StatusMeaningBody
204Deleted.(empty)
404Organization or spend limit not found.`{ error, code: 'not_found' }`
403Admin access required.`{ error, code, request_id }`

Feature flags

List feature flags

List feature flags for the organization. Each entry has a `slug`, `is_enabled`, and `is_org_override` (true when the organization has its own row for that slug, false when the value shown is the platform default). Currently the only known slug is `generator_native_presentations`, which routes presentation requests to the native generator instead of the sandbox.

GET/v1/admin/organizations/:id/feature-flags

Path parameters

NameTypeRequiredDescription
idstringRequiredThe organization's UUID.
Example requestbash
curl https://api.cortex.cognit-dx.com/v1/admin/organizations/:id/feature-flags \
  -H "Authorization: Bearer $CORTEX_TOKEN"

Response codes

StatusMeaningBody
404Organization not found.`{ error, code: 'not_found' }`
403Admin access required.`{ error, code, request_id }`
200 OKjson
{
  "flags": [
    {
      "slug": "generator_native_presentations",
      "is_enabled": false,
      "is_org_override": false
    }
  ]
}

Set a feature flag

Enable or disable a feature flag for one organization. Upserts the organization's own row for the slug, so this never affects other organizations or the platform default. Returns the updated flag.

PUT/v1/admin/organizations/:id/feature-flags/:slug

Path parameters

NameTypeRequiredDescription
idstringRequiredThe organization's UUID.
slugstringRequiredThe flag's slug, e.g. `generator_native_presentations`. Unknown slugs are rejected.

Request body

NameTypeRequiredDescription
isEnabledbooleanRequiredWhether the flag should be enabled for this organization.
Example requestbash
curl https://api.cortex.cognit-dx.com/v1/admin/organizations/:id/feature-flags/:slug \
  -H "Authorization: Bearer $CORTEX_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
  "isEnabled": false
}'

Response codes

StatusMeaningBody
400Unknown flag slug, or isEnabled is not a boolean.`{ error, code: 'unknown_feature_flag' | 'validation_error' }`
404Organization not found.`{ error, code: 'not_found' }`
403Admin access required.`{ error, code, request_id }`
200 OKjson
{
  "slug": "generator_native_presentations",
  "is_enabled": true,
  "is_org_override": true
}