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

> Add a GSTIN or PAN to an existing 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`

Adds a tax identifier to a vendor that already exists. The identifier being added is selected by the `type` query parameter — a **GSTIN** or a **PAN** — and its value is sent in the body.

A vendor can hold **multiple GSTINs** (one per state), as long as every GSTIN belongs to the same PAN. A **PAN** on the other hand is a single-value fallback for vendors without GST registration: it can only be added while the vendor has no GSTIN and no PAN at all.

Use the [Vendor Details API](/apis/vendor-details) to check what the vendor currently holds before calling. To change a value that already exists, use the [Vendor Taxation Update API](/apis/vendor-taxation-update) instead — this endpoint never overwrites an existing identifier.

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

### Body Parameters

| Parameter | Type   | Required | Constraints                                        | Description                                                            |
| --------- | ------ | -------- | -------------------------------------------------- | ---------------------------------------------------------------------- |
| `value`   | string | Yes      | Non-empty; must be a valid GSTIN or PAN per `type` | The tax identifier to add. 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 value is rejected as an invalid format.
</Note>

### Adding 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`                |
| `value` is a valid GSTIN format                                                                         | `400 INVALID_GST_NUMBER`                                       |
| The GSTIN 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 this GSTIN                                             | `400 DUPLICATE_GSTIN` — `Duplicate GSTIN`                      |
| This vendor does not already hold this GSTIN                                                            | `400 DUPLICATE_GSTIN` — `GSTIN already exists for this vendor` |
| The new GSTIN carries the same PAN as the vendor's existing GSTIN / PAN                                 | `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

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.

### Adding a PAN

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

| Check                                                                                  | Rejected with                                                                             |
| -------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- |
| `value` is a valid PAN format — `^[A-Z]{5}[0-9]{4}[A-Z]{1}$`, for example `ABCDE1234F` | `400 INVALID_PAN_NUMBER`                                                                  |
| The PAN does not belong to your **own** business                                       | `400 VALIDATION_ERROR`                                                                    |
| The vendor has **no** GSTIN and **no** PAN on record                                   | `400 VALIDATION_ERROR` — `PAN can only be added when no GST or PAN exists for the vendor` |

The PAN is stored encrypted at rest.

<Note>
  There is no way to add a PAN alongside a GSTIN. For a GST-registered vendor the PAN is already implied by the GSTIN — Pazy derives it rather than storing it separately. Use `type=PAN` only for vendors that are not GST registered.
</Note>

### What Happens After the Write

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

* **Verification.** The value is checked against the GST portal (for GSTIN) or the PAN service (for PAN), and the result is returned as `isVerified` in the response. A value that cannot be verified is still saved — `isVerified` is simply `false`.
* **Audit trail.** A `CREATE` entry is recorded 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 new tax identifier is pushed to it.
* **MSME check.** A background MSME verification is queued — for GSTIN only when this is the vendor's first tax identifier, and always for PAN.

Verification and the audit trail are part of the request. The accounting sync and MSME check run after the record is committed and are best-effort: if either fails, the request still returns `200 OK` and the identifier remains saved.

## Code Examples

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

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

  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 = {"value": "29ABCDE1234F1Z5"}

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

Adding a PAN to a vendor with no GST registration:

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

## 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 added successfully` or `PAN added successfully`                                                                                                                                                                             |
| `data.isVerified` | boolean | `true` if the identifier 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 identifier is still saved. |

### Response Example — GSTIN Added

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

### Response Example — PAN Added, Not Verified

```json theme={null}
{
  "ok": true,
  "data": {
    "message": "PAN added 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"
  }
}
```

### Invalid PAN Format

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

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

### Duplicate GSTIN

Returned when another vendor in your organization already holds this GSTIN, 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 the new GSTIN'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"
  }
}
```

### PAN Not Allowed

Returned when a PAN is added to a vendor that already has a GSTIN or a PAN.

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

```json theme={null}
{
  "ok": false,
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "PAN can only be added when no GST or PAN exists for the vendor"
  }
}
```

### Tax Identifier Belongs to Your Own Business

Returned when the submitted GSTIN or PAN 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 `value`, or an unknown property.

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

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

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

### 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 identifier could not be persisted. The write is a single transaction, so nothing is saved 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 adding vendor taxation"
  }
}
```

## Best Practices

* Send GSTIN and PAN values in upper case — a lower-case value is rejected as an invalid format
* Use this endpoint to **add** an identifier and the [Vendor Taxation Update API](/apis/vendor-taxation-update) to **change** one; adding a value the vendor already holds is rejected rather than treated as an update
* Check the vendor's current GSTIN / PAN with the [Vendor Details API](/apis/vendor-details) first — it tells you whether to POST or PATCH
* For a multi-state vendor, add one GSTIN per state through repeated calls. All of them must share the same PAN
* Add a PAN only for vendors without GST registration, and add it before any GSTIN — once a GSTIN exists, PAN can no longer be added
* Treat `isVerified: false` as "saved but unconfirmed", not as a failure. Re-check the vendor later if verification matters to your workflow
* GST and PAN can also be set at creation time through the [Vendor Creation API](/apis/vendor-creation) — use this endpoint for vendors that are already on record
