# GigRoot Developer API

A REST API secured with OAuth 2.0 client credentials. Currently exposes
**Invoicing** (with full Indian GST support), **Contacts**, **Credit
Notes**, **Webhooks** and **Agency Billing Settings**.

## On this page

1. Authentication (OAuth 2.0)
2. Idempotency
3. Sandbox / test mode
4. Pagination
5. Webhooks
6. Invoicing API
7. Contacts API
8. Credit Notes API
9. Agency Billing Settings
10. Licensing verification API

---

## 1. Authentication

Create an API client under **Settings → API Access** in your GigRoot
agency account. You'll get a `client_id` and a `client_secret` (shown
once).

Exchange them for a short-lived access token using the client-credentials
grant:

```bash
curl -X POST https://api.gigroot.co/oauth/token \
  -H "Content-Type: application/json" \
  -d '{
    "grant_type": "client_credentials",
    "client_id": "grc_xxx",
    "client_secret": "grcs_xxx",
    "scope": "invoices:read invoices:write"
  }'
```

Response:

```json
{
  "access_token": "eyJhbGciOi...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "scope": "invoices:read invoices:write"
}
```

Send the token as a Bearer header on every API call:

```
Authorization: Bearer <access_token>
```

Tokens expire after 1 hour — request a new one as needed.

Available scopes: `invoices:read`, `invoices:write`.

## 2. Idempotency

Every state-mutating POST (`/v1/invoices`, `/v1/invoices/:id/payments`,
`/v1/contacts`, `/v1/credit-notes`, `/v1/webhooks`) accepts an
`Idempotency-Key` header. Use a unique value (a uuid) per logical
operation. Subsequent retries with the SAME key replay the original
2xx response; reuse with a different body returns `409 Conflict`. Keys
are cached for 7 days.

```bash
curl -X POST https://api.gigroot.co/v1/invoices \
  -H "Authorization: Bearer <access_token>" \
  -H "Idempotency-Key: 6e8c64b1-d2a1-4f1a-9c41-e9d2d63a01ce" \
  -H "Content-Type: application/json" \
  -d '{ ... }'
```

A replayed response carries `Idempotency-Replay: true` in the headers.

## 3. Sandbox / test mode

Pass `"mode": "test"` on POST `/v1/invoices` (or `/v1/webhooks`) to
mark records as test data. Test invoices:

- are NOT included in your live admin UI or live reports
- emit `livemode: false` in webhook payloads

When unspecified, mode defaults to `live`.

## 4. Pagination

List endpoints accept two pagination modes:

- **Page-based** (back-compat): `?page=1&limit=50`.
- **Cursor-based** (recommended for reconciliation jobs): pass the
  `nextCursor` from the previous page's `pagination` block as
  `?cursor=2026-06-25T20%3A14%3A11.123Z`. Cursor pagination is stable
  even when new records are written between page requests.

```json
{
  "data": [ ... ],
  "pagination": {
    "page": 1,
    "limit": 50,
    "total": 217,
    "totalPages": 5,
    "nextCursor": "2026-06-24T11:02:55.413Z"
  }
}
```

Rate limit: 60 requests per minute per agency.

## 5. Webhooks

Subscribe to events to avoid polling.

**Available events:**

- `invoice.created`
- `invoice.sent`
- `invoice.paid`
- `invoice.cancelled`
- `payment.recorded`
- `credit_note.created`

### Register an endpoint — `POST /v1/webhooks`

```bash
curl -X POST https://api.gigroot.co/v1/webhooks \
  -H "Authorization: Bearer <access_token>" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://your-app.example.com/gigroot/webhooks",
    "events": ["invoice.paid", "credit_note.created"],
    "description": "Production hook",
    "mode": "live"
  }'
```

Response includes the signing `secret` exactly once:

```json
{
  "data": {
    "id": "...",
    "url": "https://your-app.example.com/gigroot/webhooks",
    "events": ["invoice.paid", "credit_note.created"],
    "isActive": true,
    "mode": "live",
    "secret": "whsec_4f1d2c..."
  }
}
```

### Verifying signatures

Every delivery carries `X-GigRoot-Signature: t=<unix>,v1=<hmac>` and
`X-GigRoot-Timestamp: <unix>`. Verify by HMAC-SHA256-ing
`${timestamp}.${rawBody}` with your `secret` and comparing in constant
time. Reject if the timestamp is more than 5 minutes old.

### Retries

Failed deliveries retry with exponential backoff up to 8 attempts
spanning ~12 hours. Inspect attempts via:

```bash
curl https://api.gigroot.co/v1/webhooks/<id>/deliveries \
  -H "Authorization: Bearer <access_token>"
```

### Other operations

- `GET /v1/webhooks` — list your endpoints.
- `PATCH /v1/webhooks/:id` — update `events` / `description` / `isActive`.
- `DELETE /v1/webhooks/:id` — remove an endpoint.

---

## 6. Invoicing API

### List invoices — `GET /v1/invoices`

