> ## Knowledge Base Index
> Fetch the complete knowledge base index at: https://help.yanet.io/sitemap.xml
> Use this file to discover available pages before exploring further.
> Pure-Markdown content can be obtained by appending a '.md' suffix to the content URLs listed in the sitemap (without the trailing slash).

# Create Purchase Orders via API

## Overview
Yanet Auto Purchase Orders provides a Public API that allows external systems to create Purchase Orders programmatically.
This is useful when you want to connect Yanet Auto Purchase Orders with another system and automatically create Purchase Orders based on data from that system.

||| Note: The Public API currently supports creating Purchase Orders. Other API operations are not covered by this documentation.

**Endpoint:** `POST /api/public/purchase-orders`

### 1. Overview
| Property | Value |
| ---- |
| **Method** | `POST` |
| **Endpoint** | `/api/public/purchase-orders` |
| **Authentication** | Client credentials |
| **Content-Type** | `application/json` |
| **Maximum POs / request** | `100` |
Purchase Orders created through this endpoint are automatically recorded with:
```
source = "public_api"
```


### 2. Authentication
Before making API requests, generate your API credentials from:
**Settings → Manage key**
Each request must include the following headers:
| Header | Required | Description |
| ---- |
| `X-Client-ID` | Yes | Shop client ID |
| `X-Client-Secret` | Yes | Shop client secret |
| `X-Shop-Domain` | Yes | Shopify shop domain, e.g. your-store.myshopify.com |

