Developer API
A REST API secured with OAuth 2.0 client credentials. Start with invoicing and software licensing — more modules are on the way.
Pretty walk-through with formatted code blocks. raw .md
On this page
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.
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.
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.
Pass "mode": "test" on POST /v1/invoices (or /v1/webhooks) to mark records as test data. Test invoices:
livemode: false in webhook payloadsWhen unspecified, mode defaults to live. Use the same OAuth credentials — the mode field is the toggle.
List endpoints accept two pagination modes:
?page=1&limit=50. 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.
Subscribe to events to avoid polling. Available events:
invoice.createdinvoice.sentinvoice.paidinvoice.cancelledpayment.recordedcredit_note.createdRegister 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.
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"
}'clientGstin + clientState capture buyer identity. Seller GSTIN + state come from your agency billing settings (see /v1/agency-billing).placeOfSupply (and country for a foreign client) to set it yourself. It then derives taxSplit:• intra_state → CGST + SGST (half-half)export_with_payment / sez_with_payment → IGSTcgstAmount, 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).taxRate overrides the invoice-level rate for mixed-HSN invoices.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.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.
Set "pricesIncludeTax": true and treat each items[].rate as gross-inclusive. The server back-computes the taxable base.
PUT /v1/invoices/:idMutable 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" }'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"
}'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]" }'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" }'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.CN/26-27/0001).409 when the invoice is already fully credited, still a draft, or void.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"]
}
}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.