> ## Documentation Index
> Fetch the complete documentation index at: https://docs.purplelabelmd.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Choose a custom domain for a kind (client self-serve)

> Registers a custom domain for one kind using your own API key: the platform registers it with the auth provider and returns the CNAME record to add to your DNS (state cname_rendered). Idempotent — re-sending the SAME hostname returns the current row unchanged; sending a DIFFERENT hostname for a kind you already registered is a conflict (remove it first). A hostname already claimed by another brand is a conflict (hostnames are globally unique). The brand is the one your key owns (resolved from X-Brand-Id); the brand_id in the path must match it (any mismatch answers 404, never 403).



## OpenAPI

````yaml /openapi/public-openapi.json put /v1/account/brands/{brand_id}/domains/{kind}
openapi: 3.1.0
info:
  title: Purple API
  version: 0.0.0
servers:
  - url: https://api.dev.purplelabelmd.com
security: []
tags:
  - description: Sign a patient in and out and manage the browser session.
    name: Sessions & authentication
  - description: Run the server-driven intake questionnaire question by question.
    name: Intake
  - description: Address autocomplete for the intake flow.
    name: Addresses
  - description: Read the public status of a patient's enrollment.
    name: Enrollment
  - description: Track a client's onboarding progress.
    name: Onboarding
  - description: Read back the platform configuration stored for your brand.
    name: Brand configuration
  - description: Enable offerings for your brand and set their display copy.
    name: Offering configuration
  - description: Start and track payments for an order.
    name: Payments
  - description: Subscribe to platform events and manage delivery endpoints.
    name: Webhooks
paths:
  /v1/account/brands/{brand_id}/domains/{kind}:
    parameters:
      - $ref: '#/components/parameters/AccountBrandIdPath'
      - $ref: '#/components/parameters/DomainKindPath'
    put:
      tags:
        - Brand configuration
      summary: Choose a custom domain for a kind (client self-serve)
      description: >-
        Registers a custom domain for one kind using your own API key: the
        platform registers it with the auth provider and returns the CNAME
        record to add to your DNS (state cname_rendered). Idempotent —
        re-sending the SAME hostname returns the current row unchanged; sending
        a DIFFERENT hostname for a kind you already registered is a conflict
        (remove it first). A hostname already claimed by another brand is a
        conflict (hostnames are globally unique). The brand is the one your key
        owns (resolved from X-Brand-Id); the brand_id in the path must match it
        (any mismatch answers 404, never 403).
      operationId: setAccountBrandDomain
      parameters:
        - $ref: '#/components/parameters/BrandIdHeader'
        - $ref: '#/components/parameters/IdempotencyKeyHeader'
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RegisterDomainRequest'
        required: true
      responses:
        '200':
          content:
            application/json:
              schema:
                properties:
                  data:
                    $ref: '#/components/schemas/AccountDomainView'
                required:
                  - data
                type: object
          description: the registered row + its CNAME paste-row (state cname_rendered)
        '400':
          $ref: '#/components/responses/EdgeBadRequest'
        '401':
          $ref: '#/components/responses/EdgeUnauthorized'
        '403':
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/EdgeProblem'
          description: >-
            the body carried a field that exists on this operation but is not
            yours to set — host ownership is declared by an operator, not by the
            brand that serves on the host. Nothing was written. Distinct from
            the 400 an unrecognized field gets: the field is real, the standing
            is not.
        '404':
          $ref: '#/components/responses/EdgeNotFound'
        '409':
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/EdgeProblem'
          description: >-
            a binding conflict: a different hostname for this kind, a host
            claimed elsewhere (value-free — never says by whom), or the same
            hostname already held under a DIFFERENT owner — a binding is never
            silently re-owned, so remove it first. One status, one problem
            `type`; only the human-readable detail distinguishes the cause.
        '503':
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/EdgeProblem'
          description: >-
            the durable domain registry is unavailable (nothing activated;
            retryable)
      security:
        - apiKey: []
