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

# Create Invoice

> The issuing organization is resolved from the API key — no organizationId is required. The customer can be sent inline (`customer`) or referenced by ID (`customerId`).

Builds, validates, and stamps (timbra) a CFDI 4.0 invoice through the configured PAC (Proveedor Autorizado de Certificación) in a single call. On success, the invoice is fiscally valid at the SAT and is recorded in the organization's invoice history.

## Authentication

Requires an API key with the `write:invoices` scope, sent via the `X-API-Key` header. **The issuing organization is resolved from the API key** — there is no `organizationId` parameter anywhere in this request. The issuer's RFC, legal name, régimen fiscal, and ZIP code are resolved from the organization's fiscal profile (`PUT /organizations/:organizationId/legal`) — configure that before creating your first invoice, or this endpoint returns a 400.

This endpoint does not accept Supabase Bearer tokens, since a logged-in user can belong to multiple organizations and the organization can't be resolved unambiguously that way.

## Request Body

| Field            | Type   | Required | Description                                                   |
| ---------------- | ------ | -------- | ------------------------------------------------------------- |
| `series`         | string | Yes      | Invoice series (e.g. `A`)                                     |
| `folioNumber`    | string | Yes      | Invoice folio number                                          |
| `date`           | string | Yes      | Issuance date-time (ISO 8601, no timezone)                    |
| `paymentForm`    | string | Yes      | SAT forma de pago catalog code                                |
| `use`            | string | Yes      | SAT uso CFDI catalog code                                     |
| `customer`       | object | One of\* | Inline customer — see below                                   |
| `customerId`     | string | One of\* | Existing customer ID (from the Customers resource)            |
| `items`          | array  | Yes      | Line items — see below                                        |
| `type`           | string | No       | `I` (Ingreso), `E` (Egreso), or `T` (Traslado) — default: `I` |
| `paymentMethod`  | string | No       | `PUE` or `PPD` — default: `PUE`                               |
| `currency`       | string | No       | Currency code — default: `MXN`                                |
| `exchange`       | number | No       | Exchange rate — default: `1`                                  |
| `export`         | string | No       | SAT clave de exportación — default: `01`                      |
| `idempotencyKey` | string | No       | See [Idempotency](#idempotency) below                         |

\* Exactly one of `customer` or `customerId` is required — sending both, or neither, returns a 400.

`subtotal` and `total` are **not** request fields — they're computed automatically from `items`.

### `customer` (inline)

| Field       | Type   | Required | Description                                                                  |
| ----------- | ------ | -------- | ---------------------------------------------------------------------------- |
| `legalName` | string | Yes      | Customer legal name                                                          |
| `taxId`     | string | Yes      | Customer RFC                                                                 |
| `taxSystem` | string | No       | SAT régimen fiscal code — required unless the customer is público en general |
| `zip`       | string | No       | Customer ZIP code                                                            |

### `items[]`

| Field         | Type   | Required | Description                                                            |
| ------------- | ------ | -------- | ---------------------------------------------------------------------- |
| `quantity`    | number | Yes      | Quantity                                                               |
| `description` | string | Yes      | Line item description                                                  |
| `unitPrice`   | number | Yes      | Unit price                                                             |
| `amount`      | number | Yes      | Line total (`quantity * unitPrice`)                                    |
| `productKey`  | string | Yes      | SAT product/service key                                                |
| `unitKey`     | string | Yes      | SAT unit key                                                           |
| `unit`        | string | No       | Unit label — defaults to `unitKey`                                     |
| `taxObject`   | string | No       | SAT objeto de impuesto code — defaults based on `taxes`                |
| `taxes[]`     | array  | No       | Transferred tax lines — `type`, `factorType`, `rate`, `base`, `amount` |

## Business rules applied automatically

* The issuer (RFC, legal name, régimen fiscal, ZIP) is resolved from `organization_legal`, matching how Facturapi resolves the issuer from the account profile instead of requiring it on every request.
* `base` is required on every tax line in CFDI 4.0 — filled from the item's `amount` if omitted.
* `subtotal`/`total` are computed from `items[].amount` and `items[].taxes[].amount`.
* Invoices to **público en general** (`customer.taxId = XAXX010101000`, `customer.legalName = PUBLICO EN GENERAL`, `type = I`) automatically get the SAT `InformacionGlobal` node and `use = S01`.
* The customer's RFC, régimen fiscal, and uso CFDI are validated against the SAT catalog compatibility matrix before stamping (see [Create Customer](/api-reference/customers/create#fiscal-validation-error-cfdi-4-0) for the same validation rules).

See [`docs/PAC_CFDI_CONTRACT.md`](https://github.com/Timbrix/timbrix-platform/blob/main/docs/PAC_CFDI_CONTRACT.md) in the repository for the internal payload contract this endpoint translates into before calling the PAC.

## Idempotency

Stamping is not safe to blindly retry — if your request to `POST /invoices` times out, you can't tell whether the CFDI was actually stamped at the SAT before the timeout. Pass a unique `idempotencyKey` (e.g. your own order ID) and retry with the **same** key: if an invoice was already created for your organization with that key, the existing invoice is returned instead of stamping a second one.

```json theme={null}
{
  "series": "A",
  "folioNumber": "1",
  "idempotencyKey": "order-8421",
  "...": "..."
}
```

Without an `idempotencyKey`, every request stamps a new CFDI — retries are your responsibility to avoid.

## Example Request

```bash theme={null}
curl -X POST https://api.timbrix.mx/invoices \
  -H "X-API-Key: sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "series": "A",
    "folioNumber": "1",
    "date": "2026-07-30T15:50:00",
    "paymentForm": "01",
    "paymentMethod": "PUE",
    "use": "G03",
    "customerId": "590ce6c56d04f840aa8438af",
    "items": [
      {
        "quantity": 1,
        "description": "Servicio de consultoría",
        "unitPrice": 100,
        "amount": 100,
        "productKey": "84111506",
        "unitKey": "E48",
        "taxes": [
          {
            "type": "002",
            "factorType": "Tasa",
            "rate": "0.160000",
            "amount": 16
          }
        ]
      }
    ]
  }'
```

## Example Response

```json theme={null}
{
  "id": "0f2a1c3e-1a2b-4c3d-9e8f-1234567890ab",
  "uuid": "d3bfbc57-44af-4390-a064-f0afab85e5df",
  "status": "valid",
  "series": "A",
  "folioNumber": "1",
  "total": 116,
  "date": "2026-07-30T15:50:00",
  "xml": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><cfdi:Comprobante ...>",
  "createdAt": "2026-07-30T15:50:03.412Z"
}
```

## Common Errors

### 400 Bad Request

Invalid or incomplete invoice payload, an issuing organization without complete fiscal data, sending both/neither `customer` and `customerId`, or a customer whose RFC/régimen/uso CFDI combination fails SAT catalog validation.

```json theme={null}
{
  "statusCode": 400,
  "message": "La organización no tiene datos fiscales completos (RFC, razón social, régimen fiscal y código postal). Complétalos en PUT /organizations/:organizationId/legal antes de timbrar.",
  "error": "BAD_REQUEST"
}
```

### 401 Unauthorized

Missing or invalid API key.

### 403 Forbidden

The API key does not have the `write:invoices` scope.

### 404 Not Found

`customerId` does not exist, or belongs to a different organization than the one owning the API key.

### 503 Service Unavailable

The PAC did not respond after automatic retries, or returned a transient error. Safe to retry.


## OpenAPI

````yaml POST /invoices
openapi: 3.1.0
info:
  title: Timbrix API
  description: >-
    REST API with OAuth2 server for managing organizations, members, and
    webhooks
  version: '1.0'
  contact: {}
servers:
  - url: https://api.timbrix.mx
    description: Production
security: []
tags:
  - name: organizations
    description: Organization management endpoints
  - name: oauth
    description: OAuth2 authentication and authorization
  - name: webhooks
    description: Webhook configuration and delivery
  - name: users
    description: User information endpoints
  - name: api-keys
    description: API Keys management and validation
  - name: invoices
    description: CFDI 4.0 invoice creation and stamping via PAC
paths:
  /invoices:
    post:
      tags:
        - invoices
      summary: Create and stamp a CFDI 4.0 invoice via the configured PAC
      description: >-
        The issuing organization is resolved from the API key — no
        organizationId is required. The customer can be sent inline (`customer`)
        or referenced by ID (`customerId`).
      operationId: InvoicesController_create
      parameters: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateInvoiceDto'
      responses:
        '201':
          description: ''
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/InvoiceResponseDto'
        '400':
          description: Invalid invoice payload
        '404':
          description: customerId not found
        '503':
          description: PAC unavailable
      security:
        - apiKey: []
components:
  schemas:
    CreateInvoiceDto:
      type: object
      properties:
        type:
          type: string
          enum:
            - I
            - E
            - T
          example: I
          description: 'Ingreso, Egreso, or Traslado (default: I)'
        series:
          type: string
          example: A
        folioNumber:
          type: string
          example: '1'
        date:
          type: string
          example: '2026-07-30T22:50:00'
        paymentForm:
          type: string
          example: '01'
          description: SAT forma de pago catalog code
        paymentMethod:
          type: string
          enum:
            - PUE
            - PPD
          example: PUE
          description: 'Default: PUE'
        currency:
          type: string
          example: MXN
          description: 'Default: MXN'
        exchange:
          type: number
          example: 1
          description: 'Default: 1'
        export:
          type: string
          example: '01'
          description: 'SAT clave de exportación catalog code — default: 01'
        use:
          type: string
          example: G03
          description: SAT uso CFDI catalog code
        customer:
          $ref: '#/components/schemas/InvoiceCustomerDto'
        customerId:
          type: string
          description: Existing customer ID — mutually exclusive with `customer`
        items:
          type: array
          items:
            $ref: '#/components/schemas/InvoiceItemDto'
        idempotencyKey:
          type: string
          description: >-
            Client-supplied key to safely retry this request without
            double-stamping. If an invoice was already created for this
            organization with the same key, that invoice is returned instead of
            stamping again.
          example: order-8421-attempt-1
      required:
        - series
        - folioNumber
        - date
        - paymentForm
        - use
        - items
    InvoiceResponseDto:
      type: object
      properties:
        id:
          type: string
          description: Timbrix invoice record ID
          example: 0f2a1c3e-1a2b-4c3d-9e8f-1234567890ab
        uuid:
          type: string
          description: Folio fiscal UUID asignado por el SAT vía PAC
          example: d3bfbc57-44af-4390-a064-f0afab85e5df
        status:
          type: string
          enum:
            - valid
          example: valid
        series:
          type: string
          example: A
        folioNumber:
          type: string
          example: '1'
        total:
          type: number
          example: 116
        date:
          type: string
          example: '2026-07-30T22:50:00'
        xml:
          type: string
          description: XML del CFDI timbrado (UTF-8)
        createdAt:
          type: string
          example: '2026-07-30T22:50:03.412Z'
      required:
        - id
        - uuid
        - status
        - series
        - folioNumber
        - total
        - date
        - xml
        - createdAt
    InvoiceCustomerDto:
      type: object
      properties:
        legalName:
          type: string
          example: ESCUELA KEMPER URGATE SA DE CV
        taxId:
          type: string
          example: EKU9003173C9
        taxSystem:
          type: string
          example: '601'
          description: >-
            SAT régimen fiscal code — required unless the customer is público en
            general
        zip:
          type: string
          example: '45079'
      required:
        - legalName
        - taxId
    InvoiceItemDto:
      type: object
      properties:
        quantity:
          type: number
          example: 1
        description:
          type: string
          example: Servicio de consultoría
        unitPrice:
          type: number
          example: 100
        amount:
          type: number
          example: 100
        productKey:
          type: string
          example: '84111506'
        unitKey:
          type: string
          example: E48
        unit:
          type: string
          example: E48
        taxObject:
          type: string
          example: '02'
        taxes:
          type: array
          items:
            $ref: '#/components/schemas/InvoiceTaxDto'
      required:
        - quantity
        - description
        - unitPrice
        - amount
        - productKey
        - unitKey
    InvoiceTaxDto:
      type: object
      properties:
        type:
          type: string
          example: '002'
        factorType:
          type: string
          example: Tasa
        rate:
          type: string
          example: '0.160000'
        base:
          type: number
          example: 100
        amount:
          type: number
          example: 16
      required:
        - type
        - factorType
  securitySchemes:
    apiKey:
      type: apiKey
      in: header
      name: X-API-Key
      description: 'API Key for authentication (format: sk_...)'

````