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

# Create a customer

> Creates a customer to bill. Only `name` is required. Calls are not deduplicated: the same name twice makes two customers.

**Scope:** `clients:write`

**Idempotency:** `Idempotency-Key` optional. With one, a retry with the same key and body replays the first response (`Idempotent-Replayed: true`); the same key with a different body is a `422` `idempotency_mismatch`.



## OpenAPI

````yaml /openapi.json post /customers
openapi: 3.1.0
info:
  title: Invoice-AI API
  version: 1.0.0
  description: >-
    Stripe-shaped invoicing over HTTP: customers, products, prices, invoices and
    invoice items, with `cus_…`, `prod_…`, `price_…`, `in_…` and `ii_…` ids.


    - **Auth:** `Authorization: Bearer inv_live_…`, with per-key scopes.

    - **Envelope:** JSON bodies; responses wrap the resource in `{ "data": … }`;
    lists add `next_cursor`.

    - **Money:** integer minor units of the currency everywhere (`250000` is
    $2,500.00; ¥5,000 is `5000`).

    - **Errors:** RFC 9457 `application/problem+json` — branch on `code`.

    - **Idempotency:** operations that spend an invoice number, email or record
    payment require an `Idempotency-Key`.

    - **Rate limits:** 120 requests/minute per key. Sending email: 10/hour per
    key and 50/hour per account. See the `RateLimit-*` headers.
  contact:
    name: Invoice-AI support
    url: https://invoice.horizonpay.co
servers:
  - url: https://invoice.horizonpay.co/api/v1
    description: Production
security:
  - apiKey: []
