> ## 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 Taxation Update API

> Correct a GSTIN or PAN already recorded against a vendor

## Authentication

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`

Replaces a tax identifier the vendor already holds. The identifier being changed is selected by the `type` query parameter — a **GSTIN** or a **PAN** — and the body carries both the current value (`oldValue`) and its replacement (`newValue`).

`oldValue` is not optional bookkeeping: it identifies **which** record to update. A vendor can hold several GSTINs, so the value you are replacing is how Pazy picks the row. For PAN, `oldValue` acts as a confirmation check against the stored PAN. In both cases a mismatch rejects the request.

This endpoint edits in place — the identifier keeps its record, so anything already linked to it stays linked. To add a new identifier instead, use the [Vendor Taxation Creation API](/apis/vendor-taxation-creation).

### Path Parameters

| Parameter  | Type   | Required | Constraints      | Description                            |
| ---------- | ------ | -------- | ---------------- | -------------------------------------- |
| `vendorId` | string | Yes      | 1-100 characters | Slug / unique identifier of the vendor |

### Query Parameters

| Parameter | Type   | Required | Constraints      | Description                           |
| --------- | ------ | -------- | ---------------- | ------------------------------------- |
| `type`    | string | Yes      | `GSTIN` or `PAN` | Which tax identifier is being updated |

### Body Parameters

| Parameter  | Type   | Required | Constraints                                           | Description                                                                    |
| ---------- | ------ | -------- | ----------------------------------------------------- | ------------------------------------------------------------------------------ |
| `oldValue` | string | Yes      | Non-empty; must match a value currently on the vendor | The tax identifier being replaced. Leading and trailing whitespace is trimmed. |
| `newValue` | string | Yes      | Non-empty; must be a valid GSTIN or PAN per `type`    | The replacement value. Leading and trailing whitespace is trimmed.             |

Unknown properties are rejected with `400 VALIDATION_ERROR` — they are not silently ignored.

<Note>
  Values are matched **case-sensitively against upper-case formats**. Send `29ABCDE1234F1Z5`, not `29abcde1234f1z5` — a lower-case `newValue` is rejected as an invalid format, and a lower-case `oldValue` will not match the stored record.
</Note>

### Updating a GSTIN

`type=GSTIN` applies the following checks, in order. The first one that fails rejects the request and nothing is written.

| Check                                                                                                    | Rejected with                                                       |
| -------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------- |
| Vendor exists in your organization                                                                       | `404 VENDOR_NOT_FOUND`                                              |
| Caller is an admin / bookkeeper, or the vendor's owner                                                   | `403 ACCESS_DENIED`                                                 |
| Vendor state is `ACTIVE` or `APPROVAL_PENDING`                                                           | `400 VALIDATION_ERROR` — `Vendor is not active`                     |
| `newValue` is a valid GSTIN format                                                                       | `400 INVALID_GST_NUMBER`                                            |
| `newValue` does not belong to your **own** business (it is not built on one of your organization's PANs) | `400 VALIDATION_ERROR`                                              |
| No other vendor in the organization already uses `newValue`                                              | `400 DUPLICATE_GSTIN` — `Duplicate GSTIN`                           |
| This vendor does not already hold `newValue`                                                             | `400 DUPLICATE_GSTIN` — `GSTIN already exists for this vendor`      |
| `newValue` carries the same PAN as the vendor's existing GSTIN / PAN                                     | `400 VALIDATION_ERROR`                                              |
| `oldValue` matches a GSTIN currently on the vendor                                                       | `404 VENDOR_NOT_FOUND` — `Existing GSTIN not found for this vendor` |
| The GSTIN being replaced is not used by any invoice or vendor advance                                    | `400 VALIDATION_ERROR`                                              |

Accepted GSTIN formats:

* The standard 15-character GSTIN — `^[0-9]{2}[A-Z]{5}[0-9]{4}[A-Z]{1}[1-9A-Z]{1}[CDSZ][0-9A-Z]{1}$`, for example `29ABCDE1234F1Z5`
* Any 15-character value beginning with `99`, used for UIN-style registrations

<Warning>
  A GSTIN that has already been used on an invoice or a vendor advance **cannot be edited** — those transactions carry the tax identity they were filed under, and rewriting it would change historical records. Add the correct GSTIN with the [Vendor Taxation Creation API](/apis/vendor-taxation-creation) instead, and use it going forward.
</Warning>

The cross-vendor duplicate check can be relaxed by an organization configuration. When your organization is set up to allow duplicate vendors based on GSTIN, `Duplicate GSTIN` is not raised for a GSTIN held by another vendor — the per-vendor check (`GSTIN already exists for this vendor`) always applies regardless.

### Updating a PAN

`type=PAN` applies the same vendor, access, and state checks, then:

| Check                                                                                     | Rejected with                                                               |
| ----------------------------------------------------------------------------------------- | --------------------------------------------------------------------------- |
| `newValue` is a valid PAN format — `^[A-Z]{5}[0-9]{4}[A-Z]{1}$`, for example `ABCDE1234F` | `400 INVALID_PAN_NUMBER`                                                    |
| `newValue` does not belong to your **own** business                                       | `400 VALIDATION_ERROR`                                                      |
| The vendor does **not** have a GSTIN                                                      | `400 VALIDATION_ERROR` — `PAN cannot be edited when vendor has a GSTIN`     |
| The vendor has a PAN on record                                                            | `404 VENDOR_NOT_FOUND` — `Existing PAN not found for this vendor`           |
| `oldValue` equals the vendor's current PAN                                                | `400 VALIDATION_ERROR` — `oldValue does not match the vendor's current PAN` |

