Purchase Order Creation API
curl --request POST \
--url https://api.pazy.io/v1.0/procurement/purchase-orderimport requests
url = "https://api.pazy.io/v1.0/procurement/purchase-order"
response = requests.post(url)
print(response.text)const options = {method: 'POST'};
fetch('https://api.pazy.io/v1.0/procurement/purchase-order', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.pazy.io/v1.0/procurement/purchase-order",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://api.pazy.io/v1.0/procurement/purchase-order"
req, _ := http.NewRequest("POST", url, nil)
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.pazy.io/v1.0/procurement/purchase-order")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.pazy.io/v1.0/procurement/purchase-order")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
response = http.request(request)
puts response.read_bodyPurchase Order APIs
Purchase Order Creation API
Create purchase orders with line items, vendor details, and procurement settings
Purchase Order Creation API
curl --request POST \
--url https://api.pazy.io/v1.0/procurement/purchase-orderimport requests
url = "https://api.pazy.io/v1.0/procurement/purchase-order"
response = requests.post(url)
print(response.text)const options = {method: 'POST'};
fetch('https://api.pazy.io/v1.0/procurement/purchase-order', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.pazy.io/v1.0/procurement/purchase-order",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://api.pazy.io/v1.0/procurement/purchase-order"
req, _ := http.NewRequest("POST", url, nil)
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.pazy.io/v1.0/procurement/purchase-order")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.pazy.io/v1.0/procurement/purchase-order")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
response = http.request(request)
puts response.read_bodyAuthentication
All requests require an API key in the request headers. Headers:Authorization: Api-Key YOUR_API_KEY
Content-Type: application/json
Request
Content-Type:application/json
Body Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
poNumber | string | Yes | Purchase order number to assign to the document (minimum 1 character) |
lineItems | array | Yes | List of line items that make up the purchase order (minimum 1 item required) |
poType | string | Yes | Indicates whether the purchase order is for goods or services. Valid values: GOODS, SERVICES |
matchingType | string | Yes | Specifies whether the purchase order follows a two-way or three-way match process. Valid values: TWO_WAY, THREE_WAY |
poDate | string | Yes | Purchase order date in ISO-8601 format (YYYY-MM-DD) |
vendorId | string | No | Identifier of the vendor that should be linked to the purchase order (minimum 1 character) |
gstin | string | Yes | Entity GSTIN of the organization 15 character length ({State Code}{PAN}{Entity Number}{Z}{Checksum}) |
description | string | No | Short description for the purchase order (1-255 characters) |
currency | string | No | ISO 4217 currency code that will be used for the purchase order amounts (3 characters, e.g., INR, USD) |
paymentTerms | string | No | Free form outline of the agreed payment terms (minimum 1 character) |
deliveryDate | string | No | Requested delivery date for the purchase order in ISO-8601 format (YYYY-MM-DD) |
termsAndConditions | string | No | Terms and conditions that must be displayed on the purchase order (minimum 1 character) |
additionalNotes | string | No | Additional notes to append to the purchase order document (minimum 1 character) |
state | string | No | Target state for the PO: DRAFTED or ACTIVE. When ACTIVE is requested the API validates all required fields. If they are all present the PO is created in active state; if anything is missing it falls back to DRAFTED and the response includes submitWarnings. Omitting this field defaults to DRAFTED. |
tags | object | No | PO-level custom fields (also called tags). Map of field slug to a { value } object. Supports dropdown, text, date, and numeric fields. See Tags and Custom Fields below. |
Fields Required for Active State (when state: "ACTIVE")
When state: "ACTIVE" is passed, the following fields must be present in addition to the schema-required fields for the PO to be activated:
| Field | Level | Requirement |
|---|---|---|
vendorId | PO | Vendor must be linked |
description | PO | Description must be non-empty |
paymentTerms | PO | Payment terms must be non-empty |
quantity | Each line item | Must be greater than 0 |
rate | Each line item | Must be greater than 0 |
submitWarnings in the response identifies exactly which fields were absent.
Line Items Object
Each item in thelineItems array must contain the following fields:
| Parameter | Type | Required | Description |
|---|---|---|---|
quantity | number | Yes | Quantity requested for the line item |
rate | number | Yes | Unit rate to be applied to the line item |
identifier | string | Yes | Free-text label for the line item (minimum 1 character) |
skuCode | string | Required for THREE_WAY | Item code of an existing SKU in your inventory. Used to link this line item to a SKU. Takes priority over skuName when both are provided |
skuName | string | Required for THREE_WAY | Name of an existing SKU in your inventory. Used to link this line item to a SKU when skuCode is not provided |
tags | object | No | Line-item-level custom fields (also called tags). Map of field slug to a { value } object. |
taxId | integer | No | ID of a GST tax configured in your organization. The tax is applied to the line item total (quantity × rate) |
taxAmount | number | No | GST amount for this line item. When provided with taxId, it must match the calculated tax based on the tax rate |
GST on line items: ProvidetaxIdto apply GST to a line item. WhentaxAmountis provided, it must match the calculated tax for that line item or the request returns a400error. Line items withouttaxIdare unaffected.
SKU matching (THREE_WAY): WhenmatchingTypeisTHREE_WAY, every line item must include eitherskuCodeorskuName, and it must resolve to an existing SKU in your inventory. If any line item is missing both fields, or if the provided code/name cannot be matched, the request is rejected with a400error and the purchase order is not created. See Bulk SKU Creation to pre-populate your inventory.
Custom Fields
Custom fields use their organization-configured slugs. The value type is resolved from the configured field; dropdown values are organization tag IDs, date values useYYYY-MM-DD, and numeric values are sent as numbers. The same tags object can be included on a line item for line-level custom fields.
To get the slugs and field types available to your organization, call the Tag List API and use each data.tags[].id value as the key in tags. For dropdown fields, use the Tag Detail API with that slug to retrieve valid option IDs.
{
"tags": {
"project-code": { "value": "PRJ-42" },
"service-start-date": { "value": "2024-02-01" },
"budget-amount": { "value": 125000.5 },
"cost-centre": { "value": 1234 }
},
"lineItems": [
{
"identifier": "SKU-001",
"quantity": 1,
"rate": 1000,
"tags": { "warehouse": { "value": "WH-01" } }
}
]
}
PO Type Values
ThepoType field accepts the following values:
GOODS: Purchase order for physical goods or productsSERVICES: Purchase order for services
Matching Type Values
ThematchingType field accepts the following values:
TWO_WAY: Purchase order matching based on purchase order and invoice (2-way match)THREE_WAY: Purchase order matching based on purchase order, invoice, and goods receipt note (3-way match)
Tags and Custom Fields
Tags can be applied both at the PO level (tags in the request body) and at the line-item level (tags inside each lineItems entry). In both cases tags is an object that maps a tag slug to a { value } object:
To discover the available tag slugs, their types, and (for
DROPDOWN tags) the valid value ids, see the tag APIs: the Tag List API, and Tag Detail API.{
"tags": {
"cost-center": { "value": 42 },
"project-code": { "value": "PRJ-2024" },
"expected-by": { "value": "2024-03-15" },
"budget": { "value": 5000 }
}
}
value you send depends on the tag’s type:
| Tag type | What to send as value |
|---|---|
DROPDOWN | The tag value id (number) of an existing option |
VARCHAR | Free text (string) |
DATETIME | A date string in YYYY-MM-DD format |
NUMBER | A number |
NUMERIC | A number (legacy numeric field type) |
Code Examples
curl -X POST https://api.pazy.io/v1.0/procurement/purchase-order \
-H "Authorization: Api-Key YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"poNumber": "PO-2024-001",
"poDate": "2024-01-15",
"poType": "GOODS",
"matchingType": "THREE_WAY",
"vendorId": "<vendorId>",
"gstin": "29ABCDE1234F1Z5",
"description": "Office supplies purchase order",
"currency": "INR",
"paymentTerms": "Net 30 days",
"deliveryDate": "2024-02-15",
"termsAndConditions": "All goods must be delivered in original packaging",
"additionalNotes": "Please deliver during business hours",
"state": "ACTIVE",
"tags": {
"cost-center": { "value": 42 },
"project-code": { "value": "PRJ-2024" }
},
"lineItems": [
{
"identifier": "SKU-001",
"quantity": 100,
"rate": 50.00,
"tags": {
"budget-line": { "value": "Stationery" }
},
"taxId": 3961,
"taxAmount": 250.00
},
{
"identifier": "SKU-002",
"quantity": 50,
"rate": 75.50
}
]
}'
const response = await fetch('https://api.pazy.io/v1.0/procurement/purchase-order', {
method: 'POST',
headers: {
'Authorization': 'Api-Key YOUR_API_KEY',
'Content-Type': 'application/json'
},
body: JSON.stringify({
poNumber: 'PO-2024-001',
poDate: '2024-01-15',
poType: 'GOODS',
matchingType: 'THREE_WAY',
vendorId: '<vendorId>',
gstin: '29ABCDE1234F1Z5',
description: 'Office supplies purchase order',
currency: 'INR',
paymentTerms: 'Net 30 days',
deliveryDate: '2024-02-15',
termsAndConditions: 'All goods must be delivered in original packaging',
additionalNotes: 'Please deliver during business hours',
state: 'ACTIVE',
tags: {
'cost-center': { value: 42 },
'project-code': { value: 'PRJ-2024' }
},
lineItems: [
{
identifier: 'SKU-001',
quantity: 100,
rate: 50.00,
tags: {
'budget-line': { value: 'Stationery' }
},
taxId: 3961,
taxAmount: 250.00
},
{
identifier: 'SKU-002',
quantity: 50,
rate: 75.50
}
]
})
});
const result = await response.json();
import requests
url = "https://api.pazy.io/v1.0/procurement/purchase-order"
headers = {
"Authorization": "Api-Key YOUR_API_KEY",
"Content-Type": "application/json"
}
payload = {
"poNumber": "PO-2024-001",
"poDate": "2024-01-15",
"poType": "GOODS",
"matchingType": "THREE_WAY",
"vendorId": "<vendorId>",
"gstin": "29ABCDE1234F1Z5",
"description": "Office supplies purchase order",
"currency": "INR",
"paymentTerms": "Net 30 days",
"deliveryDate": "2024-02-15",
"termsAndConditions": "All goods must be delivered in original packaging",
"additionalNotes": "Please deliver during business hours",
"state": "ACTIVE",
"tags": {
"cost-center": {"value": 42},
"project-code": {"value": "PRJ-2024"}
},
"lineItems": [
{
"identifier": "SKU-001",
"quantity": 100,
"rate": 50.00,
"tags": {
"budget-line": {"value": "Stationery"}
},
"taxId": 3961,
"taxAmount": 250.00
},
{
"identifier": "SKU-002",
"quantity": 50,
"rate": 75.50
}
]
}
response = requests.post(url, headers=headers, json=payload)
result = response.json()
Success Response
HTTP Status:200 OK
Response Fields:
| Field | Type | Description |
|---|---|---|
ok | boolean | Indicates whether the request was successful |
data | object | Contains the purchase order creation response data |
data.poId | string | Unique slug identifier for the purchase order (use this to retrieve or update the PO) |
data.state | string | State the PO was created in — DRAFTED or APPROVED (active) |
data.skuMatchWarnings | array | Not applicable for THREE_WAY POs (unmatched SKUs are rejected). Reserved for future use |
data.submitWarnings | object | Present only when state: "ACTIVE" was requested but required fields were missing. The PO is still created in draft state |
skuMatchWarnings Object
| Field | Type | Description |
|---|---|---|
lineItemIdentifier | string | The identifier value of the unmatched line item |
skuCode | string | The skuCode that was provided (if applicable) |
skuName | string | The skuName that was provided (if applicable) |
message | string | Human-readable description of why the SKU could not be matched |
submitWarnings Object
| Field | Type | Description |
|---|---|---|
po | array of strings | PO-level fields that were missing (e.g. "vendorId", "description", "paymentTerms") |
lineItems | object | Map of line item identifier to an array of missing fields (e.g. "quantity", "rate") |
Response Example — draft (no state field or state: "DRAFTED")
{
"ok": true,
"data": {
"poId": "po_slug_identifier",
"state": "DRAFTED"
}
}
Response Example — active (state: "ACTIVE", all fields present)
{
"ok": true,
"data": {
"poId": "po_slug_identifier",
"state": "ACTIVE"
}
}
Response Example — draft despite state: "ACTIVE" (fields missing)
{
"ok": true,
"data": {
"poId": "po_slug_identifier",
"state": "DRAFTED",
"submitWarnings": {
"po": ["vendorId", "paymentTerms"],
"lineItems": {
"SKU-001": ["rate"]
}
}
}
}
Response Example — state: "ACTIVE" with SKU warnings
{
"ok": true,
"data": {
"poId": "po_slug_identifier",
"state": "ACTIVE",
"skuMatchWarnings": [
{
"lineItemIdentifier": "SKU-001",
"skuCode": "UNKNOWN-CODE",
"message": "SKU with item code \"UNKNOWN-CODE\" not found in your inventory"
}
]
}
}
Error Responses
Missing Required Fields
HTTP Status:400 Bad Request
{
"ok": false,
"error": {
"code": "MISSING_REQUIRED_FIELD",
"message": "poNumber is required"
}
}
Invalid Procurement Type
HTTP Status:400 Bad Request
{
"ok": false,
"error": {
"code": "INVALID_PROCUREMENT_TYPE",
"message": "Invalid procurement type"
}
}
Invalid Matching Type
HTTP Status:400 Bad Request
{
"ok": false,
"error": {
"code": "INVALID_MATCHING_TYPE",
"message": "Invalid matching type"
}
}
Invalid Date
HTTP Status:400 Bad Request
{
"ok": false,
"error": {
"code": "INVALID_DATE",
"message": "Invalid date"
}
}
Invalid Expected Delivery Date
HTTP Status:400 Bad Request
{
"ok": false,
"error": {
"code": "INVALID_EXPECTED_DELIVERY_DATE",
"message": "Invalid expected delivery date"
}
}
SKU Required (THREE_WAY match)
HTTP Status:400 Bad Request
{
"ok": false,
"error": {
"code": "SKU_REQUIRED",
"message": "skuCode or skuName is required for each line item in a THREE_WAY purchase order. Missing on: \"SKU-001\""
}
}
SKU Not Found (THREE_WAY match)
HTTP Status:400 Bad Request
{
"ok": false,
"error": {
"code": "SKU_NOT_FOUND",
"message": "SKU with item code \"UNKNOWN-CODE\" not found in your inventory"
}
}
Invalid Tax ID
HTTP Status:400 Bad Request
{
"ok": false,
"error": {
"code": "INTERNAL_ERROR",
"message": "Invalid tax id"
}
}
Invalid Tax Amount
HTTP Status:400 Bad Request
{
"ok": false,
"error": {
"code": "INTERNAL_ERROR",
"message": "Invalid trx tax amount"
}
}
Invalid Line Items
HTTP Status:400 Bad Request
{
"ok": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "Validation failed: /body/lineItems: must NOT have fewer than 1 items"
}
}
400 Bad Request
{
"ok": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "Validation failed: /body/lineItems/0/quantity: must be number"
}
}
400 Bad Request
{
"ok": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "Validation failed: /body/lineItems/0/identifier: must NOT have fewer than 1 characters"
}
}
Invalid Currency
HTTP Status:400 Bad Request
{
"ok": false,
"error": {
"code": "INVALID_CURRENCY",
"message": "Invalid currency Only INR is supported at the moment"
}
}
Invalid Description Length
HTTP Status:400 Bad Request
{
"ok": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "Validation failed: /body/description: must NOT have more than 255 characters"
}
}
Vendor Not Found
HTTP Status:404 Not Found
{
"ok": false,
"error": {
"code": "VENDOR_NOT_FOUND",
"message": "Vendor not found"
}
}
Invalid GSTIN
HTTP Status:400 Bad Request
{
"ok": false,
"error": {
"code": "INVALID_GSTIN",
"message": "Invalid GSTIN"
}
}
Invalid State
HTTP Status:400 Bad Request
{
"ok": false,
"error": {
"code": "INVALID_STATE",
"message": "Invalid state"
}
}
Authentication Errors
HTTP Status:401 Unauthorized
{
"ok": false,
"error": {
"code": "MISSING_CREDENTIALS",
"message": "Missing Credentials"
}
}
{
"ok": false,
"error": {
"code": "INVALID_API_KEY",
"message": "Invalid API Key"
}
}
Permission Errors
HTTP Status:403 Forbidden
{
"ok": false,
"error": {
"code": "INSUFFICIENT_PERMISSIONS",
"message": "Permission check failed - PERMISSION_CHECK_FAILED"
}
}
Purchase Order Creation Errors
HTTP Status:500 Internal Server Error
{
"ok": false,
"error": {
"code": "PROCUREMENT_CREATION_FAILED",
"message": "Error creating procurement"
}
}
Internal Error
HTTP Status:500 Internal Server Error
{
"ok": false,
"error": {
"code": "INTERNAL_ERROR",
"message": "Internal error"
}
}
Best Practices
Purchase Order Creation
- Ensure all required fields (
poNumber,lineItems,poType,matchingType,poDate) are provided - Use ISO 8601 date format (YYYY-MM-DD) for all date fields
- Provide at least one line item with valid
quantity,rate, andidentifiervalues - Use
INRfor thecurrencyfield (only INR is currently supported) - Choose the appropriate
matchingTypebased on your procurement process:- Use
TWO_WAYfor simpler matching between PO and invoice - Use
THREE_WAYwhen goods receipt verification is required
- Use
- Link the purchase order to a vendor using
vendorIdif available - Include
gstinfor tax compliance in Indian markets - Keep
descriptionconcise (max 255 characters) for better readability - Use
termsAndConditionsto specify important contractual terms - Add
additionalNotesfor delivery instructions or special requirements
Activating a PO on Creation
Passstate: "ACTIVE" when the PO is complete and ready to be activated immediately:
- The response
statewill beACTIVEif all required fields are present, orDRAFTEDif anything was missing - Check
submitWarningsin the response — it lists exactly which PO-level or line item fields prevented activation - A PO created in
DRAFTEDstate can be activated later by calling the update API withstate: "ACTIVE" - Omitting
state(or passingstate: "DRAFTED") always creates in draft, regardless of what fields are present
Line Items
- Ensure each line item has a unique
identifier - Use positive numbers for
quantityandrate
Was this page helpful?