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

# Vendor Advance Creation API

> Raise a vendor advance payment request, optionally linked to a purchase order

## Authentication

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
```

The permission required on your API key is **Payment Request** with the **Create** action enabled.

<Warning>
  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](#error-responses).
</Warning>

<Info>
  `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.
</Info>

## 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](/apis/vendor-creation) or [Vendor List](/apis/vendor-list) APIs |
| `gstId`               | integer | No       | Organization GST tax id from the [GST Taxes](/apis/taxes-gst) 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](/apis/taxes-tds) 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](#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](#tags-object)                                                                                                |
| `orgEntity`           | integer | No       | Organization entity (GSTIN entity) id, as returned by the [Company Entities](/apis/company-entities) API. Defaults to the organization's first entity                |

<Warning>
  Unknown body properties are rejected rather than ignored — sending any field not listed above returns a `400 VALIDATION_ERROR`.
</Warning>

### 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 (`PERCENTAGE` or `ABSOLUTE`) 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`.

Fetch the ids and their rates from the [TDS Taxes](/apis/taxes-tds) and [GST Taxes](/apis/taxes-gst) APIs, compute the expected amounts from the returned `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
```

The round-off follows the organization's configured rounding behaviour. The resulting net payable is what [Payment Request Details](/apis/payment-request-details) returns as `amount`, with your original value available there as `tax.subTotal`.

### Purchase Order Linking

When `purchaseOrderLinkId` 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.

<Info>
  To discover the available tag slugs, their types, and (for `DROPDOWN` tags) the valid value ids, see the tag APIs: the [Tag List API](/apis/tag-list), and [Tag Detail API](/apis/tag-detail).
</Info>

```json theme={null}
"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 with `MISSING_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 `PENDING` state and the approval workflow starts, notifying approvers.
* Policy has no approval stages → the request is created already `APPROVED`.

## Code Examples

<CodeGroup>
  ```bash cURL theme={null}
  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" }
      }
    }'
  ```

  ```javascript JavaScript (Fetch API) theme={null}
  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();
  ```

  ```python Python (requests) theme={null}
  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()
  ```
</CodeGroup>

### Minimal Request

Only `amount` and `vendorId` are required — taxes, currency, PO link and tags are all optional (subject to the organization's submission policy):

```json theme={null}
{
  "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](/apis/payment-request-details) API |
| `data.policyUsed`       | string  | Name of the approval policy that was applied                                                                        |

### Response Example

```json theme={null}
{
  "ok": true,
  "data": {
    "paymentRequestId": "<paymentRequestId>",
    "policyUsed": "Procurement Default Policy"
  }
}
```

<Info>
  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.
</Info>

## Error Responses

### Validation Errors

**HTTP Status:** `400 Bad Request`

Schema-level failures:

```json theme={null}
{
  "ok": false,
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Validation failed: vendorId: must have required property 'vendorId'"
  }
}
```

```json theme={null}
{
  "ok": false,
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Validation failed: /amount: must be > 0"
  }
}
```

```json theme={null}
{
  "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:

```json theme={null}
{
  "ok": false,
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "gstId and gstAmount must be provided together"
  }
}
```

```json theme={null}
{
  "ok": false,
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "tdsId and tdsAmount must be provided together"
  }
}
```

An id that doesn't resolve to a tax of the right type for this organization:

```json theme={null}
{
  "ok": false,
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Invalid gstId"
  }
}
```

```json theme={null}
{
  "ok": false,
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Invalid tdsId"
  }
}
```

An amount that disagrees with the rate configured against the id:

```json theme={null}
{
  "ok": false,
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Invalid gstAmount: computed GST is 18000 but 17500 was given"
  }
}
```

```json theme={null}
{
  "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`

```json theme={null}
{
  "ok": false,
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Invalid currency: USD"
  }
}
```

```json theme={null}
{
  "ok": false,
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Invalid org entity id given."
  }
}
```

### Vendor Not Found

**HTTP Status:** `404 Not Found`

```json theme={null}
{
  "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               |

```json theme={null}
{
  "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:

```json theme={null}
{
  "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:

```json theme={null}
{
  "ok": false,
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "No valid payer could be resolved from the applicable procurement policy"
  }
}
```

```json theme={null}
{
  "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:

```json theme={null}
{
  "ok": false,
  "error": {
    "code": "ACCESS_DENIED",
    "message": "Access denied: only admins and bookkeepers can create vendor advances"
  }
}
```

Vendor advances aren't enabled for the organization:

```json theme={null}
{
  "ok": false,
  "error": {
    "code": "ACCESS_DENIED",
    "message": "Vendor advances are not enabled for this organization"
  }
}
```

### Duplicate Idempotency Key

If the same `x-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`

```json theme={null}
{
  "error": "Duplicate request: idempotency key already used"
}
```

An `x-idempotency-key` that is present but empty, over 256 bytes, or contains control characters:

**HTTP Status:** `400 Bad Request`

```json theme={null}
{
  "error": "Invalid x-idempotency-key header"
}
```

### Authentication Errors

**HTTP Status:** `401 Unauthorized`

```json theme={null}
{
  "ok": false,
  "error": {
    "code": "MISSING_CREDENTIALS",
    "message": "Missing Credentials"
  }
}
```

```json theme={null}
{
  "ok": false,
  "error": {
    "code": "INVALID_API_KEY",
    "message": "Invalid API Key"
  }
}
```

### Permission Errors

**HTTP Status:** `403 Forbidden`

```json theme={null}
{
  "ok": false,
  "error": {
    "code": "INSUFFICIENT_PERMISSIONS",
    "message": "Permission check failed - PERMISSION_CHECK_FAILED"
  }
}
```

## Best Practices

* Read the tax ids and rates from [TDS Taxes](/apis/taxes-tds) and [GST Taxes](/apis/taxes-gst) 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-key` per advance so a timeout retry can't create a duplicate payment request
* Send `amount` as the **pre-tax** value; adding GST or subtracting TDS yourself will fail the tax amount validation
* Treat the `Invalid gstAmount`/`Invalid tdsAmount` messages 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_FIELD` failures
* Store the returned `paymentRequestId` and track approval progress via [Payment Request Details](/apis/payment-request-details)