tags:
  - name: Business
    x-displayName: Business
    description: >-
      Your business profile — the issuer printed on every invoice. Read-only
      over the API; edit it in Settings.
  - name: Customers
    x-displayName: Customers
    description: >-
      The people and companies you bill. Every invoice references a customer by
      `cus_…` id. Customers are archived rather than deleted, so issued invoices
      keep naming who they were billed to.
  - name: Products
    x-displayName: Products
    description: >-
      The things you sell. A product carries a name and description; amounts
      live on its prices. Archiving sets `active: false` and keeps history
      intact.
  - name: Prices
    x-displayName: Prices
    description: >-
      Ways to charge for a product: an amount in integer minor units and a
      currency, one-time or recurring. Bill a price on an invoice line with
      `price: "price_…"`.
  - name: Invoices
    x-displayName: Invoices
    description: >-
      Invoices move `draft` → `open` (finalize assigns a permanent, consecutive
      number) → `paid`, or `open` → `void`. Drafts are freely editable and
      deletable; finalized invoices are frozen. `overdue` is an open invoice
      past its due date, derived at read time. Operations that spend a number,
      send email or record payment require an `Idempotency-Key`.
  - name: Invoice items
    x-displayName: Invoice items
    description: >-
      Individual lines of an invoice. Add or remove lines on a draft one at a
      time; totals are recomputed server-side and the whole invoice is returned.
  - name: Webhook endpoints
    x-displayName: Webhook endpoints
    description: >-
      Register https endpoints to receive invoice events (`invoice.created`,
      `invoice.finalized`, `invoice.paid`, …). Deliveries are signed per
      [Standard Webhooks](https://www.standardwebhooks.com/) with `webhook-id`,
      `webhook-timestamp` and `webhook-signature` headers (HMAC-SHA256 over
      `{id}.{timestamp}.{body}`); failed deliveries are retried with backoff.
paths:
  /customers:
    post:
      tags:
        - Customers
      summary: Create a customer
      description: >-
        Creates a customer to bill. Only `name` is required. Calls are not
        deduplicated: the same name twice makes two customers.


        **Scope:** `clients:write`


        **Idempotency:** `Idempotency-Key` optional. With one, a retry with the
        same key and body replays the first response (`Idempotent-Replayed:
        true`); the same key with a different body is a `422`
        `idempotency_mismatch`.
      operationId: createCustomer
      parameters:
        - in: header
          name: Idempotency-Key
          schema:
            description: >-
              A unique key per distinct operation (a UUID works), reused only
              when retrying that same request. Up to 255 characters; stored for
              24 hours.
            examples:
              - 6f1c2d9e-8a4b-4c3f-9e7d-2b5a1c8f4e30
            type: string
            maxLength: 255
          description: >-
            A unique key per distinct operation (a UUID works), reused only when
            retrying that same request. Up to 255 characters; stored for 24
            hours.
      requestBody:
        required: true
        content:
          application/json:
            example:
              name: Acme Industries
              tax_id: US-EIN 12-3456789
              email: ap@acme.example
              address:
                line1: 4th Floor, Market Tower
                city: Austin
                state: TX
                postal_code: '73301'
                country: US
            schema:
              $ref: '#/components/schemas/CustomerCreate'
      responses:
        '201':
          description: The created customer.
          headers:
            X-Request-Id:
              $ref: '#/components/headers/RequestId'
            RateLimit-Limit:
              $ref: '#/components/headers/RateLimitLimit'
            RateLimit-Remaining:
              $ref: '#/components/headers/RateLimitRemaining'
            RateLimit-Reset:
              $ref: '#/components/headers/RateLimitReset'
            Idempotent-Replayed:
              $ref: '#/components/headers/IdempotentReplayed'
          content:
            application/json:
              example:
                data:
                  id: cus_Nf3kQ8pR2mX7vB1cT9wL4sZ6
                  object: customer
                  name: Acme Industries
                  email: ap@acme.example
                  phone: null
                  tax_id: US-EIN 12-3456789
                  address:
                    line1: 4th Floor, Market Tower
                    line2: null
                    city: Austin
                    state: TX
                    postal_code: '73301'
                    country: US
                  deleted: false
                  created: '2026-09-22T09:30:00.000Z'
              schema:
                type: object
                properties:
                  data:
                    $ref: '#/components/schemas/Customer'
                    description: The created customer.
                required:
                  - data
                additionalProperties: false
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '409':
          description: >-
            A request with the same Idempotency-Key is still running. Codes:
            `conflict`.
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/Problem'
              examples:
                conflict:
                  summary: >-
                    A request with this Idempotency-Key is still in flight.
                    Retry in a moment.
                  value:
                    type: https://invoice.horizonpay.co/problems/conflict
                    title: Conflict
                    status: 409
                    detail: >-
                      A request with this Idempotency-Key is still in flight.
                      Retry in a moment.
                    instance: req_7f3c9a1e2b4d4e8a9c6f2d1b0a3e5f71
                    code: conflict
        '422':
          description: >-
            The body failed validation, or the Idempotency-Key was reused with a
            different body. Codes: `validation`, `idempotency_mismatch`.
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/Problem'
              examples:
                validation_1:
                  summary: Some fields need attention.
                  value:
                    type: https://invoice.horizonpay.co/problems/validation
                    title: Validation failed
                    status: 422
                    detail: Some fields need attention.
                    instance: req_7f3c9a1e2b4d4e8a9c6f2d1b0a3e5f71
                    code: validation
                    errors:
                      - path: email
                        message: Enter a valid email
                idempotency_mismatch_2:
                  summary: >-
                    This Idempotency-Key was already used with a different
                    request body.
                  value:
                    type: >-
                      https://invoice.horizonpay.co/problems/idempotency-mismatch
                    title: Idempotency-Key reused
                    status: 422
                    detail: >-
                      This Idempotency-Key was already used with a different
                      request body.
                    instance: req_7f3c9a1e2b4d4e8a9c6f2d1b0a3e5f71
                    code: idempotency_mismatch
                    errors:
                      - path: Idempotency-Key
                        message: >-
                          Generate a new key per distinct request, not per retry
                          loop.
        '429':
          $ref: '#/components/responses/RateLimited'
      x-codeSamples:
        - lang: bash
          label: cURL
          source: |-
            curl https://invoice.horizonpay.co/api/v1/customers \
              -H "Authorization: Bearer $INVOICE_AI_API_KEY" \
              -H "Idempotency-Key: $(uuidgen)" \
              -H "Content-Type: application/json" \
              -d '{
                "name": "Acme Industries",
                "tax_id": "US-EIN 12-3456789",
                "email": "ap@acme.example",
                "address": {
                  "line1": "4th Floor, Market Tower",
                  "city": "Austin",
                  "state": "TX",
                  "postal_code": "73301",
                  "country": "US"
                }
              }'
        - lang: node
          label: Node.js
          source: |-
            import InvoiceAI from '@horizonpay/invoice-ai'

            const invoiceai = new InvoiceAI() // reads INVOICE_AI_API_KEY

            const customer = await invoiceai.customers.create({
              name: 'Acme Industries',
              tax_id: 'US-EIN 12-3456789',
              email: 'ap@acme.example',
              address: {
                line1: '4th Floor, Market Tower',
                city: 'Austin',
                state: 'TX',
                postal_code: '73301',
                country: 'US',
              },
            })
            console.log(customer.id)
        - lang: python
          label: Python
          source: |-
            from invoice_ai import InvoiceAI

            client = InvoiceAI()  # reads INVOICE_AI_API_KEY

            customer = client.customers.create(
                name="Acme Industries",
                tax_id="US-EIN 12-3456789",
                email="ap@acme.example",
                address={
                    "line1": "4th Floor, Market Tower",
                    "city": "Austin",
                    "state": "TX",
                    "postal_code": "73301",
                    "country": "US",
                },
            )
            print(customer.id)
