Developer API

Build on GigRoot

A REST API secured with OAuth 2.0 client credentials. Start with invoicing and software licensing — more modules are on the way.

View

Pretty walk-through with formatted code blocks. raw .md

1. Leads API — create leads from external forms or apps

Push leads straight into your GigRoot CRM from a website contact form, landing page, or any external app. This endpoint is authenticated with a simple API key (no OAuth handshake) — create one under Settings → Privacy & Security → API Keys and send it in the x-api-key header.

Create a lead — POST /v1/leads

curl -X POST https://api.gigroot.co/v1/leads \
  -H "x-api-key: gr_mcp_xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Jane Doe",
    "email": "[email protected]",
    "title": "Website enquiry — e-commerce build",
    "phone": "+1 555 0100",
    "company": "Acme Co",
    "value": 5000,
    "source": "Website",
    "notes": "Wants a Shopify store",
    "customFields": { "budget_range": "5k-10k", "urgency": "high" }
  }'

Response:

{
  "success": true,
  "data": { "id": "...", "leadNumber": "LEAD#202606001", "name": "Jane Doe", "email": "[email protected]" }
}

Required: name, email, title. Any custom fields you've configured for Leads (CRM → Settings → Custom Fields) and marked "Accept via public API" can be sent inside the customFields object.

Discover the accepted fields — GET /v1/leads/schema returns the standard + custom fields for your agency:

curl https://api.gigroot.co/v1/leads/schema -H "x-api-key: gr_mcp_xxx"

Rate limited to 300 requests / 15 min per IP. The same API key can also drive the MCP server.

2. 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:

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:

{
  "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.

3. Idempotency

Every state-mutating POST — 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 (no duplicate invoice created, no double-charge recorded). Reuse with a different body returns409 Conflict — that's almost always a client bug. Keys are cached for 7 days.

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 response headers.

4. 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. Use the same OAuth credentials — the mode field is the toggle.

5. Pagination & rate limits

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.
{
  "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.

6. 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

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:

{
  "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>
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 GET /v1/webhooks/:id/deliveries.

Other operations: GET /v1/webhooks (list), PATCH /v1/webhooks/:id (events / isActive / description), DELETE /v1/webhooks/:id.

7. Invoicing API (GST + multi-currency)

List invoices — GET /v1/invoices (scope invoices:read)

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

Get one — GET /v1/invoices/:id

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. Set "isNonGst": true to raise a Bill of Supply in the non-GST series. taxRate: 0 no longer implies it, because zero-rated exports are tax invoices.

An invoice created through the API is a draft. It carries a DRAFT-… placeholder number until it's issued. Any of these issues it and assigns the real number from your series:POST /v1/invoices/:id/send, PUT with status: "sent", or a recorded payment. Issuing an invoice with GST details missing returns 400 with the reasons.

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,
    "discountMode": "before_tax",
    "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"
  }'

Indian GST support

  • clientGstin + clientState capture buyer identity. Seller GSTIN + state come from your agency billing settings (see /v1/agency-billing).
  • The server works out the place of supply (s.12 IGST Act): the state in the client's GSTIN for B2B; for B2C, the client's address, otherwise your own state. Pass placeOfSupply (and country for a foreign client) to set it yourself. It then 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, along with placeOfSupply, creditedAmount (total of credit notes against the invoice) and roundOff.
  • documentLabel comes from the document type: "Tax Invoice", or "Bill of Supply" for a non-GST invoice (isNonGst).
  • Per-line taxRate overrides the invoice-level rate for mixed-HSN invoices.
  • Discounts always come off the taxable value. discountMode: "after_tax" is accepted but treated as before tax.
  • supplyType (optional) accepts: b2b, b2c, export, export_with_payment, sez, sez_with_payment.
  • isReverseCharge: true marks the invoice as reverse-charge.

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:

{
  "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

Mutable fields: status (draft/sent/cancelled), notes, dueDate, externalRef, metadata. Items and amounts are immutable via the API — raise a credit note, or cancel and re-issue. A cancelled (void) invoice keeps its number. Invoices that already have a recorded payment cannot change status (use the payments endpoint instead).

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

Scope: invoices:write. Honors Idempotency-Key. Pushes a payment row, recalculates amountPaid, and flips the invoice to partially_paid or paid automatically. A payment on a draft issues it first. Payments on a void invoice are refused with 409.

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

curl -X POST https://api.gigroot.co/v1/invoices/INVOICE_ID/send \
  -H "Authorization: Bearer <access_token>" \
  -H "Content-Type: application/json" \
  -d '{ "to": "[email protected]", "cc": "[email protected]" }'

8. 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. Honors Idempotency-Key. If a contact with the same externalRef already exists we return it rather than creating a duplicate.

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": "[email protected]",
    "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"
  }'

Update — PATCH /v1/contacts/:id.

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

9. Credit Notes API

Credit notes under s.34 of the CGST Act against an issued invoice. They're also available from the invoice in the app.

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.

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"
  }'
  • invoiceId and reason are required. issueDate, notes, externalRef and metadata are optional.
  • amount is the reduction in taxable value. GST is added in the invoice's own proportion. Omit amount to credit everything left on the invoice.
  • Credit notes are numbered in their own series, sequential per financial year (e.g. CN/26-27/0001).
  • After 30 November following the invoice's financial year, a credit note is issued as a financial note without GST.
  • Returns 409 when the invoice is already fully credited, still a draft, or void.

10. Agency Billing Settings

Read — GET /v1/agency-billing

{
  "data": {
    "gstRegistered": true,
    "gstin": "27AAACA0000B1Z5",
    "businessState": "MH",
    "pan": "AAACA0000B",
    "legalName": "Your Agency Pvt Ltd",
    "contactEmail": "[email protected]",
    "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"]
  }
}

11. 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:

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

Response:

{
  "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.

Open playground