components:
  parameters:
    AccountBrandIdPath:
      description: >-
        the brand whose custom domains to view or manage (must match
        X-Brand-Id).
      in: path
      name: brand_id
      required: true
      schema:
        pattern: ^brd_[A-Za-z0-9][A-Za-z0-9_-]*$
        type: string
    DomainKindPath:
      description: >-
        the custom-domain kind — login (the auth domain) or member (the
        member-portal domain).
      in: path
      name: kind
      required: true
      schema:
        enum:
          - login
          - member
        type: string
    BrandIdHeader:
      description: >-
        Opaque brand id (brd_...). Validated to belong to the resolved client
        (§2).
      in: header
      name: X-Brand-Id
      required: true
      schema:
        pattern: ^brd_[A-Za-z0-9][A-Za-z0-9_-]*$
        type: string
    IdempotencyKeyHeader:
      description: >-
        Accepted on mutations; carried in trusted context, enforced in a later
        WI.
      in: header
      name: Idempotency-Key
      required: false
      schema:
        type: string
  schemas:
    RegisterDomainRequest:
      additionalProperties: false
      description: >-
        Choose a custom domain for a kind. The only field is the hostname you
        want to use; the brand is the one your API key owns (resolved from
        X-Brand-Id, never taken from the body), so a request-supplied brand or
        provider id is structurally inert. Any other field is refused with a 400
        that names it — a field this operation does not accept is never silently
        ignored.
      properties:
        hostname:
          description: the custom hostname to register (e.g. login.example.com).
          minLength: 1
          type: string
      required:
        - hostname
      type: object
    AccountDomainView:
      additionalProperties: false
      description: >-
        One custom-domain row as the registry holds it: the brand, the kind
        (login | member), the hostname, its provisioning state, who owns and who
        bound the host, timestamps, and the CNAME paste-row to add to your DNS.
        The row never carries a secret nor the provider's internal domain id. An
        absent row (a kind you have not registered) is the shared-domain
        default.
      properties:
        bound_by:
          description: >-
            which actor bound this host, and on which plane —
            `operator:<subject>` or `client:<client id>`. Derived from the
            authenticated caller at the moment of the binding; it can never be
            supplied in a request body. Absent on a row bound before this field
            was recorded (the binder of those rows is genuinely unknown, and is
            not guessed). The time of the binding is the row's created_at, since
            a live binding is never re-owned in place.
          type: string
        brand_id:
          pattern: ^brd_[A-Za-z0-9][A-Za-z0-9_-]*$
          type: string
        cname:
          allOf:
            - $ref: '#/components/schemas/DomainCnameRecord'
          description: >-
            the CNAME record to add to your DNS (present from cname_rendered
            onward).
        created_at:
          format: date-time
          type: string
        hostname:
          description: your custom hostname (e.g. login.example.com).
          type: string
        kind:
          enum:
            - login
            - member
          type: string
        owner:
          description: >-
            who owns this host binding. `customer` is a domain you brought and
            verified; `platform-brand` is the platform-owned member host every
            brand receives by default; `platform-walk` is a standing platform
            program host. Set on the operator plane only — a brand cannot assign
            its own ownership — and always present on a row.
          enum:
            - platform-walk
            - platform-brand
            - customer
          type: string
        state:
          description: >-
            the provisioning state. login: requested → cname_rendered →
            verifying → verified → cert_issued → active. member: requested →
            cname_rendered → verifying → verified → cert_requested → cert_issued
            → active (the serving tail: verify stops at cert_requested;
            cert_issued and active are operator-advanced — active means the
            hostname serves this brand at the edge).
          enum:
            - requested
            - cname_rendered
            - verifying
            - verified
            - cert_issued
            - active
            - cert_requested
          type: string
        updated_at:
          format: date-time
          type: string
      required:
        - brand_id
        - kind
        - hostname
        - state
        - owner
        - created_at
        - updated_at
      type: object
    EdgeProblem:
      description: RFC 7807 problem+json error body.
      properties:
        detail:
          type: string
        status:
          type: integer
        title:
          type: string
        type:
          format: uri-reference
          type: string
      required:
        - type
        - title
        - status
      type: object
    DomainCnameRecord:
      additionalProperties: false
      description: >-
        The CNAME record you add to your DNS to point your custom hostname at
        the platform's edge. A business hostname pair — never a secret. Present
        once the domain has been registered with the provider (state
        cname_rendered onward).
      properties:
        name:
          description: the record NAME to set (your custom hostname).
          type: string
        type:
          const: CNAME
          type: string
        value:
          description: the record VALUE — the edge target the CNAME must point at.
          type: string
      required:
        - type
        - name
        - value
      type: object
  responses:
    EdgeBadRequest:
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/EdgeProblem'
      description: malformed or absent X-Brand-Id
    EdgeUnauthorized:
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/EdgeProblem'
      description: >-
        missing, unknown, or revoked API key - one opaque body (no
        enumeration/timing oracle, §1)
    EdgeNotFound:
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/EdgeProblem'
      description: >-
        brand not found for this tenant - identical for unknown and foreign
        brands (404, never 403; no cross-tenant existence leak, §2)
  securitySchemes:
    apiKey:
      description: 'Per-client API key (M2M). Presented as `Authorization: Bearer <key>`.'
      scheme: bearer
      type: http

````