The PAN is stored encrypted at rest, and there is no transaction-usage restriction on editing it — unlike GSTIN, a PAN can be corrected even after the vendor has been transacted with.

<Note>
  Once a vendor has a GSTIN, its PAN is derived from that GSTIN and can no longer be edited on its own. Correct the GSTIN instead.
</Note>

### What Happens After the Write

Once the change is committed, Pazy runs a few follow-up steps:

* **Verification.** `newValue` is checked against the GST portal (for GSTIN) or the PAN service (for PAN), and the result is returned as `isVerified` in the response — and stored on the record, replacing the previous verification state. A value that cannot be verified is still saved.
* **Audit trail.** An `UPDATE` entry recording both the before and after values is written against the vendor with source `API`, visible through the [Vendor Audit Trail API](/apis/vendor-audit-trail).
* **Accounting sync.** If an accounting integration is connected, the changed tax identifier is pushed to it.

Verification and the audit trail are part of the request. The accounting sync runs after the record is committed and is best-effort: if it fails, the request still returns `200 OK` and the new value remains saved.

## Code Examples

<CodeGroup>
  ```bash cURL theme={null}
  curl -X PATCH "https://api.pazy.io/v1.0/vendor/<vendorId>/taxation?type=GSTIN" \
    -H "Authorization: Api-Key YOUR_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "oldValue": "29ABCDE1234F1Z5",
      "newValue": "27ABCDE1234F1Z2"
    }'
  ```

  ```javascript JavaScript (Fetch API) theme={null}
  const response = await fetch(
    'https://api.pazy.io/v1.0/vendor/<vendorId>/taxation?type=GSTIN',
    {
      method: 'PATCH',
      headers: {
        'Authorization': 'Api-Key YOUR_API_KEY',
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        oldValue: '29ABCDE1234F1Z5',
        newValue: '27ABCDE1234F1Z2'
      })
    }
  );

  const result = await response.json();
  ```

  ```python Python (requests) theme={null}
  import requests

  url = "https://api.pazy.io/v1.0/vendor/<vendorId>/taxation"
  headers = {
      "Authorization": "Api-Key YOUR_API_KEY",
      "Content-Type": "application/json"
  }
  params = {"type": "GSTIN"}
  payload = {
      "oldValue": "29ABCDE1234F1Z5",
      "newValue": "27ABCDE1234F1Z2"
  }

  response = requests.patch(url, headers=headers, params=params, json=payload)
  result = response.json()
  ```
</CodeGroup>

Correcting the PAN of a vendor with no GST registration:

```bash cURL theme={null}
curl -X PATCH "https://api.pazy.io/v1.0/vendor/<vendorId>/taxation?type=PAN" \
  -H "Authorization: Api-Key YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "oldValue": "ABCDE1234F",
    "newValue": "ABCDE5678G"
  }'
```

## Success Response

**HTTP Status:** `200 OK`

**Response Fields:**

| Field             | Type    | Description                                                                                                                                                                                                                                                 |
| ----------------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `ok`              | boolean | Indicates whether the request was successful                                                                                                                                                                                                                |
| `data`            | object  | Result of the operation                                                                                                                                                                                                                                     |
| `data.message`    | string  | Human-readable confirmation — `GSTIN updated successfully` or `PAN updated successfully`                                                                                                                                                                    |
| `data.isVerified` | boolean | `true` if `newValue` was verified with the source authority. For GSTIN this means the registration is active on the GST portal; for PAN it means the PAN was confirmed. `false` when verification failed or was unavailable — the new value is still saved. |

### Response Example — GSTIN Updated

```json theme={null}
{
  "ok": true,
  "data": {
    "message": "GSTIN updated successfully",
    "isVerified": true
  }
}
```

### Response Example — PAN Updated, Not Verified

```json theme={null}
{
  "ok": true,
  "data": {
    "message": "PAN updated successfully",
    "isVerified": false
  }
}
```

## Error Responses

### Invalid GSTIN Format

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

```json theme={null}
{
  "ok": false,
  "error": {
    "code": "INVALID_GST_NUMBER",
    "message": "Invalid GST format for new value"
  }
}
```

### Invalid PAN Format

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

```json theme={null}
{
  "ok": false,
  "error": {
    "code": "INVALID_PAN_NUMBER",
    "message": "Invalid PAN format for new value"
  }
}
```