components:
  schemas:
    CustomerCreate:
      type: object
      properties:
        name:
          type: string
          minLength: 1
          description: Full name or legal business name.
          examples:
            - Acme Industries
        email:
          description: >-
            Billing email. `POST /invoices/{id}/send` delivers here unless you
            pass `to`. `""` clears it.
          examples:
            - ap@acme.example
          anyOf:
            - type: string
              format: email
              pattern: >-
                ^(?!\.)(?!.*\.\.)([A-Za-z0-9_'+\-\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\-]*\.)+[A-Za-z]{2,}$
            - type: string
              const: ''
        phone:
          description: Phone number, free text.
          type: string
        tax_id:
          description: >-
            Tax registration number (VAT, EIN, GSTIN…), free text — no format
            check.
          type: string
        address:
          description: Billing address. Every field is optional.
          type: object
          properties:
            line1:
              description: Street address.
              type: string
            line2:
              description: Apartment, suite, unit.
              type: string
            city:
              description: City or locality.
              type: string
            state:
              description: State, province or region.
              type: string
            postal_code:
              description: ZIP or postal code.
              type: string
            country:
              description: ISO 3166-1 alpha-2 country code (uppercased for you).
              examples:
                - US
              type: string
              minLength: 2
              maxLength: 2
      required:
        - name
    Customer:
      type: object
      properties:
        id:
          type: string
          description: Unique identifier, `cus_…`.
          examples:
            - cus_Nf3kQ8pR2mX7vB1cT9wL4sZ6
        object:
          type: string
          const: customer
          description: Always `customer`.
        name:
          type: string
          description: Full name or legal business name.
          examples:
            - Acme Industries
        email:
          anyOf:
            - type: string
            - type: 'null'
          description: >-
            Billing email, copied onto invoices for this customer — where `POST
            /invoices/{id}/send` delivers by default.
          examples:
            - ap@acme.example
        phone:
          anyOf:
            - type: string
            - type: 'null'
          description: Phone number, free text.
          examples:
            - +1 512 555 0100
        tax_id:
          anyOf:
            - type: string
            - type: 'null'
          description: >-
            Tax registration number (VAT, EIN, GSTIN, ABN…), free text. Printed
            on invoices.
          examples:
            - US-EIN 12-3456789
        address:
          type: object
          properties:
            line1:
              anyOf:
                - type: string
                - type: 'null'
              description: Street address.
              examples:
                - 4th Floor, Market Tower
            line2:
              anyOf:
                - type: string
                - type: 'null'
              description: Apartment, suite, unit.
            city:
              anyOf:
                - type: string
                - type: 'null'
              description: City or locality.
              examples:
                - Austin
            state:
              anyOf:
                - type: string
                - type: 'null'
              description: State, province or region.
              examples:
                - TX
            postal_code:
              anyOf:
                - type: string
                - type: 'null'
              description: ZIP or postal code.
              examples:
                - '73301'
            country:
              anyOf:
                - type: string
                - type: 'null'
              description: ISO 3166-1 alpha-2 country code.
              examples:
                - US
          required:
            - line1
            - line2
            - city
            - state
            - postal_code
            - country
          additionalProperties: false
          description: Billing address. Every field may be null.
        deleted:
          type: boolean
          description: >-
            True once archived with `DELETE /customers/{id}`. Archived customers
            stay readable.
        created:
          type: string
          description: When the customer was created (ISO 8601).
          format: date-time
      required:
        - id
        - object
        - name
        - email
        - phone
        - tax_id
        - address
        - deleted
        - created
      additionalProperties: false
      description: Someone you bill. Customers are archived, never hard-deleted.
    Problem:
      type: object
      properties:
        type:
          type: string
          description: >-
            A URI identifying the problem type:
            `https://invoice.horizonpay.co/problems/<code>` with underscores as
            hyphens.
          examples:
            - https://invoice.horizonpay.co/problems/invalid-state
        title:
          type: string
          description: Short, human-readable summary of the problem type.
          examples:
            - Invalid state for this operation
        status:
          type: integer
          minimum: -9007199254740991
          maximum: 9007199254740991
          description: The HTTP status code, repeated for convenience.
          examples:
            - 409
        detail:
          type: string
          description: >-
            Human-readable explanation of this occurrence. Wording may change —
            do not parse it.
          examples:
            - This invoice has been finalized and can no longer be edited.
        instance:
          type: string
          description: >-
            The request id (also sent as `X-Request-Id`), for correlating with
            our logs.
          examples:
            - req_7f3c9a1e2b4d4e8a9c6f2d1b0a3e5f71
        code:
          type: string
          description: >-
            Stable machine-readable code. Branch on this, never on `detail`. One
            of: `validation`, `idempotency_mismatch`, `unauthorized`,
            `forbidden`, `not_found`, `invalid_state`, `conflict`,
            `idempotency_key_required`, `rate_limited`, `upstream_failed`,
            `internal_error`.
          examples:
            - invalid_state
        errors:
          description: Field-level problems. Present on `validation` errors only.
          type: array
          items:
            type: object
            properties:
              path:
                type: string
                description: Dot-path of the offending field, e.g. `items.0.unit_amount`.
                examples:
                  - email
              message:
                type: string
                description: What is wrong with it.
                examples:
                  - Enter a valid email
            required:
              - path
              - message
            additionalProperties: false
      required:
        - type
        - title
        - status
        - detail
        - instance
        - code
      additionalProperties: false
      description: >-
        An RFC 9457 problem details object, served as
        `application/problem+json`. Every non-2xx response uses this shape.
  headers:
    RequestId:
      required: true
      description: >-
        The request id: yours, if you sent `X-Request-Id` (up to 200
        characters), otherwise one we generated. Quote it to support.
      schema:
        type: string
        description: >-
          The request id: yours, if you sent `X-Request-Id` (up to 200
          characters), otherwise one we generated. Quote it to support.
        examples:
          - req_7f3c9a1e2b4d4e8a9c6f2d1b0a3e5f71
    RateLimitLimit:
      required: true
      description: Requests allowed in the current window for this key and bucket.
      schema:
        type: integer
        minimum: -9007199254740991
        maximum: 9007199254740991
        description: Requests allowed in the current window for this key and bucket.
        examples:
          - 120
    RateLimitRemaining:
      required: true
      description: Requests left in the current window.
      schema:
        type: integer
        minimum: -9007199254740991
        maximum: 9007199254740991
        description: Requests left in the current window.
        examples:
          - 119
    RateLimitReset:
      required: true
      description: Unix time (seconds) at which the window resets.
      schema:
        type: integer
        minimum: -9007199254740991
        maximum: 9007199254740991
        description: Unix time (seconds) at which the window resets.
        examples:
          - 1789371294
    IdempotentReplayed:
      description: >-
        Present and `true` when this response is a replay of the first response
        stored under the same `Idempotency-Key` — the operation did not run
        again.
      schema:
        description: >-
          Present and `true` when this response is a replay of the first
          response stored under the same `Idempotency-Key` — the operation did
          not run again.
        type: string
        enum:
          - 'true'
    RetryAfter:
      required: true
      description: Seconds to wait before retrying.
      schema:
        type: integer
        minimum: -9007199254740991
        maximum: 9007199254740991
        description: Seconds to wait before retrying.
        examples:
          - 42
  responses:
    Unauthorized:
      description: >-
        The API key is missing, malformed, unknown, revoked or expired. The
        response carries `WWW-Authenticate: Bearer`. Codes: `unauthorized`.
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/Problem'
          examples:
            unauthorized:
              summary: 'Send your API key as `Authorization: Bearer inv_live_…`.'
              value:
                type: https://invoice.horizonpay.co/problems/unauthorized
                title: Unauthorized
                status: 401
                detail: 'Send your API key as `Authorization: Bearer inv_live_…`.'
                instance: req_7f3c9a1e2b4d4e8a9c6f2d1b0a3e5f71
                code: unauthorized
    Forbidden:
      description: >-
        The key is valid but lacks a scope this operation needs (see
        `x-required-scope`). Codes: `forbidden`.
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/Problem'
          examples:
            forbidden:
              summary: This credential is missing the invoices:write scope.
              value:
                type: https://invoice.horizonpay.co/problems/forbidden
                title: Insufficient scope
                status: 403
                detail: This credential is missing the invoices:write scope.
                instance: req_7f3c9a1e2b4d4e8a9c6f2d1b0a3e5f71
                code: forbidden
    RateLimited:
      description: >-
        Rate limit exceeded for this API key. Wait the number of seconds in
        `Retry-After` before retrying. Codes: `rate_limited`.
      headers:
        Retry-After:
          $ref: '#/components/headers/RetryAfter'
        X-Request-Id:
          $ref: '#/components/headers/RequestId'
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/Problem'
          examples:
            rate_limited:
              summary: Rate limit of 120 per 60s exceeded.
              value:
                type: https://invoice.horizonpay.co/problems/rate-limited
                title: Too many requests
                status: 429
                detail: Rate limit of 120 per 60s exceeded.
                instance: req_7f3c9a1e2b4d4e8a9c6f2d1b0a3e5f71
                code: rate_limited
  securitySchemes:
    apiKey:
      type: http
      scheme: bearer
      bearerFormat: inv_live_<id>_<secret>
      description: >-
        An Invoice-AI API key sent as `Authorization: Bearer inv_live_…`.


        Create keys in **Settings → API keys**; the secret is shown once. Each
        key carries scopes, and every operation lists the scope it needs
        (`x-required-scope`):


        - `business:read` — Read your business profile, tax ID and bank details

        - `clients:read` — List and read your clients

        - `clients:write` — Create, update and archive clients

        - `products:read` — List and read products and prices

        - `products:write` — Create, update and archive products and prices

        - `invoices:read` — List and read invoices, including PDFs

        - `invoices:write` — Create, edit and delete drafts

        - `invoices:finalize` — Finalize invoices and void them

        - `invoices:send` — Email invoices to your clients

        - `payments:write` — Mark invoices as paid

        - `webhooks:manage` — Manage webhook endpoints


        Keys cannot be created or revoked through the API, so a leaked key
        cannot mint more keys.

````

This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.