Scope: `invoices:read`. Filters: `status`, `externalRef`, `mode`,
`dateFrom`, `dateTo`, `cursor`, `limit`.

```bash
curl "https://api.gigroot.co/v1/invoices?limit=20&status=paid&dateFrom=2026-04-01" \
  -H "Authorization: Bearer <access_token>"
```

### Get one — `GET /v1/invoices/:id`

```bash
curl https://api.gigroot.co/v1/invoices/INVOICE_ID \
  -H "Authorization: Bearer <access_token>"
```

### Create — `POST /v1/invoices`

Scope: `invoices:write`. Honors `Idempotency-Key`. Amounts are
computed server-side from items + tax rates + discounts.

```bash
curl -X POST https://api.gigroot.co/v1/invoices \
  -H "Authorization: Bearer <access_token>" \
  -H "Idempotency-Key: <uuid>" \
  -H "Content-Type: application/json" \
  -d '{
    "clientName": "Acme Corp",
    "clientGstin": "27AAACA1234B1Z5",
    "clientState": "MH",
    "currency": "INR",
    "taxRate": 18,
    "pricesIncludeTax": false,
    "items": [
      { "name": "Website build", "hsnCode": "998314", "quantity": 1, "rate": 50000 },
      { "name": "Annual hosting", "hsnCode": "998316", "quantity": 1, "rate": 12000, "taxRate": 18 }
    ],
    "charges": [
      { "label": "International processing fee", "amount": 250, "taxable": false }
    ],
    "supplyType": "b2b",
    "isReverseCharge": false,
    "externalRef": "your-order-id-123",
    "metadata": { "campaign": "Q3-Acme" },
    "dueDate": "2026-07-15",
    "mode": "live"
  }'
```

#### Drafts and numbering

An invoice created here is a draft with a `DRAFT-…` placeholder number.
`POST /v1/invoices/:id/send`, `PUT` with `status: "sent"`, or a recorded
payment issues it and assigns the real number, so the series has no
gaps. Issuing with GST details missing returns 400 with the reasons.

#### Indian GST support

- `clientGstin` + `clientState` capture buyer identity; the GSTIN is
  checked (format, state code, check digit). Seller GSTIN + state come
  from your agency billing settings (see `GET /v1/agency-billing`).
- The server works out the place of supply (s.12 IGST Act): the
  client's GSTIN state for B2B; for B2C the client's address, else your
  own state. Optional `placeOfSupply` and `country` set it yourself.
- It derives `taxSplit`:
  - **intra_state** → CGST + SGST (half-half).
  - **inter_state** → IGST.
  - **export** / **sez** → zero-rated under LUT;
    `export_with_payment` / `sez_with_payment` → IGST.
- `cgstAmount`, `sgstAmount`, `igstAmount` returned on every read, with
  `placeOfSupply`, `creditedAmount` and `roundOff`.
- `documentLabel` is `"Tax Invoice"` or `"Bill of Supply"`, from the
  document type. `"isNonGst": true` raises a Bill of Supply; `taxRate: 0`
  no longer implies it (zero-rated exports are tax invoices).
- Per-line `taxRate` overrides the invoice-level rate for mixed-HSN
  invoices.
- `supplyType` accepts `b2b` | `b2c` | `export` |
  `export_with_payment` | `sez` | `sez_with_payment`.
- `isReverseCharge: true` marks the invoice as reverse-charge.
- Discounts always reduce the taxable value; `discountMode: "after_tax"`
  is treated as before tax.
- Payments on a void invoice return 409; a payment on a draft issues it.

#### Multi-currency

Supported currencies: `INR`, `USD`, `EUR`, `GBP`, `AED`, `SGD`, `BRL`,
`CAD`, `AUD`, `JPY`.

For non-INR invoices we snapshot the FX rate at issue time:

```json
{
  "currency": "USD",
  "fxRate": 83.42,
  "inrEquivalent": 41710.00,
  "fxRateSource": { "provider": "exchangerate.host", "ratedAt": "2026-06-26T08:14:01.000Z" }
}
```

The rate is locked for the lifetime of the invoice — payment-time edits
don't change it.

#### Tax-inclusive prices

Set `"pricesIncludeTax": true` and treat each `items[].rate` as
gross-inclusive. The server back-computes the taxable base.

### Update — `PUT /v1/invoices/:id`

Scope: `invoices:write`. Mutable fields: `status` (`draft` / `sent` /
`cancelled`), `notes`, `dueDate`, `externalRef`, `metadata`. Items and
amounts are immutable via the API — cancel and re-issue if you need
to change them. Invoices that already have a recorded payment cannot
change status (use the payments endpoint instead).

```bash
curl -X PUT https://api.gigroot.co/v1/invoices/INVOICE_ID \
  -H "Authorization: Bearer <access_token>" \
  -H "Content-Type: application/json" \
  -d '{ "status": "sent", "externalRef": "your-order-id-123" }'
```

### Record a payment — `POST /v1/invoices/:id/payments`