### GSTIN Is in Use

Returned when the GSTIN being replaced is already referenced by an invoice or a vendor advance.

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

```json theme={null}
{
  "ok": false,
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "The current GSTIN is being used by invoice or vendor advance and cannot be edited"
  }
}
```

### Duplicate GSTIN

Returned when another vendor in your organization already holds `newValue`, or when this vendor already holds it.

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

```json theme={null}
{
  "ok": false,
  "error": {
    "code": "DUPLICATE_GSTIN",
    "message": "Duplicate GSTIN"
  }
}
```

```json theme={null}
{
  "ok": false,
  "error": {
    "code": "DUPLICATE_GSTIN",
    "message": "GSTIN already exists for this vendor"
  }
}
```

### PAN Mismatch

Returned when `newValue`'s embedded PAN differs from the PAN of the identifiers the vendor already holds. A single vendor cannot span two PANs — create a separate vendor for the other legal entity.

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

```json theme={null}
{
  "ok": false,
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "New GSTIN must have the same PAN as the vendor's existing GSTIN/PAN"
  }
}
```

### `oldValue` Does Not Match

For `type=GSTIN`, the vendor holds no GSTIN equal to `oldValue`:

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

```json theme={null}
{
  "ok": false,
  "error": {
    "code": "VENDOR_NOT_FOUND",
    "message": "Existing GSTIN not found for this vendor"
  }
}
```

For `type=PAN`, the vendor has no PAN at all:

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

```json theme={null}
{
  "ok": false,
  "error": {
    "code": "VENDOR_NOT_FOUND",
    "message": "Existing PAN not found for this vendor"
  }
}
```

For `type=PAN`, the vendor has a PAN but it is not the one you sent:

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

```json theme={null}
{
  "ok": false,
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "oldValue does not match the vendor's current PAN"
  }
}
```

### PAN Cannot Be Edited

Returned when a PAN update is attempted on a vendor that has a GSTIN.

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

```json theme={null}
{
  "ok": false,
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "PAN cannot be edited when vendor has a GSTIN"
  }
}
```

### Tax Identifier Belongs to Your Own Business

Returned when `newValue` resolves to one of your own organization's PANs.

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

```json theme={null}
{
  "ok": false,
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "This GSTIN belongs to your own business, not the vendor's. Please enter the vendor's GSTIN."
  }
}
```

### Vendor Not Active

Returned when the vendor is in any state other than `ACTIVE` or `APPROVAL_PENDING` — for example a disabled vendor.

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

```json theme={null}
{
  "ok": false,
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Vendor is not active"
  }
}
```

### Validation Error

Returned before any processing when the request does not match the schema — a missing or unsupported `type`, a missing or empty `oldValue` / `newValue`, or an unknown property.

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

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

```json theme={null}
{
  "ok": false,
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Validation failed: /type: must be equal to one of the allowed values"
  }
}
```

### Vendor Not Found

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

```json theme={null}
{
  "ok": false,
  "error": {
    "code": "VENDOR_NOT_FOUND",
    "message": "Vendor not found"
  }
}
```

### Access Denied

Returned when the caller is neither an admin / bookkeeper nor the vendor's owner.

**HTTP Status:** `403 Forbidden`

```json theme={null}
{
  "ok": false,
  "error": {
    "code": "ACCESS_DENIED",
    "message": "Access denied: You can only update vendors you own"
  }
}
```

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

The API key must carry vendor update permission.

**HTTP Status:** `403 Forbidden`

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

### Internal Error

Returned when the change could not be persisted. The write is a single transaction, so the old value is left intact and the call is safe to retry.

**HTTP Status:** `500 Internal Server Error`

```json theme={null}
{
  "ok": false,
  "error": {
    "code": "INTERNAL_ERROR",
    "message": "Error while updating vendor taxation"
  }
}
```

## Best Practices

* Fetch the current GSTIN / PAN with the [Vendor Details API](/apis/vendor-details) and send it verbatim as `oldValue` — it selects the record to change, and a stale value fails the request
* Send both values in upper case — a lower-case `newValue` is rejected as an invalid format, and a lower-case `oldValue` will not match
* Use this endpoint to **correct a mistake**, not to onboard a new registration. To add another GSTIN, use the [Vendor Taxation Creation API](/apis/vendor-taxation-creation)
* Expect GSTIN edits to be blocked once the vendor has been transacted with — check for existing invoices and advances before planning a correction, and add a new GSTIN instead when the old one is in use
* The PAN of a GST-registered vendor is not editable. Correct the GSTIN and the PAN follows from it
* Treat `isVerified: false` as "saved but unconfirmed", not as a failure. Note that a successful update **replaces** the stored verification state, so re-verifying a previously verified identifier with an unreachable service leaves the record unverified
* Review changes afterwards through the [Vendor Audit Trail API](/apis/vendor-audit-trail) — each update records both the old and the new value
