Create a brand
The brands.create operation: POST /api/v1/brands in the REST API and the brands_create tool in the MCP server.
/api/v1/brandsCreates a brand (a storefront or product line) under one of the organization's legal entities. The brand
starts in test mode only (status: test_only); it goes live through the go-live checklist.
slugis unique across Ownrecord: it appears in checkout URLs and API keys.invoiceSeries.invoice(andcreditNote, default{invoice}-CN) must be unused by every other brand of the same entity: each brand numbers its invoices in its own series within the entity.statementDescriptordefaults to the name in capitals.
The plan limits the number of brands (brands.max). Needs brands:write: organization owners and admins,
or an org or entity API key with that scope (a test-mode key creates the brand like any other key: brands
aren't mode-separated).
At a glance
- REST:
POST /api/v1/brands - MCP tool:
brands_create - Operation ID:
brands.create - Scope: Organization, or one of its legal entities or brands
- Permission:
brands:write - Risk tier:
write - Dry run: Supported (send
Ownrecord-Dry-Run: true) - Idempotency:
Idempotency-Keyrequired - Modes: Test and live
- Authentication: API key with the
brands:writescope - Since: M1
Request
Body
A JSON object (Content-Type: application/json):
| Field | Type | Required | Description |
|---|---|---|---|
entityId | ent_… ID | Yes | |
name | string | Yes | |
slug | string | Yes | |
invoiceSeries | object | Yes | |
invoiceSeries.invoice | string | Yes | |
invoiceSeries.creditNote | string | No | |
statementDescriptor | string | No | |
branding | object | No | |
branding.logoUrl | string | No | |
branding.iconUrl | string | No | |
branding.primaryColor | string | No | |
branding.accentColor | string | No | |
branding.supportEmail | string | No | |
branding.supportUrl | string | No | |
branding.termsUrl | string | No | |
branding.privacyUrl | string | No |
Response
201 Created with a JSON object:
| Field | Type | Description |
|---|---|---|
id | brd_… ID | |
object | "brand" | |
orgId | org_… ID | |
entityId | ent_… ID | |
slug | string | |
name | string | |
status | one of draft, test_only, live, suspended | |
isPlatformBrand | boolean | |
branding | object | |
branding.logoUrl | string or null | |
branding.iconUrl | string or null | |
branding.primaryColor | string or null | |
branding.accentColor | string or null | |
branding.supportEmail | string or null | |
branding.supportUrl | string or null | |
branding.termsUrl | string or null | |
branding.privacyUrl | string or null | |
statementDescriptor | string | |
statementDescriptorSuffix | string or null | |
invoiceSeries | object | |
invoiceSeries.invoice | string | |
invoiceSeries.creditNote | string | |
billingSettings | object | |
billingSettings.dunning | object | |
billingSettings.dunning.retryOffsetsDays | array of integer | |
billingSettings.dunning.finalAction | one of unpaid, canceled | |
billingSettings.entitlementGraceDays | integer | |
billingSettings.sendInvoice | object | |
billingSettings.sendInvoice.graceDays | integer | |
billingSettings.cardExpiryReminders | boolean | |
billingSettings.pausedEntitlements | boolean | |
billingSettings.paymentBehavior | one of allow_incomplete, pending_if_incomplete | |
policies | object | |
policies.breakageRecognition | one of at_expiry, manual | at_expiry (the default) or manual (held as expired credits until recognised). |
policies.supportRefundLimits | array of object | The support role's refund limit per currency (M8); a currency not listed has the default of 100 of it. |
policies.supportRefundLimits[].amount | string | Integer micros as a string. |
policies.supportRefundLimits[].currency | string | |
policies.disputeWalletFreeze | boolean | Freeze the customer's wallets when a dispute opens (default true; §19.2, M8). |
policies.taxIdUnavailable | one of charge_as_b2c, block | What a sale does when a tax ID couldn't be checked (§9.1 step 1; M13): charge_as_b2c (the default) or block. |
portalSettings | object | |
portalSettings.allowPlanChanges | boolean | Customers may switch plans along planChangeMatrix (no row: no switch). |
portalSettings.planChangeMatrix | array of object | One row per price: a subscription item on fromPriceId may switch to any of toPriceIds (same mode and currency). |
portalSettings.planChangeMatrix[].fromPriceId | price_… ID | |
portalSettings.planChangeMatrix[].toPriceIds | array of price_… ID | |
portalSettings.allowCancel | boolean | Customers may cancel their subscriptions. |
portalSettings.cancelBehavior | one of period_end, immediate | A portal cancellation ends the subscription at the period end (default) or at once. |
portalSettings.collectCancelReason | boolean | The portal asks why the customer cancels. |
portalSettings.allowQuantityChanges | boolean | Customers may change the quantity (seats) of licensed items. |
checkoutSettings | object | |
checkoutSettings.locales | array of string | The brand's languages, its default first. A default, not a restriction: a customer's own language comes first. |
checkoutSettings.collectPhone | boolean | |
checkoutSettings.requireBusinessDetails | one of auto, always, never | |
createdAt | date-time | |
updatedAt | date-time |
Examples
A brand with its own invoice series
POST /api/v1/brands
Authorization: Bearer or_test_…
Idempotency-Key: 3f1c9a52-7d0e-4b8f-9a61-0c2d5e8b7f14
Content-Type: application/json
{
"entityId": "ent_01m4tx5nj8emwbyf7y9vcf47x2",
"name": "Acme App",
"slug": "acme-app",
"invoiceSeries": {
"invoice": "ACM"
}
}
Response (201):
{
"id": "brd_01m4tx5nj8emwbyf7y9vcf47x4",
"object": "brand",
"orgId": "org_01m4tx5nj7ekd958x53w4bntcg",
"entityId": "ent_01m4tx5nj8emwbyf7y9vcf47x2",
"slug": "acme-app",
"name": "Acme App",
"status": "test_only",
"isPlatformBrand": false,
"branding": {
"logoUrl": null,
"iconUrl": null,
"primaryColor": null,
"accentColor": null,
"supportEmail": null,
"supportUrl": null,
"termsUrl": null,
"privacyUrl": null
},
"statementDescriptor": "ACME APP",
"statementDescriptorSuffix": null,
"invoiceSeries": {
"invoice": "ACM",
"creditNote": "ACM-CN"
},
"billingSettings": {
"dunning": {
"retryOffsetsDays": [1, 3, 5, 7],
"finalAction": "unpaid"
},
"entitlementGraceDays": 7,
"sendInvoice": {
"graceDays": 14
},
"cardExpiryReminders": true,
"pausedEntitlements": false,
"paymentBehavior": "allow_incomplete"
},
"policies": {
"breakageRecognition": "at_expiry",
"supportRefundLimits": [],
"disputeWalletFreeze": true,
"taxIdUnavailable": "charge_as_b2c"
},
"portalSettings": {
"allowPlanChanges": true,
"planChangeMatrix": [],
"allowCancel": true,
"cancelBehavior": "period_end",
"collectCancelReason": true,
"allowQuantityChanges": false
},
"checkoutSettings": {
"locales": ["en", "hr"],
"collectPhone": false,
"requireBusinessDetails": "auto"
},
"createdAt": "2026-10-13T12:00:00.000Z",
"updatedAt": "2026-10-13T12:00:00.000Z"
}
With support and legal links
POST /api/v1/brands
Authorization: Bearer or_test_…
Idempotency-Key: 3f1c9a52-7d0e-4b8f-9a61-0c2d5e8b7f14
Content-Type: application/json
{
"entityId": "ent_01m4tx5nj8emwbyf7y9vcf47x2",
"name": "Acme App",
"slug": "acme-app",
"invoiceSeries": {
"invoice": "ACM",
"creditNote": "ACM-CN"
},
"statementDescriptor": "ACME APP",
"branding": {
"supportEmail": "help@acme.example",
"termsUrl": "https://acme.example/terms",
"privacyUrl": "https://acme.example/privacy"
}
}
Response (201):
{
"id": "brd_01m4tx5nj8emwbyf7y9vcf47x4",
"object": "brand",
"orgId": "org_01m4tx5nj7ekd958x53w4bntcg",
"entityId": "ent_01m4tx5nj8emwbyf7y9vcf47x2",
"slug": "acme-app",
"name": "Acme App",
"status": "test_only",
"isPlatformBrand": false,
"branding": {
"logoUrl": null,
"iconUrl": null,
"primaryColor": null,
"accentColor": null,
"supportEmail": "help@acme.example",
"supportUrl": null,
"termsUrl": "https://acme.example/terms",
"privacyUrl": "https://acme.example/privacy"
},
"statementDescriptor": "ACME APP",
"statementDescriptorSuffix": null,
"invoiceSeries": {
"invoice": "ACM",
"creditNote": "ACM-CN"
},
"billingSettings": {
"dunning": {
"retryOffsetsDays": [1, 3, 5, 7],
"finalAction": "unpaid"
},
"entitlementGraceDays": 7,
"sendInvoice": {
"graceDays": 14
},
"cardExpiryReminders": true,
"pausedEntitlements": false,
"paymentBehavior": "allow_incomplete"
},
"policies": {
"breakageRecognition": "at_expiry",
"supportRefundLimits": [],
"disputeWalletFreeze": true,
"taxIdUnavailable": "charge_as_b2c"
},
"portalSettings": {
"allowPlanChanges": true,
"planChangeMatrix": [],
"allowCancel": true,
"cancelBehavior": "period_end",
"collectCancelReason": true,
"allowQuantityChanges": false
},
"checkoutSettings": {
"locales": ["en", "hr"],
"collectPhone": false,
"requireBusinessDetails": "auto"
},
"createdAt": "2026-10-13T12:00:00.000Z",
"updatedAt": "2026-10-13T12:00:00.000Z"
}
Errors
| Code | HTTP status | When | What to do |
|---|---|---|---|
validation_failed | 400 | A field is missing or malformed (details.issues). | Fix the fields listed in details.issues and send the request again. |
slug_invalid | 400 | The slug has the wrong format or is a reserved word. | Choose a slug such as acme or acme-app. Reserved words such as api, console and docs can't be used. |
slug_taken | 409 | Another brand already uses this slug (brand slugs are unique on Ownrecord). | Choose another slug. Organization and brand slugs are unique across Ownrecord; entity slugs are unique within an organization. |
invoice_series_taken | 409 | Another brand of the entity already uses the invoice or credit-note series. | Choose a different series code. Each brand numbers its invoices and credit notes in its own series within the entity. |
resource_not_found | 404 | The entity doesn't exist or is outside the request's scope. | Check the ID and that your credentials are scoped to the right organization, entity, brand and mode. |
scope_mismatch | 403 | The request is scoped to one brand; brands are created at an entity. | Use credentials scoped to that organization, entity or brand, or leave the scope fields out of the request. |
entitlement_required | 403 | The plan's brands.max is reached. | Upgrade the organization's plan, or remove something to get back under the limit. |
permission_denied | 403 | You can't create brands here. | Ask an organization owner or admin for a role or scope that allows this action. |
It can also return these common errors: api_version_unsupported, invalid_json, unsupported_media_type, authentication_required, api_key_invalid, idempotency_key_required, idempotency_key_reused, idempotency_in_progress, approval_required, approval_rejected, approval_expired, stale_approval, approval_policy_denied, rate_limited, internal_error.