---
title: "Create purchase order"
method: POST
path: "/v1/erp/payouts/purchase_orders"
tags: ["Purchase Order"]
---

# Create purchase order

`POST /v1/erp/payouts/purchase_orders`

## Request

- Base URL: `https://api.cardda.com`
- URL: `https://api.cardda.com/v1/erp/payouts/purchase_orders`
- Auth: HTTP bearer

Creates a new purchase order to track purchase commitments before invoices are received. This endpoint handles three types of purchase orders with comprehensive validation and automatic folio generation.

## Purchase Order Types

### 1. Against Invoice
Single invoice purchase order with exactly one item at 100%.

**Use case**: Simple purchases with a single invoice expected.

**Example**: Purchasing office equipment for $10,000 CLP.

### 2. By Milestone
Multiple named deliverables with individual percentages.

**Use case**: Projects with defined stages and separate invoicing for each stage.

**Example**: Software project with Design (30%), Development (50%), Testing (20%).

### 3. By Percentage
Multiple payment stages with percentages (no names required).

**Use case**: Service contracts with payment schedule.

**Example**: Consulting service with 30% advance, 50% mid-project, 20% final payment.

## Prerequisites

Before creating a purchase order, you need:

1. **Business Partner**: The vendor/supplier for this purchase
   - Must be pre-registered and approved
   - Find partners: `GET /v1/erp/payouts/business_partners`

2. **Company**: The company making the purchase
   - User must be a member with appropriate role
   - Required roles: admin, owner, or member

## Validation Rules