###### Step 1 — Open Settings
Open **Yanet Auto Purchase Orders** and go to **Settings**.
![](https://storage.crisp.chat/users/helpdesk/website/-/3/7/f/d/37fde77105fa8400/image_159895u.png)

###### Step 2 — Open API Access
Select **API Access** from the Settings menu.
![](https://storage.crisp.chat/users/helpdesk/website/-/3/7/f/d/37fde77105fa8400/image_1r98g3n.png)

###### Step 3 — Generate API Credentials
Click **Generate new key** to generate your **Client ID** and **Client Secret**.
![](https://storage.crisp.chat/users/helpdesk/website/-/3/7/f/d/37fde77105fa8400/image_69b4rz.png)

###### Step 4 — Copy Your Credentials
Copy the **Client ID** and **Client Secret** and use them to authenticate API requests.
> Keep your Client Secret secure and do not share it publicly.

###### Example
```
X-Client-ID: YOUR_CLIENT_ID
X-Client-Secret: YOUR_CLIENT_SECRET
X-Shop-Domain: your-store.myshopify.com
```

###### Authentication errors — 401 Unauthorized
| Error | Description |
| ---- |
| `Missing X-Client-ID header` | `X-Client-ID` is missing or empty |
| `Missing X-Client-Secret header` | `X-Client-Secret` is missing or empty |
| `Missing X-Shop-Domain header` | `X-Shop-Domain` is missing or empty |
| `Invalid X-Shop-Domain: "..." is not a valid Shopify shop domain` | The shop domain does not pass Shopify domain validation |
| `Shop not found for domain "..."` | No shop exists for the specified domain |
| `Shop "..." is inactive or uninstalled` | The shop exists but is not active |
| `API credentials are not configured for shop "...". Generate keys in Settings → Manage key.` | API keys have not been generated for the shop |
| `Invalid X-Client-ID` | The client ID does not match the shop's stored credentials |
| `Invalid X-Client-Secret` | The client ID is valid, but the client secret does not match |
| `Shop record for "..." is missing an ID` | Unexpected shop configuration/data issue |


### 3. Request Body
The API supports three request formats.
###### 3.1 Recommended: Wrapped array
Use this format when creating one or multiple Purchase Orders.
```
{
  "purchase_orders": [
    {
      "...": "PO 1"
    },
    {
      "...": "PO 2"
    }
  ]
}
```

###### 3.2 Raw array
```
[
  {
    "...": "PO 1"
  },
  {
    "...": "PO 2"
  }
]
```

###### 3.3 Single Purchase Order — Legacy
A single PO can also be sent directly as the request body.
```
{
  "...": "single PO fields"
}
```
> **Recommendation:** Use the wrapped `purchase_orders` format for new integrations. It provides a consistent structure for both single-PO and batch requests.

### 4. Purchase Order Fields

###### 4.1 Required Fields
| Field | Type | Required | Description |
| ---- |
| `supplier_name` | `string` | Yes | Existing supplier name in the app. Matching is case-insensitive. |
| `destination` | `string` | Yes | Shopify Location ID. Accepts the GID or numeric ID format used by the app. |
| `items` | `array` | Yes | Purchase order line items. Must contain at least one item. |

###### Supplier field compatibility
For backward compatibility, you may use `supplier` instead of `supplier_name`.
```
{
  "supplier": "Acme Supplies"
}
```
In this API, `supplier` is interpreted as a **supplier name**, not a supplier ID.
Resolution priority:
```
supplier_name → supplier
```
If both are provided, `supplier_name` takes priority.

###### 4.2 Line Items — items[]
Each Purchase Order must contain at least one line item.
###### Required fields
| Field | Type | Required | Description |
| ---- |
| `product_id` | `string` | Yes | Shopify Product ID |
| `variant_id` | `string` | Yes | Shopify Variant ID |
| `product_title` | `string` | Yes | Product title |
| `variant_title` | `string` | Yes | Variant title |
| `quantity` | `number` | Yes | Quantity to purchase. Must be `≥ 1`. |
| `cost` | `number` | Yes | Unit cost. Must be `≥ 0`. |

###### Optional fields
| Field | Type | Description |
| ---- |
| `sku` | `string` | Product SKU |
| `supplier_sku` | `string` | Supplier SKU |
| `price` | `number` | Price. Must be `≥ 0`. |
| `compareAtPrice` | `number` | Compare-at price. Must be `≥ 0`. |
| `tax` | `number` | Tax percentage. Must be between `0` and `100`. |
| `image_url` | `string` | Product image URL |
| `accepted_quantity` | `number` | Accepted quantity. Must be `≥ 0`. |
| `rejected_quantity` | `number` | Rejected quantity. Must be `≥ 0`. |

###### 4.3 Optional Purchase Order Fields
| Field | Type | Description |
| ---- |
| `reference_number` | `string` | External or display reference |
| `sequence_number` | `number` | PO sequence number. Must be `≥ 1`. |
| `status` | `string` | `draft`, `ordered`, `partial_received`, `completed`, `cancelled`, or `closed` |
| `estimated_arrival` | `ISO date` | Estimated arrival date |
| `estimated_ship` | `ISO date` | Estimated shipping date |
| `invoice_date` | `ISO date` | Invoice date |
| `shipping_carrier` | `string` | Shipping carrier |
| `tracking_number` | `string` | Tracking number |
| `additional_note` | `string` | Additional notes |
| `tags` | `string[]` | PO tags |
| `payment_status` | `string` | `pending`, `partial`, or `paid` |
| `amount_paid` | `number` | Amount paid. Must be `≥ 0`. |
| `paid_on` | `ISO date` | Payment date |
| `payment_due` | `ISO date` | Payment due date |
| `payment_note` | `string` | Payment note |
| `payment_method` | `string` | Payment method |
| `payment_terms` | `string` | Payment terms |
| `additional_costs` | `array` | Additional costs associated with the PO |

###### 4.4 Additional Costs — additional\_costs[]
Each additional cost must contain:
| Field | Type | Required | Description |
| ---- |
| `name` | `string` | Yes | Cost name |
| `value` | `string` | Yes | Cost description/value |
| `cost` | `number` | Yes | Cost amount. Must be `≥ 0`. |
Example:
```
{
  "additional_costs": [
    {
      "name": "Shipping",
      "value": "Express",
      "cost": 50
    }
  ]
}
```

###### 4.5 Server-managed Fields
The following fields should not be provided by the client:
| Field | Behavior |
| ---- |
| `shopId` | Automatically resolved from `X-Shop-Domain` |
| `source` | Automatically set to `public_api` |
| `supplier` as an ID | Supplier is resolved from the supplier name |


### 5. Response
The endpoint uses **partial success** for batch requests.
A request can return `200 OK` even when some Purchase Orders fail.
Always check:
* `success` — number of successfully created POs
* `failed` — number of failed POs
* `created` — details of successfully created POs
* `errors` — errors for POs that could not be created

###### Example response
```
{
  "success": 1,
  "failed": 1,
  "created": [
    {
      "id": "674a1b2c3d4e5f678901234",
      "po_number": "PO-042",
      "reference_number": "EXT-1001",
      "sequence_number": 42,
      "supplier": "6655aabbccddeeff00112233",
      "supplier_name": "Acme Supplies",
      "destination": "gid://shopify/Location/123456789",
      "status": "ordered",
      "payment_status": "pending",
      "tracking_number": "",
      "estimated_arrival": "2026-09-01T00:00:00.000Z",
      "estimated_ship": null,
      "invoice_date": null,
      "subtotal": 1000,
      "tax_total": 0,
      "additional_costs_total": 50,
      "total": 1050,
      "item_count": 1,
      "source": "public_api",
      "created_at": "2026-08-21T07:00:00.000Z",
      "updated_at": "2026-08-21T07:00:00.000Z",
      "shipping_carrier": "",
      "additional_note": "",
      "tags": [],
      "amount_paid": 0,
      "paid_on": null,
      "payment_due": null,
      "payment_note": "",
      "payment_method": "",
      "payment_terms": "",
      "items": [
        {
          "product_id": "gid://shopify/Product/111",
          "variant_id": "gid://shopify/ProductVariant/222",
          "product_title": "T-Shirt",
          "variant_title": "M / Black",
          "sku": "TS-M-BLK",
          "supplier_sku": "ACME-TS-01",
          "quantity": 10,
          "accepted_quantity": 0,
          "rejected_quantity": 0,
          "cost": 100,
          "price": 0,
          "tax": 0,
          "image_url": ""
        }
      ],
      "additional_costs": [
        {
          "name": "Shipping",
          "value": "Express",
          "cost": 50
        }
      ]
    }
  ],
  "errors": [
    "EXT-1002: Supplier not found for name \"Unknown Vendor\""
  ]
}
```

###### Response fields
| Field | Type | Description |
| ---- |
| `success` | `number` | Number of POs created successfully |
| `failed` | `number` | Number of POs that failed |
| `created` | `array` | Details of successfully created POs |
| `errors` | `string[]` | Per-PO error messages. Uses `reference_number` or `PO #n` to identify the failed PO. |


### 6. Errors
###### 6.1 Request-level errors — 400 Bad Request
Request-level errors prevent the request from being processed.
| Error | Description |
| ---- |
| `purchase_orders is required and must be a non-empty array` | Request body is empty or no Purchase Orders could be parsed |
| `You can create at most 100 purchase orders per request` | The request contains more than 100 POs |
| `Shop not resolved from X-Shop-Domain` | The shop could not be resolved after authentication |

###### 6.2 Per-PO errors
Per-PO errors are returned inside the `errors[]` array. Other valid POs in the same request are still processed.
| Error | Description |
| ---- |
| `supplier_name is required (or supplier as supplier name)` | Supplier name is missing |
| `destination is required` | Destination is missing |
| `items is required and must be non-empty` | `items` is missing or empty |
| `Supplier not found for name "..."` | No matching supplier exists in the shop |

###### Batch behavior
For example, if a request contains 3 POs and 1 fails:
```
{
  "success": 2,
  "failed": 1,
  "created": ["PO 1", "PO 3"],
  "errors": [
    "EXT-1002: Supplier not found for name \"Unknown Vendor\""
  ]
}
```
The successful POs are still created.


### 7. cURL Examples
###### 7.1 Create Multiple Purchase Orders
```
curl -X POST "https://your-app-host.com/api/public/purchase-orders" \
  -H "Content-Type: application/json" \
  -H "X-Client-ID: YOUR_CLIENT_ID" \
  -H "X-Client-Secret: YOUR_CLIENT_SECRET" \
  -H "X-Shop-Domain: your-store.myshopify.com" \
  -d '{
    "purchase_orders": [
      {
        "supplier_name": "Acme Supplies",
        "destination": "gid://shopify/Location/123456789",
        "reference_number": "EXT-1001",
        "status": "ordered",
        "estimated_arrival": "2026-09-01",
        "additional_note": "Created via public API",
        "tags": [
          "api",
          "wholesale"
        ],
        "items": [
          {
            "product_id": "gid://shopify/Product/111",
            "variant_id": "gid://shopify/ProductVariant/222",
            "product_title": "T-Shirt",
            "variant_title": "M / Black",
            "sku": "TS-M-BLK",
            "quantity": 10,
            "cost": 100,
            "tax": 0
          }
        ],
        "additional_costs": [
          {
            "name": "Shipping",
            "value": "Express",
            "cost": 50
          }
        ]
      }
    ]
  }'
```

###### 7.2 Create a Single Purchase Order
The recommended format can also be used for a single PO:
```
curl -X POST "https://your-app-host.com/api/public/purchase-orders" \
  -H "Content-Type: application/json" \
  -H "X-Client-ID: YOUR_CLIENT_ID" \
  -H "X-Client-Secret: YOUR_CLIENT_SECRET" \
  -H "X-Shop-Domain: your-store.myshopify.com" \
  -d '{
    "purchase_orders": [
      {
        "supplier_name": "Acme Supplies",
        "destination": "gid://shopify/Location/123456789",
        "status": "draft",
        "items": [
          {
            "product_id": "gid://shopify/Product/111",
            "variant_id": "gid://shopify/ProductVariant/222",
            "product_title": "T-Shirt",
            "variant_title": "M / Black",
            "quantity": 5,
            "cost": 100
          }
        ]
      }
    ]
  }'
```


### 8. JavaScript Example
```
const BASE_URL = 'https://po.yanet.io';

const headers = {
  'Content-Type': 'application/json',
  'X-Client-ID': 'YOUR_CLIENT_ID',
  'X-Client-Secret': 'YOUR_CLIENT_SECRET',
  'X-Shop-Domain': 'your-store.myshopify.com',
};

const response = await fetch(
  `${BASE_URL}/api/public/purchase-orders`,
  {
    method: 'POST',
    headers,
    body: JSON.stringify({
      purchase_orders: [
        {
          supplier_name: 'Acme Supplies',
          destination: 'gid://shopify/Location/123456789',
          reference_number: 'EXT-1001',
          status: 'ordered',
          items: [
            {
              product_id: 'gid://shopify/Product/111',
              variant_id: 'gid://shopify/ProductVariant/222',
              product_title: 'T-Shirt',
              variant_title: 'M / Black',
              sku: 'TS-M-BLK',
              quantity: 10,
              cost: 100,
            },
          ],
        },
      ],
    }),
  }
);

const result = await response.json();

console.log('Created:', result.success);
console.log('Failed:', result.failed);
console.log('Errors:', result.errors);
console.log('Created POs:', result.created);
```


### 9. Integration Guidelines
Before integrating, make sure that:
1. **The supplier already exists** in Yanet. Supplier matching is performed by name and is case-insensitive.
2. **The destination is valid** for the Shopify shop and uses a Location ID recognized by the app.
3. **A maximum of 100 POs** can be submitted in a single request.
4. **Batch requests support partial success.** A failed PO does not prevent other POs from being created.
5. After every request, check `success`, `failed`, and `errors`.
6. Each create operation can be recorded in **API Call History** when API logging is enabled.


### 10. Quick Reference
###### Endpoint
```
POST /api/public/purchase-orders
```

###### Required headers
```
Content-Type: application/json
X-Client-ID: YOUR_CLIENT_ID
X-Client-Secret: YOUR_CLIENT_SECRET
X-Shop-Domain: your-store.myshopify.com
```

###### Minimal request
```
{
  "purchase_orders": [
    {
      "supplier_name": "Acme Supplies",
      "destination": "gid://shopify/Location/123456789",
      "items": [
        {
          "product_id": "gid://shopify/Product/111",
          "variant_id": "gid://shopify/ProductVariant/222",
          "product_title": "T-Shirt",
          "variant_title": "M / Black",
          "quantity": 10,
          "cost": 100
        }
      ]
    }
  ]
}
```

###### Success indicators
```
HTTP 200
success > 0
failed = 0
```
For batch requests, `HTTP 200` does not necessarily mean every PO was created successfully. Always inspect `success`, `failed`, and `errors`.