Scope: `invoices:write`. Honors `Idempotency-Key`. Pushes a payment
row, recalculates `amountPaid`, and flips the invoice to
`partially_paid` or `paid` automatically.

```bash
curl -X POST https://api.gigroot.co/v1/invoices/INVOICE_ID/payments \
  -H "Authorization: Bearer <access_token>" \
  -H "Idempotency-Key: <uuid>" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": 50000,
    "method": "bank_transfer",
    "reference": "UTR-2026-04812",
    "date": "2026-06-15"
  }'
```

### Send by email — `POST /v1/invoices/:id/send`

Scope: `invoices:write`. Uses your agency's branding. When `to` is
omitted we fall back to the linked contact's email.

```bash
curl -X POST https://api.gigroot.co/v1/invoices/INVOICE_ID/send \
  -H "Authorization: Bearer <access_token>" \
  -H "Content-Type: application/json" \
  -d '{ "to": "billing@acme.example", "cc": "ap@acme.example" }'
```

---

## 7. Contacts API

Scope: same as invoices (`invoices:read` / `invoices:write`).

### List — `GET /v1/contacts`

Filters: `type`, `search`, `externalRef`, `page`, `limit`.

### Get one — `GET /v1/contacts/:id`

### Create — `POST /v1/contacts`

```bash
curl -X POST https://api.gigroot.co/v1/contacts \
  -H "Authorization: Bearer <access_token>" \
  -H "Idempotency-Key: <uuid>" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Acme Corp",
    "email": "billing@acme.example",
    "phone": "+91-9876543210",
    "type": "client",
    "company": "Acme Corp Pvt Ltd",
    "gstin": "27AAACA1234B1Z5",
    "state": "MH",
    "country": "India",
    "address": "123 Marine Drive, Mumbai 400001",
    "externalRef": "your-customer-id-456"
  }'
```

If a contact with the same `externalRef` already exists we return it
rather than creating a duplicate.

### Update — `PATCH /v1/contacts/:id`

```bash
curl -X PATCH https://api.gigroot.co/v1/contacts/CONTACT_ID \
  -H "Authorization: Bearer <access_token>" \
  -H "Content-Type: application/json" \
  -d '{ "gstin": "27AAACA1234B1Z5" }'
```

---

## 8. Credit Notes API

GST-compliant credit notes against a previously-issued invoice.

### List — `GET /v1/credit-notes`

Filters: `invoiceId`, `externalRef`, `page`, `limit`.

### Get one — `GET /v1/credit-notes/:id`

### Create — `POST /v1/credit-notes`

Honors `Idempotency-Key`.

```bash
curl -X POST https://api.gigroot.co/v1/credit-notes \
  -H "Authorization: Bearer <access_token>" \
  -H "Idempotency-Key: <uuid>" \
  -H "Content-Type: application/json" \
  -d '{
    "invoiceId": "INVOICE_ID",
    "amount": 3000,
    "reason": "Partial refund — service downtime credit",
    "externalRef": "your-refund-id-789"
  }'
```

Credit notes under s.34 CGST Act. `invoiceId` and `reason` are
required. `amount` is the reduction in taxable value; GST is added in
the invoice's own proportion. Omit it to credit everything left.
Numbered in their own series, sequential per financial year
(CN/26-27/0001). After 30 November following the invoice's financial
year, a credit note is issued as a financial note without GST. 409 when
the invoice is already fully credited, a draft, or void.

---

## 9. Agency Billing Settings

### Read — `GET /v1/agency-billing`

```json
{
  "data": {
    "gstRegistered": true,
    "gstin": "27AAACA0000B1Z5",
    "businessState": "MH",
    "pan": "AAACA0000B",
    "legalName": "Your Agency Pvt Ltd",
    "contactEmail": "billing@youragency.com",
    "taxRates": [
      { "name": "GST 5%", "rate": 5, "isDefault": false },
      { "name": "GST 18%", "rate": 18, "isDefault": true }
    ],
    "discountBeforeTax": false,
    "supportedCurrencies": ["INR", "USD", "EUR", "GBP", "AED", "SGD", "BRL", "CAD", "AUD", "JPY"]
  }
}
```

---

## 10. Licensing verification API

If you issue software licenses through GigRoot, your deployed product
verifies its license with a single public call — no OAuth token needed,
just the license key embedded in your app:

```bash
curl "https://api.gigroot.co/licensing/verify?key=lic_xxxxxxxx"
```

Response:

```json
{
  "valid": true,
  "status": "active",
  "plan": "annual",
  "product": { "name": "Acme ERP" },
  "expiresAt": "2027-06-09T00:00:00.000Z",
  "daysLeft": 187,
  "banner": { "show": false, "message": "", "severity": "info" },
  "payUrl": "https://app.gigroot.co/pay/<token>"
}
```

When `banner.show` is true, display the message in your product and link
the "Pay Now" button to `payUrl`. After the customer pays, the license
auto-extends and the next `verify` call returns `valid: true` again.

---

## Test it live

Paste your credentials and run real calls in the browser playground:
https://gigroot.co/developers/playground