### General Rules (All Types)
- **amount_cents**: Integer, `>= 0`, in the **minor unit of `currency`** — not always a
  hundredth. `CLP` → whole pesos (`10000` is $10.000). `USD` → cents (`99000` is US$990).
  `UF`/`CLF` → **ten-thousandths of a UF** (`400000` is 40 UF). See
  [Amounts and currency](https://docs.cardda.com/docs/amounts-and-currency).
- **description**: Required field for purchase reference
- **currency**: Must be exactly one of `CLP`, `UF`, `CLF`, `USD` — **case-sensitive**, so
  `clp` or `uf` is rejected. `UF` is normalized to its ISO code `CLF` on save, which is what
  reads back.
- **items**: Must have at least one item
- **percentages**: Item percentages must sum to exactly 100%
- **custom columns**: every custom column the company marked as
  `required_on_purchase_orders` must have a value, sent in `custom_column_values` or
  coming from the column's `default_value`. Otherwise 422 with
  `errors: ["<label> es obligatoria para solicitar una orden de compra"]` and nothing is
  created. Once created, a required value cannot be emptied
  (`PATCH /v1/custom_column_values` answers 422); orders created before the column was
  required stay editable.

### Type-Specific Rules

**AgainstInvoice:**
- Must have exactly 1 item
- Item percentage must be 100%
- Item name is optional (not required)

**ByMilestone:**
- Must have at least 1 item
- Each item must have a name
- Item percentages must sum to 100%

**ByPercentage:**
- Must have at least 2 items
- Item names are optional (not required)
- Item percentages must sum to 100%

## Automatic Features

### Folio Generation
System automatically generates a `folio`: a per-company sequential **integer**
(1, 2, 3, ...), unique within the company. It is not a formatted string.

### Item Amount Calculation
System automatically calculates item amounts based on percentages:
- Formula: `item.amount_cents = (total_amount_cents × percentage / 100).round`
- Automatically recalculated when total amount changes

### Creator Tracking
System automatically assigns current authenticated user as creator (`user_id`).

## Workflow

1. Purchase order is created with status `draft`
2. Admin/creator assigns reviewers to items (if approval required)
3. Reviewers approve or reject items independently
4. When fiscal invoice arrives, it's linked to approved items
5. Linked items can be paid via bank transactions
6. When all items are paid, purchase order is considered complete

## Important Notes

- Regular users can only create purchase orders for companies they are members of
- All amounts are integers in the minor unit of the order's `currency`: whole pesos for CLP,
  cents for USD, ten-thousandths for UF/CLF. See
  [Amounts and currency](https://docs.cardda.com/docs/amounts-and-currency)
- Item amounts are automatically calculated from percentages
- With `tax_treatment` (`taxable` / `exempt`) the order is entered by its `net_amount_cents`
  and `amount_cents` is calculated as the gross total (net + 19 % IVA when taxable), which is
  what the order is matched against on the invoice
- Full audit trail is maintained for all changes

## Parameters

- `#/paths/~1v1~1erp~1payouts~1rules/get/parameters/0` (unresolved $ref)

## Request body

- object
  - `purchase_order` object, required: `tax_treatment` and `net_amount_cents` are sent together; `amount_cents` is calculated from them and ignored if sent.
    - `type` 'Erp::Payouts::PurchaseOrders::AgainstInvoice' | 'Erp::Payouts::PurchaseOrders::ByMilestone' | 'Erp::Payouts::PurchaseOrders::ByPercentage', required: Purchase order type determines validation rules: - AgainstInvoice: Exactly 1 item at 100% (item name optional) - ByMilestone: At least 1 item with names (e.g., "Design 30%", "Development 70%") - ByPercentage: At least 2 items without names (e.g., 30% advance, 70% final)
    - `business_partner_id` string, uuid, required: ID of the vendor/supplier for this purchase
    - `description` string, required: Description of what is being purchased
    - `amount_cents` integer: Total amount (gross, the amount the invoice will carry) as an integer in the minor unit of `currency`: whole pesos for CLP, cents for USD, ten-thousandths for UF/CLF (40 UF is `400000`). Item amounts are auto-calculated from percentages. See [Amounts and currency](https://docs.cardda.com/docs/amounts-and-currency). **Required unless `tax_treatment` is sent.** When the order has a `tax_treatment`, `amount_cents` is calculated from `net_amount_cents` and any value sent here is ignored.
    - `tax_treatment` 'taxable' | 'exempt', required: Whether the order is subject to Chilean IVA (19 %): - `taxable` (afecta): `amount_cents = net_amount_cents + round(net_amount_cents × 0.19)`, rounded to the minor unit of `currency`. - `exempt` (exenta): `amount_cents = net_amount_cents`. Optional for now: an order created without it keeps the `amount_cents` sent as-is and reads back with `tax_treatment: null` ("IVA not declared"). Send it together with `net_amount_cents`; one without the other is rejected with 422.
    - `net_amount_cents` integer, required: Net amount (before IVA), in the same minor unit as `amount_cents`. Required when `tax_treatment` is sent, and only accepted with it.
    - `currency` 'CLP' | 'UF' | 'CLF' | 'USD', required: Currency for this purchase order. Accepted on write: `CLP`, `UF`, `CLF`, `USD` — **case-sensitive**, so `clp` or `uf` is rejected. `UF` is normalized to its ISO 4217 code `CLF` on save, so `CLF` is what reads back.
    - `items_attributes` object[], required: Purchase order items. Percentages must sum to exactly 100%. Item amounts are automatically calculated as: (total_amount × percentage / 100).round
      - `number` integer, required: Order number (must be unique within purchase order)
      - `name` string: Item name/description: - Required for ByMilestone type - Optional for AgainstInvoice and ByPercentage types
      - `percentage` number, required: Percentage of total amount (0-100, up to 2 decimal places)
    - `custom_column_values` object[]: Values for the company's custom columns that apply to purchase orders, set in the same request and transaction as the order. Optional, unless the company marked some column as required (`required_on_purchase_orders: true` on the custom column): then every required column must end up with a value, or the order is not created (422). - Columns of another company, or that do not apply to purchase orders, are ignored. - A blank `value` is ignored, so the column's `default_value` applies. A non-blank default satisfies a required column. - For an `options` column, `value` is the option `key`; a key outside the options answers 422. - Categorization rules run after the order is created, so they do not satisfy a required column.
      - `custom_column_id` string, uuid, required
      - `value` string, required: The text, or the option `key` for an `options` column.

## Response `201`

Purchase order created successfully

- object
  - `id` string, uuid, required: Unique identifier for the purchase order
  - `company_id` string, uuid, required: ID of the company that owns this purchase order
  - `business_partner_id` string, uuid, required: ID of the vendor/supplier for this purchase order
  - `user_id` string, required: ID of the user who created this purchase order
  - `folio` integer, required: Unique sequential folio number (auto-incremented per company)
  - `description` string, required: Description of the purchase
  - `amount_cents` integer, required: Total amount as an integer in the minor unit of `currency`: whole pesos for CLP, cents for USD, ten-thousandths for UF/CLF. See [Amounts and currency](https://docs.cardda.com/docs/amounts-and-currency).
  - `tax_treatment` 'taxable' | 'exempt' | 'null', nullable: Whether the order is subject to IVA (`taxable`) or exempt (`exempt`). `null` for orders created without it ("IVA not declared"), whose `amount_cents` is the amount that was sent. When present, `amount_cents` is derived from `net_amount_cents`.
  - `net_amount_cents` integer, nullable: Net amount (before IVA). `null` when `tax_treatment` is `null`.
  - `tax_amount_cents` integer, nullable: IVA of the order (`amount_cents - net_amount_cents`): `0` for an exempt order, `null` when `tax_treatment` is `null`.
  - `currency` 'CLP' | 'CLF' | 'USD', required: Currency of the purchase order **as stored**. `UF` submitted on write reads back as its ISO 4217 code `CLF`, so responses and filters use `CLF`.
  - `type` 'Erp::Payouts::PurchaseOrders::AgainstInvoice' | 'Erp::Payouts::PurchaseOrders::ByMilestone' | 'Erp::Payouts::PurchaseOrders::ByPercentage', required: Purchase order type (STI discriminator)
  - `review_status` 'draft' | 'pending' | 'approved' | 'declined', required: Lifecycle/approval state of the purchase order: - draft: initial draft, not yet submitted for review - pending: submitted, awaiting reviewer decisions - approved: all assigned reviewers approved - declined: at least one reviewer rejected
  - `created_at` string, date-time, required: Timestamp when the purchase order was created
  - `updated_at` string, date-time, required: Timestamp when the purchase order was last updated
  - `available_balance_cents` integer: Amount still available for linking to invoices (`amount_cents - linked_amount_cents`), in the same minor unit as `amount_cents`.
  - `linked_amount_cents` integer: Total amount already linked to invoices
  - `business_partner` object: Vendor/supplier information
    - `id` string, uuid
    - `name` string
    - `partner_id` string, uuid: ID of the underlying Person record for this vendor/supplier.
    - `email` string, email
  - `items` Schema[]: List of purchase order items (unresolved $ref)
  - `payables` object[]: Related fiscal invoices (only included in show endpoint)
    - `id` string, uuid
    - `folio` string
    - `amount_cents` integer

## Other responses

- `401`: Unauthorized — missing/invalid `Authorization`, or the caller is not authorized to create a purchase order for this company (Pundit policy). **The response body is empty** for this guard (Pundit renders nothing). Branch your client logic on the status code, not the body. A bad/unknown `company-id` header resolves to a **404**, not a 401. See [Errors](https://docs.cardda.com/docs/errors) for details.
- `422`: Unprocessable Entity - Validation errors

---

[API](https://skmtc.dev/cardda/apis/erp-api.md) - [All operations](https://skmtc.dev/cardda/apis/erp-api/llms.txt) - [OpenAPI document](https://skmtc.dev/cardda/apis/erp-api/revisions/0730ed26bc3c?raw)
