Vendor Advance Creation API
curl --request POST \
--url https://api.pazy.io/v1.0/payment/request/vendor-advanceimport requests
url = "https://api.pazy.io/v1.0/payment/request/vendor-advance"
response = requests.post(url)
print(response.text)const options = {method: 'POST'};
fetch('https://api.pazy.io/v1.0/payment/request/vendor-advance', 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/payment/request/vendor-advance",
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/payment/request/vendor-advance"
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/payment/request/vendor-advance")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.pazy.io/v1.0/payment/request/vendor-advance")
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_bodyPayment APIs
Vendor Advance Creation API
Raise a vendor advance payment request, optionally linked to a purchase order
Vendor Advance Creation API
curl --request POST \
--url https://api.pazy.io/v1.0/payment/request/vendor-advanceimport requests
url = "https://api.pazy.io/v1.0/payment/request/vendor-advance"
response = requests.post(url)
print(response.text)const options = {method: 'POST'};
fetch('https://api.pazy.io/v1.0/payment/request/vendor-advance', 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/payment/request/vendor-advance",
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/payment/request/vendor-advance"
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/payment/request/vendor-advance")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.pazy.io/v1.0/payment/request/vendor-advance")
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
x-idempotency-key: YOUR_UNIQUE_REQUEST_ID
In addition to the API key permission, the user the API key belongs to must have an admin or bookkeeper role in the organization, and vendor advances must be enabled for the organization. Either check failing returns
403 ACCESS_DENIED — see Error Responses.x-idempotency-key is optional but strongly recommended on this endpoint. When supplied, a repeat request with the same key within 5 minutes is rejected with 409 Conflict instead of creating a second advance. Generate a new unique value (e.g. a UUID) per logical advance, and reuse it only when retrying that exact attempt.Request
Content-Type:application/json
Body Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
amount | number | Yes | Original (pre-tax) advance amount. Must be greater than 0. Gross and net payable are computed server-side |
vendorId | string | Yes | Vendor identifier the advance is raised for (1–100 characters), as returned by the Vendor Creation or Vendor List APIs |
gstId | integer | No | Organization GST tax id from the GST Taxes API. When provided, gstAmount becomes required |
gstAmount | number | No | Expected GST amount. Required when gstId is provided, and rejected when it isn’t. Validated against the server-computed value |
tdsId | integer | No | Organization TDS tax id from the TDS Taxes API. When provided, tdsAmount becomes required |
tdsAmount | number | No | Expected TDS amount. Required when tdsId is provided, and rejected when it isn’t. Validated against the server-computed value |
currency | string | No | ISO currency code (1–8 characters). Must be an active organization currency. Defaults to the organization’s base currency |
purchaseOrderLinkId | string | No | Purchase order identifier to link (1–100 characters). See Purchase Order Linking |
narration | string | No | Free-text description for the advance (max 1000 characters) |
tags | object | No | Tags and custom fields keyed by slug. See Tags Object |
orgEntity | integer | No | Organization entity (GSTIN entity) id, as returned by the Company Entities API. Defaults to the organization’s first entity |
Unknown body properties are rejected rather than ignored — sending any field not listed above returns a
400 VALIDATION_ERROR.Tax Handling
GST and TDS are both optional, and each id must travel together with its amount:- Both omitted — the tax is treated as not applicable and contributes
0. - Both supplied — the amount is recomputed server-side from the tax’s configured rate (
PERCENTAGEorABSOLUTE) and compared against the value you sent. A mismatch rejects the whole request, so you always know the exact tax before the advance is created. - One supplied without the other — rejected with
400 VALIDATION_ERROR.
rate and rateType, and send them alongside the ids.
Amount Calculation
amount is the pre-tax value. The net payable stored on the request is derived as:
netPayable = amount + gstAmount - tdsAmount + roundOff
amount, with your original value available there as tax.subTotal.
Purchase Order Linking
WhenpurchaseOrderLinkId is supplied, the purchase order must satisfy all of the following, or the request is rejected:
- It exists in the organization
- It belongs to the same vendor as
vendorId - Its state is
APPROVED - Its matching state is
UNMATCHED - It is not already linked to another advance (including one pending resubmission)
- The advance’s net payable does not exceed the purchase order amount
Tags Object
tags is keyed by tag or custom-field slug, with each entry wrapping the value in a value property. Dropdown tags (including the predefined cost centre, department, location and expense head tags) take the value id; text, numeric and date fields take the literal value.
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-centre": { "value": 812 },
"project-code": { "value": "PRJ-1" },
"delivery-date": { "value": "2026-07-01" }
}
Submission and Approval Policies
The organization’s submission policy is enforced strictly: if it marks any field mandatory for a procurement of this amount, that field must be present or the request fails withMISSING_REQUIRED_FIELD listing every missing slug. Note that sending narration satisfies a required description field, and sending purchaseOrderLinkId satisfies a required identifier field.
The organization’s approval policy is then matched against the payload (amount, vendor, tags, PO), falling back to the organization’s default policy when no rule matches. The name of the policy that was applied comes back as policyUsed:
- Policy has approval stages → the request is created in
PENDINGstate and the approval workflow starts, notifying approvers. - Policy has no approval stages → the request is created already
APPROVED.
Code Examples
curl -X POST https://api.pazy.io/v1.0/payment/request/vendor-advance \
-H "Authorization: Api-Key YOUR_API_KEY" \
-H "Content-Type: application/json" \
-H "x-idempotency-key: 3f29f2b0-6b8b-4e21-9b0a-9e6f1e2d4c31" \
-d '{
"amount": 100000,
"vendorId": "vendor_identifier",
"gstId": 4170,
"gstAmount": 18000,
"tdsId": 5019,
"tdsAmount": 1000,
"currency": "INR",
"purchaseOrderLinkId": "purchase_order_identifier",
"narration": "Advance for Q3 packaging order",
"tags": {
"cost-centre": { "value": 812 },
"project-code": { "value": "PRJ-1" }
}
}'
const response = await fetch(
'https://api.pazy.io/v1.0/payment/request/vendor-advance',
{
method: 'POST',
headers: {
Authorization: 'Api-Key YOUR_API_KEY',
'Content-Type': 'application/json',
'x-idempotency-key': crypto.randomUUID()
},
body: JSON.stringify({
amount: 100000,
vendorId: 'vendor_identifier',
gstId: 4170,
gstAmount: 18000,
tdsId: 5019,
tdsAmount: 1000,
currency: 'INR',
purchaseOrderLinkId: 'purchase_order_identifier',
narration: 'Advance for Q3 packaging order',
tags: {
'cost-centre': { value: 812 },
'project-code': { value: 'PRJ-1' }
}
})
}
);
const result = await response.json();
import requests
import uuid
url = "https://api.pazy.io/v1.0/payment/request/vendor-advance"
headers = {
"Authorization": "Api-Key YOUR_API_KEY",
"Content-Type": "application/json",
"x-idempotency-key": str(uuid.uuid4())
}
payload = {
"amount": 100000,
"vendorId": "vendor_identifier",
"gstId": 4170,
"gstAmount": 18000,
"tdsId": 5019,
"tdsAmount": 1000,
"currency": "INR",
"purchaseOrderLinkId": "purchase_order_identifier",
"narration": "Advance for Q3 packaging order",
"tags": {
"cost-centre": {"value": 812},
"project-code": {"value": "PRJ-1"}
}
}
response = requests.post(url, headers=headers, json=payload)
result = response.json()
Minimal Request
Onlyamount and vendorId are required — taxes, currency, PO link and tags are all optional (subject to the organization’s submission policy):
{
"amount": 25000,
"vendorId": "vendor_identifier"
}
Success Response
HTTP Status:200 OK
Response Fields:
| Field | Type | Description |
|---|---|---|
ok | boolean | Indicates whether the request was successful |
data.paymentRequestId | string | Identifier of the created payment request. Pass to the Payment Request Details API |
data.policyUsed | string | Name of the approval policy that was applied |
Response Example
{
"ok": true,
"data": {
"paymentRequestId": "<paymentRequestId>",
"policyUsed": "Procurement Default Policy"
}
}
Creation is atomic — the advance, its tag mappings, the payment request, its payers and the approval workflow all commit together. If any step fails, nothing is created.
Error Responses
Validation Errors
HTTP Status:400 Bad Request
Schema-level failures:
{
"ok": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "Validation failed: vendorId: must have required property 'vendorId'"
}
}
{
"ok": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "Validation failed: /amount: must be > 0"
}
}
{
"ok": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "Validation failed: vendorName: must NOT have additional properties"
}
}
Tax Errors
HTTP Status:400 Bad Request
An id sent without its amount, or vice versa:
{
"ok": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "gstId and gstAmount must be provided together"
}
}
{
"ok": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "tdsId and tdsAmount must be provided together"
}
}
{
"ok": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "Invalid gstId"
}
}
{
"ok": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "Invalid tdsId"
}
}
{
"ok": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "Invalid gstAmount: computed GST is 18000 but 17500 was given"
}
}
{
"ok": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "Invalid tdsAmount: computed TDS is 1000 but 900 was given"
}
}
Currency and Entity Errors
HTTP Status:400 Bad Request
{
"ok": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "Invalid currency: USD"
}
}
{
"ok": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "Invalid org entity id given."
}
}
Vendor Not Found
HTTP Status:404 Not Found
{
"ok": false,
"error": {
"code": "VENDOR_NOT_FOUND",
"message": "Vendor not found"
}
}
Purchase Order Link Errors
HTTP Status:400 Bad Request
All PO link failures return VALIDATION_ERROR; the message identifies which check failed:
message | Meaning |
|---|---|
Purchase order not found | No purchase order matches purchaseOrderLinkId in this organization |
Purchase order does not belong to the selected vendor | The PO’s vendor differs from vendorId |
Purchase order must be approved to link an advance | The PO is not in APPROVED state |
Purchase order is already matched | The PO’s matching state is not UNMATCHED |
Purchase order is already linked to another advance | An active advance already references this PO |
Purchase order is linked to an advance pending resubmission | An advance against this PO is awaiting resubmission |
Advance net payable cannot exceed the linked purchase order amount | The computed net payable is greater than the PO amount |
{
"ok": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "Purchase order does not belong to the selected vendor"
}
}
Missing Policy-Mandated Fields
HTTP Status:400 Bad Request
The organization’s submission policy requires fields that weren’t supplied. Every missing slug is listed:
{
"ok": false,
"error": {
"code": "MISSING_REQUIRED_FIELD",
"message": "Missing required fields: cost-centre, description"
}
}
Policy Resolution Errors
HTTP Status:400 Bad Request
The applicable approval policy could not produce a usable payer — usually a misconfigured procurement policy that needs fixing in Pazy:
{
"ok": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "No valid payer could be resolved from the applicable procurement policy"
}
}
{
"ok": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "Invalid payer users in the applicable procurement policy"
}
}
Access Denied
HTTP Status:403 Forbidden
The API key’s user is not an admin or bookkeeper:
{
"ok": false,
"error": {
"code": "ACCESS_DENIED",
"message": "Access denied: only admins and bookkeepers can create vendor advances"
}
}
{
"ok": false,
"error": {
"code": "ACCESS_DENIED",
"message": "Vendor advances are not enabled for this organization"
}
}
Duplicate Idempotency Key
If the samex-idempotency-key is reused within 5 minutes of the original request, it is rejected before any processing happens. Note this error uses a different response shape than the rest of this API:
HTTP Status: 409 Conflict
{
"error": "Duplicate request: idempotency key already used"
}
x-idempotency-key that is present but empty, over 256 bytes, or contains control characters:
HTTP Status: 400 Bad Request
{
"error": "Invalid x-idempotency-key header"
}
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"
}
}
Best Practices
- Read the tax ids and rates from TDS Taxes and GST Taxes at the start of each run rather than hardcoding ids — rates change, and a stale id fails the amount check
- Send a unique
x-idempotency-keyper advance so a timeout retry can’t create a duplicate payment request - Send
amountas the pre-tax value; adding GST or subtracting TDS yourself will fail the tax amount validation - Treat the
Invalid gstAmount/Invalid tdsAmountmessages as authoritative — they carry the server-computed figure, so you can correct and resubmit directly - Read the organization’s submission policy requirements once and include those tags on every request, instead of discovering them through
MISSING_REQUIRED_FIELDfailures - Store the returned
paymentRequestIdand track approval progress via Payment Request Details
Was this page helpful?