openapi: 3.0.3
info:
title: 'Dukanam Mobile API'
description: 'Interactive Dukanam API reference for authentication, billing, POS, inventory, GST, compliance, exports, team access, testimonials and private feedback.'
version: 1.0.0
servers:
- url: 'https://dukanam.com'
tags:
- name: Authentication
description: "\nPrimary WhatsApp OTP authentication for mobile clients."
- name: Businesses
description: ''
- name: 'Business onboarding'
description: "\nComplete the resumable setup required after registering a new business. Until onboarding finishes,\nclients may resubmit a completed required step to correct saved data without moving the current step backwards."
- name: 'GST verification'
description: "\nVerify an Indian GSTIN through the configured Masters India taxpayer service.\nThe endpoint is available during onboarding so a merchant can use the verified\nlegal name and registered address before saving their business tax profile."
- name: Contacts
description: "\nLoading customers and suppliers from a spreadsheet, which is how a shop moves its party list\noff whatever it used before. A file is uploaded, checked, and reviewed before anything is\nwritten; parties appear only when the import is committed, and then all of them at once."
- name: 'Khata ledger'
description: ''
- name: Inventory
description: "\nSuppliers of an item: the terms agreed with each (rate, lead time, minimum order, their own\nitem code, preferred flag) beside what purchase invoices show was actually billed."
- name: 'Sales invoices'
description: "\nSuccessful sales create purchase-date warranties from each covered item's policy, including\nnon-serialised goods. Invoice responses expose warranties and warranty_delivery. Automatic\nprimary-customer email includes invoice and warranty PDFs when mail and the recipient are available.\nIssued policies are snapshots; item edits do not change existing coverage. Returns reduce\ncovered quantities, voiding cancels cover, and coverage corrections supersede old slips."
- name: 'Business documents'
description: ''
- name: Payments
description: ''
- name: Expenses
description: "\nExpense records are a core Purchases capability on every plan. Workspace purchase permissions still apply."
- name: POS
description: "\nPOS and cash-register endpoints remain available while onboarding is on the `first_work` step so a shop can complete its first sale. Other business endpoints continue to return `onboarding_incomplete` until setup finishes."
- name: 'Cash register'
description: "\nCash-register and POS endpoints remain available while onboarding is on the `first_work` step so a shop can complete its first POS sale. Other business endpoints continue to return `onboarding_incomplete` until setup finishes."
- name: Reports
description: ''
- name: 'Business compliance guidance'
description: ''
- name: 'GST compliance'
description: ''
- name: Billing
description: ''
- name: Referrals
description: "\nRefer other shops and earn free subscription months. Referrals belong to the signed-in\naccount, not to one workspace, so these routes are not business-scoped. The server owns\neligibility, reward calculation and the free-period schedule; clients display what it returns."
- name: 'Referral program administration'
description: ''
- name: 'Data exports'
description: "\nAn export too large to finish inside a request is generated on the queue. The export endpoint\nanswers **202** with the record below; poll it until `status` is `completed`, then fetch\n`download_url`. Generated files are a convenience copy of data the workspace already owns and\nare deleted once they expire."
- name: 'Team access'
description: "\nOwners and administrators can manage workspace members. Smart Books supports one accountant seat in addition to the owner. Business supports all documented roles, with five total seats including the owner and pending invitations."
- name: 'Team invitations'
description: ''
- name: Testimonials
description: ''
- name: 'Testimonial moderation'
description: ''
- name: Feedback
description: ''
- name: 'Feedback moderation'
description: ''
- name: 'Apple subscriptions'
description: "\nApple App Store subscription verification and authoritative Dukanam entitlement state.\nOwner endpoints require the business's `owner_user_id` to match the signed-in user,\nas with web billing and API onboarding. A pivot role alone does not confer ownership."
- name: 'Account deletion'
description: "\nRequest, inspect and cancel deletion of the signed-in account and its data, as\nrequired by the Google Play user data policy. The public web equivalent of these\nendpoints is published at /delete-account."
- name: 'App link analytics'
description: "\nOpens of the promoted pages: the https://dukanam.com/app download link and the store-type\npages (the /billing-software-for-retail-stores index and one page per store type, in every\nlanguage). /app redirects phones to their store, so its opens are read from the request;\nstore pages may be served from the CDN cache and report their own opens with a beacon,\nadding reading time, scroll depth, screen size and the first call to action used.\nLocation is Cloudflare's IP-based estimate (city level, approximate). An open is one human\nvisit; link previews and crawlers are counted separately.\nDates are calendar days in Asia/Kolkata. Requires a super-admin token."
- name: 'Billing webhooks'
description: ''
- name: 'Business details'
description: "\nThe card a shop shows a customer so they can pay it by direct bank transfer:\nthe registered name and GSTIN, plus one bank account in full.\n\nThe full account number, IFSC and branch are stored encrypted on the payment\naccount. The payment-accounts listing carries only the last four digits.\nFull details are available here and in receiving_bank_account on business\nresources for accounting members. This card endpoint is served no-store."
- name: 'ChatGPT plugin'
description: ''
- name: 'Invoice-linked expenses'
description: "\nAdvanced Plan feature `invoice_costs`. Owner/admin writes; accountant reads. Every sales\nor purchase cost is an operating expense, recorded once in Expenses with an immutable\ninvoice reference and description. Purchase costs do not increase inventory/COGS. Sales\ncosts reduce party/worker contribution. Unpaid costs accrue a payable; settlement never\ncreates a second expense. Costs remain after invoice returns/voids until explicitly voided."
- name: 'Invoice branding'
description: "\nHow this workspace's bills look: one of three ready-made templates or a custom\nlayout, any accent colour, a logo, a signature, closing lines, and an optional UPI QR.\n\nBranding is frozen onto every invoice and business document at record time. Changing\nit here changes what future documents print as and leaves every saved document\nexactly as it was issued, which is what a reprint two years from now has to show."
- name: 'Loans and EMI'
description: "\nBorrowings the shop carries — a vehicle loan, a working-capital loan, a credit card — and the\nrepayments made against them. Each loan owns a liability account in the existing chart of\naccounts, so it appears on the balance sheet as a liability rather than a negative asset, and\nevery EMI records a split journal: the principal reduces what is owed, the interest is an expense.\n\nPlan feature `loans_and_income`; permission `accounting`. Online-only — nothing here belongs in\nthe offline cache or the write queue."
- name: Lookups
description: "\nSearch tenant-scoped values for lookup controls. Results are limited to the active business and the caller's workspace permissions and plan features."
- name: 'Master partner teams'
description: "\nAuthenticated partner-portal session cookies are required (not a workspace bearer\ntoken). Sign in at /partners/login with WhatsApp OTP. Mutating requests also require\nthe session CSRF token in X-CSRF-TOKEN or X-XSRF-TOKEN. Only active masters may use\nthese routes. Team data is scoped to that master; foreign IDs return 404. Amounts\nare integer paise in responses; reward requests use rupees and percent."
- name: 'Partner portal'
description: "\nOwn-account access for individual partners, masters and team members. Requires a\npartner session from /partners/login (WhatsApp OTP), not a workspace bearer token.\nWrites require the session CSRF token. Responses are private and not cached.\nTeam members see their master's eligible programs and their own rewards/payments."
- name: 'Partner program administration'
description: "\nRequires a super-admin token. Named programs supplement the existing default shop\nreferral program; global enablement, payout minimum and TDS still apply."
- name: 'Party and referrer profitability'
description: "\nAdvanced Plan exports require the corresponding profitability feature, accounting and\nexports permissions, and data_export. PDF, Excel and CSV contain every matching row,\nindependently of screen pagination. Dates, calculations and tenant checks match the\non-screen reports. Excel/CSV money is numeric rupees; PDF displays INR."
- name: 'Party pricing'
description: "\nRate cards that decide what a party pays. A contact carries one card and, optionally, a\nblanket discount; an invoice line for an item then takes its rate from the card unless the\nrequest quotes a rate of its own. See the Contacts endpoints for attaching a card to a party."
- name: 'Payment accounts'
description: ''
- name: 'Platform broadcasts'
description: "\nSuper admins push an announcement, optionally with a banner image, to the shops that match\na set of workspace filters: where they trade, what they sell, and what they pay for."
- name: 'Platform business insights'
description: ''
- name: 'Platform overview'
description: "\nThe numbers behind the admin console's overview, one section per call. Requires a\nsuper-admin token. Money is in paise and excludes GST: plan prices include 18% GST, so\nthe taxable part is price × 10000 / 11800. Days are calendar days in Asia/Kolkata.\nAn active business had someone use the web app, open the mobile app, or create records\nthat day; request-level tracking started on 27 September 2026 and earlier days are\nreconstructed from created records. Invoices are sales invoices except voided ones."
- name: 'Push testing'
description: "\nUnauthenticated bench for firing a push at a single device token, so mobile work can be\nverified without a signed-in user or a scheduled reminder behind it.\n\nEnabled by default in all environments, including production, for temporary testing.\nSet `FIREBASE_TEST_ENDPOINT_ENABLED=false` to disable after testing. This endpoint is public."
- name: 'Referrers and referral commissions'
description: "\nReferrers recommend the shop and bring customers; link them to sales to track performance and commissions.\nThe UI calls these people Referrers. Existing /workers URLs and worker_* request/response keys are retained for compatibility.\n\nAdvanced Plan feature `referral_workers`. Owners/administrators manage referrers, assign\ncommissions and make payments. Accountants can read profiles and statements. All amounts\nare integer paise; percentage rates use basis points (1000 means 10%). Commission is earned\non a saved invoice's discounted, tax-exclusive value. Customer dues remain separate."
- name: 'Usage reports'
description: ''
components:
securitySchemes:
default:
type: http
scheme: bearer
description: 'WhatsApp OTP is the primary auth flow. The documentation helper above uses the legacy password fallback and automatically copies the returned `data.token` into every authenticated endpoint.'
partnerSession:
type: apiKey
in: cookie
name: dukanam-session
description: 'Partner portal session obtained through WhatsApp OTP at /partners/login. Writes also require the session CSRF token.'
security:
-
default: []
paths:
/api/v1/auth/otp/request:
post:
summary: 'Request a WhatsApp verification code.'
operationId: requestAWhatsAppVerificationCode
description: "The response is intentionally identical for an existing account and a\nnew phone number, so callers cannot use this endpoint to discover users.\nDuring the resend cooldown, supply your existing challenge_id to resume\nthat same pending code without sending another message. Keep it private."
parameters: []
responses:
202:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'If this number can receive WhatsApp, a verification code has been sent.'
challenge_id: 00000000-0000-4000-8000-000000000001
expires_in_seconds: 600
resend_in_seconds: 60
properties:
message:
type: string
example: 'If this number can receive WhatsApp, a verification code has been sent.'
challenge_id:
type: string
example: 00000000-0000-4000-8000-000000000001
description: 'Opaque identifier required to verify the code.'
expires_in_seconds:
type: integer
example: 600
description: 'Remaining whole seconds before this code expires, rounded down.'
resend_in_seconds:
type: integer
example: 60
description: 'Remaining whole seconds in the resend cooldown; decreases when resuming an existing challenge.'
422:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'The given data was invalid.'
errors:
phone:
- 'Enter a valid 10-digit Indian mobile number.'
properties:
message:
type: string
example: 'The given data was invalid.'
errors:
type: object
properties:
phone:
type: array
example:
- 'Enter a valid 10-digit Indian mobile number.'
items:
type: string
429:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'Please wait before requesting another verification code.'
properties:
message:
type: string
example: 'Please wait before requesting another verification code.'
503:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'WhatsApp verification is not available right now. Please use email and password instead.'
properties:
message:
type: string
example: 'WhatsApp verification is not available right now. Please use email and password instead.'
tags:
- Authentication
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
phone:
type: string
description: 'An Indian mobile number in local or E.164 form.'
example: '9876543210'
challenge_id:
type: string
description: 'Optional opaque ID from an earlier request for the same phone. A valid pending challenge is reused during cooldown; other callers remain rate limited.'
example: 00000000-0000-4000-8000-000000000001
nullable: true
required:
- phone
security: []
/api/v1/auth/otp/verify:
post:
summary: 'Verify a WhatsApp code and sign in to the matching account.'
operationId: verifyAWhatsAppCodeAndSignInToTheMatchingAccount
description: "After a valid OTP, an existing verified identity takes precedence. Otherwise,\na unique normalized user.phone match signs in immediately without email,\npassword, or account linking. Its verified identity is saved automatically\nif it has none; an existing different verified identity is preserved.\nBusiness and customer contact numbers are never used to select an account.\nMultiple matching user accounts return 422 with errors.code and no token\nor registration proof; offer password sign-in instead of choosing an account.\nOnly an unknown phone receives a short-lived, one-time registration proof.\nOpen account setup directly; do not show an account-linking step. Keep the\nproof secret and never put it in a URL."
parameters: []
responses:
200:
description: ''
content:
application/json:
schema:
oneOf:
-
description: ''
type: object
example:
data:
user:
id: 1
name: 'Shop Owner'
email: owner@example.com
phone: null
token: 1|example-mobile-token
default_business_id: 1
properties:
data:
type: object
properties:
user:
type: object
properties:
id:
type: integer
example: 1
name:
type: string
example: 'Shop Owner'
email:
type: string
example: owner@example.com
phone:
type: string
example: null
nullable: true
description: 'Present for an existing verified identity or unique normalized user.phone match.'
token:
type: string
example: 1|example-mobile-token
description: 'Sanctum bearer token, present for an existing account.'
default_business_id:
type: integer
example: 1
description: 'Last selected eligible business, otherwise the first eligible business by name; null if none. Present after sign-in.'
-
description: ''
type: object
example:
data:
requires_registration: true
registration_proof: one-time-registration-proof
properties:
data:
type: object
properties:
requires_registration:
type: boolean
example: true
description: 'Present and true only when the verified phone matches no account. Open account setup directly.'
registration_proof:
type: string
example: one-time-registration-proof
description: 'One-time secret for account setup; also accepted by the legacy enrollment endpoint.'
422:
description: ''
content:
application/json:
schema:
oneOf:
-
description: ''
type: object
example:
message: 'The given data was invalid.'
errors:
code:
- 'The verification code is invalid or has expired.'
properties:
message:
type: string
example: 'The given data was invalid.'
errors:
type: object
properties:
code:
type: array
example:
- 'The verification code is invalid or has expired.'
items:
type: string
-
description: 'Ambiguous saved phone'
type: object
example:
message: 'The given data was invalid.'
errors:
code:
- 'This number is saved on more than one account. Sign in with email and password to continue.'
properties:
message:
type: string
example: 'The given data was invalid.'
errors:
type: object
properties:
code:
type: array
example:
- 'This number is saved on more than one account. Sign in with email and password to continue.'
items:
type: string
503:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'WhatsApp verification is not available right now. Please use email and password instead.'
properties:
message:
type: string
example: 'WhatsApp verification is not available right now. Please use email and password instead.'
tags:
- Authentication
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
challenge_id:
type: string
description: 'The opaque challenge ID from the request response.'
example: 00000000-0000-4000-8000-000000000001
code:
type: string
description: 'The six-digit WhatsApp code.'
example: '123456'
device_name:
type: string
description: 'A name for the device token.'
example: "Priya's phone"
fcm_token:
type: string
description: 'Optional Firebase Cloud Messaging registration token to save after successful sign-in.'
example: eY2x9_example_fcm_registration_token
nullable: true
required:
- challenge_id
- code
- device_name
security: []
/api/v1/auth/otp/register:
post:
summary: 'Create an account after WhatsApp verification.'
operationId: createAnAccountAfterWhatsAppVerification
description: "The proof is single-use and expires quickly. A recovery password is\nrequired for sensitive actions, while WhatsApp remains the primary sign-in\nroute."
parameters: []
responses:
201:
description: ''
content:
application/json:
schema:
type: object
example:
data:
user:
id: 1
name: 'Priya Rao'
email: priya@example.com
phone: null
business:
id: 1
name: 'Priya Textiles'
role: owner
is_owner: true
token: 1|example-mobile-token
properties:
data:
type: object
properties:
user:
type: object
properties:
id:
type: integer
example: 1
name:
type: string
example: 'Priya Rao'
email:
type: string
example: priya@example.com
phone:
type: string
example: null
nullable: true
business:
type: object
properties:
id:
type: integer
example: 1
name:
type: string
example: 'Priya Textiles'
role:
type: string
example: owner
is_owner:
type: boolean
example: true
description: "Always true for the newly registered account's workspace."
token:
type: string
example: 1|example-mobile-token
422:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'The given data was invalid.'
errors:
registration_proof:
- 'Your verified phone session has expired. Request a new WhatsApp code.'
properties:
message:
type: string
example: 'The given data was invalid.'
errors:
type: object
properties:
registration_proof:
type: array
example:
- 'Your verified phone session has expired. Request a new WhatsApp code.'
items:
type: string
503:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'WhatsApp verification is not available right now. Please use email and password instead.'
properties:
message:
type: string
example: 'WhatsApp verification is not available right now. Please use email and password instead.'
tags:
- Authentication
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
challenge_id:
type: string
description: 'The verified challenge ID.'
example: 00000000-0000-4000-8000-000000000001
registration_proof:
type: string
description: 'One-time secret returned by OTP verification.'
example: one-time-registration-proof
name:
type: string
description: "The account owner's name."
example: 'Priya Rao'
business_name:
type: string
description: 'The first workspace name.'
example: 'Priya Textiles'
email:
type: string
description: 'Account and recovery email address.'
example: priya@example.com
password:
type: string
description: 'A recovery password, at least eight characters.'
example: SecurePassword123!
device_name:
type: string
description: 'A name for the device token.'
example: "Priya's phone"
fcm_token:
type: string
description: 'Optional Firebase Cloud Messaging registration token to save for the new user.'
example: eY2x9_example_fcm_registration_token
nullable: true
referral_code:
type: string
description: "Optional referral code from a shared link's `ref` parameter: a customer's referral code or an influencer partner's link code. An unknown code is rejected with 422 while the referral program is on, and ignored while it is paused."
example: K7M2QX9A
password_confirmation:
type: string
description: 'Must match password.'
example: SecurePassword123!
acquisition:
type: object
description: 'Optional first-touch attribution captured by the mobile client. Stored only at signup; referral records take precedence.'
example:
utm_source: google
utm_medium: organic
utm_campaign: launch
referrer_host: www.google.com
properties:
utm_source:
type: string
description: 'Optional campaign source, max 255 characters.'
example: google
utm_medium:
type: string
description: 'Optional campaign medium, max 255 characters.'
example: organic
utm_campaign:
type: string
description: 'Optional campaign name, max 255 characters.'
example: launch
referrer_host:
type: string
description: 'Optional hostname only, max 253 characters; no URL path or query.'
example: www.google.com
required:
- challenge_id
- registration_proof
- name
- business_name
- email
- password
- device_name
- password_confirmation
security: []
/api/v1/auth/otp/link:
post:
summary: 'Link a newly verified WhatsApp number to an existing password account (legacy compatibility).'
operationId: linkANewlyVerifiedWhatsAppNumberToAnExistingPasswordAccountlegacyCompatibility
description: "Clients must first obtain the same short-lived registration proof used\nfor new account creation. The email/password check makes enrollment an\nexplicit migration path instead of trusting mutable historical contact\nphone fields."
parameters: []
responses:
200:
description: ''
content:
application/json:
schema:
type: object
example:
data:
user:
id: 1
name: 'Shop Owner'
email: owner@example.com
phone: null
token: 1|example-mobile-token
default_business_id: 1
properties:
data:
type: object
properties:
user:
type: object
properties:
id:
type: integer
example: 1
name:
type: string
example: 'Shop Owner'
email:
type: string
example: owner@example.com
phone:
type: string
example: null
nullable: true
token:
type: string
example: 1|example-mobile-token
default_business_id:
type: integer
example: 1
description: 'Last selected eligible business, otherwise the first eligible business by name; null if none.'
422:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'The given data was invalid.'
errors:
registration_proof:
- 'Your verified phone session has expired. Request a new WhatsApp code.'
properties:
message:
type: string
example: 'The given data was invalid.'
errors:
type: object
properties:
registration_proof:
type: array
example:
- 'Your verified phone session has expired. Request a new WhatsApp code.'
items:
type: string
503:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'WhatsApp verification is not available right now. Please use email and password instead.'
properties:
message:
type: string
example: 'WhatsApp verification is not available right now. Please use email and password instead.'
tags:
- Authentication
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
challenge_id:
type: string
description: 'The verified challenge ID.'
example: 00000000-0000-4000-8000-000000000001
registration_proof:
type: string
description: 'One-time secret returned by OTP verification.'
example: one-time-registration-proof
email:
type: string
description: 'Existing Dukanam account email address.'
example: owner@example.com
password:
type: string
description: 'Existing account password.'
example: SecurePassword123!
device_name:
type: string
description: 'A name for the device token.'
example: "Priya's phone"
fcm_token:
type: string
description: 'Optional Firebase Cloud Messaging registration token to save after linking.'
example: eY2x9_example_fcm_registration_token
nullable: true
required:
- challenge_id
- registration_proof
- email
- password
- device_name
security: []
/api/v1/auth/register:
post:
summary: 'Register an account and its first workspace.'
operationId: registerAnAccountAndItsFirstWorkspace
description: "The returned business includes `role: owner` and `is_owner: true` immediately,\nwithout a follow-up workspace request. Ownership is determined by `owner_user_id`."
parameters: []
responses:
201:
description: ''
content:
application/json:
schema:
type: object
example:
data:
user:
id: 1
name: 'Shop Owner'
email: owner@example.com
business:
id: 1
name: 'Owner Shop'
role: owner
is_owner: true
token: 1|example-mobile-token
properties:
data:
type: object
properties:
user:
type: object
properties:
id:
type: integer
example: 1
name:
type: string
example: 'Shop Owner'
email:
type: string
example: owner@example.com
business:
type: object
properties:
id:
type: integer
example: 1
name:
type: string
example: 'Owner Shop'
role:
type: string
example: owner
is_owner:
type: boolean
example: true
description: "Always true for the newly registered account's workspace."
token:
type: string
example: 1|example-mobile-token
tags:
- Authentication
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
name:
type: string
description: 'Must not be greater than 128 characters.'
example: b
email:
type: string
description: 'Must be a valid email address. Must not be greater than 255 characters.'
example: zbailey@example.net
phone:
type: string
description: 'Must not be greater than 32 characters.'
example: i
nullable: true
business_name:
type: string
description: 'Must not be greater than 128 characters.'
example: 'y'
password:
type: string
description: 'Must be at least 8 characters.'
example: pBNvYg
referral_code:
type: string
description: "Optional referral code from a shared link's `ref` parameter: a customer's referral code or an influencer partner's link code. An unknown code is rejected with 422 while the referral program is on, and ignored while it is paused."
example: K7M2QX9A
nullable: true
acquisition:
type: object
description: 'Optional first-touch attribution captured by the mobile client. Stored only at signup; referral records take precedence.'
example:
utm_source: google
utm_medium: organic
utm_campaign: launch
referrer_host: www.google.com
properties:
utm_source:
type: string
description: 'Optional campaign source, max 255 characters.'
example: google
utm_medium:
type: string
description: 'Optional campaign medium, max 255 characters.'
example: organic
utm_campaign:
type: string
description: 'Optional campaign name, max 255 characters.'
example: launch
referrer_host:
type: string
description: 'Optional hostname only, max 253 characters; no URL path or query.'
example: www.google.com
device_name:
type: string
description: 'A name for the device token.'
example: 'Scribe API Docs'
fcm_token:
type: string
description: 'An optional Firebase Cloud Messaging registration token to save for the user.'
example: eY2x9_example_fcm_registration_token
required:
- name
- email
- business_name
- password
- device_name
security: []
/api/v1/auth/login:
post:
summary: 'Sign in with the password fallback and return the default business.'
operationId: signInWithThePasswordFallbackAndReturnTheDefaultBusiness
description: ''
parameters: []
responses:
200:
description: ''
content:
application/json:
schema:
type: object
example:
data:
user:
id: 1
name: 'Shop Owner'
email: owner@example.com
phone: null
token: 1|example-mobile-token
default_business_id: 1
properties:
data:
type: object
properties:
user:
type: object
properties:
id:
type: integer
example: 1
name:
type: string
example: 'Shop Owner'
email:
type: string
example: owner@example.com
phone:
type: string
example: null
nullable: true
token:
type: string
example: 1|example-mobile-token
default_business_id:
type: integer
example: 1
description: 'Last selected eligible business, otherwise the first eligible business by name; null if none.'
tags:
- Authentication
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
email:
type: string
description: 'Must be a valid email address.'
example: gbailey@example.net
password:
type: string
description: ''
example: '|]|{+-'
remember:
type: boolean
description: ''
example: false
fcm_token:
type: string
description: 'Must not be greater than 4096 characters.'
example: v
nullable: true
device_name:
type: string
description: 'A name for the device token.'
example: 'Scribe API Docs'
required:
- email
- password
- device_name
security: []
/api/v1/auth/forgot-password:
post:
summary: 'Email a password reset link.'
operationId: emailAPasswordResetLink
description: 'Always returns the same response so callers cannot discover registered email addresses.'
parameters: []
responses:
200:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'If an account exists for that email address, a password reset link has been sent.'
properties:
message:
type: string
example: 'If an account exists for that email address, a password reset link has been sent.'
tags:
- Authentication
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
email:
type: string
description: 'The account email address.'
example: owner@example.com
required:
- email
security: []
/api/v1/auth/reset-password:
post:
summary: 'Reset an account password.'
operationId: resetAnAccountPassword
description: 'A successful reset revokes every mobile bearer token and sends a security notification.'
parameters: []
responses:
200:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'Password reset successfully. Sign in with your new password.'
properties:
message:
type: string
example: 'Password reset successfully. Sign in with your new password.'
422:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'The given data was invalid.'
errors:
email:
- 'This password reset token is invalid.'
properties:
message:
type: string
example: 'The given data was invalid.'
errors:
type: object
properties:
email:
type: array
example:
- 'This password reset token is invalid.'
items:
type: string
tags:
- Authentication
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
token:
type: string
description: 'The token delivered in the password-reset email.'
example: reset-token
email:
type: string
description: 'The account email address.'
example: owner@example.com
password:
type: string
description: 'The new password, at least eight characters.'
example: new-secure-password
password_confirmation:
type: string
description: 'Must match password.'
example: new-secure-password
required:
- token
- email
- password
- password_confirmation
security: []
/api/v1/auth/me:
get:
summary: 'Return the current user and active workspaces available under each plan and seat assignment.'
operationId: returnTheCurrentUserAndActiveWorkspacesAvailableUnderEachPlanAndSeatAssignment
description: ''
parameters: []
responses:
200:
description: ''
content:
application/json:
schema:
type: object
example:
data:
user:
id: 1
name: 'Shop Owner'
email: owner@example.com
phone: null
businesses:
-
id: 1
name: 'My Shop'
role: owner
is_owner: true
default_business_id: 1
workspaces:
limit: 1
used: 1
remaining: 0
can_create: false
properties:
data:
type: object
properties:
user:
type: object
properties:
id:
type: integer
example: 1
name:
type: string
example: 'Shop Owner'
email:
type: string
example: owner@example.com
phone:
type: string
example: null
nullable: true
businesses:
type: array
example:
- id: 1
name: 'My Shop'
role: owner
is_owner: true
items:
type: object
properties:
id:
type: integer
example: 1
name:
type: string
example: 'My Shop'
role:
type: string
example: owner
is_owner:
type: boolean
example: true
default_business_id:
type: integer
example: 1
description: 'Last selected business if still accessible, otherwise the first eligible business by name; null when none is available.'
workspaces:
type: object
properties:
limit:
type: integer
example: 1
description: 'Workspaces the current plan covers. 0 means unlimited.'
used:
type: integer
example: 1
description: 'Active workspaces the account already owns. Workspaces it merely belongs to are not counted.'
remaining:
type: integer
example: 0
description: 'Workspaces still available, or null when the limit is unlimited.'
can_create:
type: boolean
example: false
description: 'Whether POST /api/v1/businesses would succeed rather than return workspace_limit_reached.'
description: "How many workspaces the account's plan covers and how many it already owns. A plan covers every workspace its owner holds, so this allowance is account-wide rather than per workspace."
tags:
- Authentication
/api/v1/auth/fcm-token:
patch:
summary: "Update or clear the current user's Firebase Cloud Messaging registration token."
operationId: updateOrClearTheCurrentUsersFirebaseCloudMessagingRegistrationToken
description: 'Send `null` to remove the currently stored token.'
parameters: []
responses:
200:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'FCM token updated.'
properties:
message:
type: string
example: 'FCM token updated.'
tags:
- Authentication
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
fcm_token:
type: string
description: 'The Firebase Cloud Messaging registration token, or null to clear it.'
example: eY2x9_example_fcm_registration_token
nullable: true
required:
- fcm_token
/api/v1/auth/token:
delete:
summary: ''
operationId: deleteApiV1AuthToken
description: ''
parameters: []
responses: {}
tags:
- Authentication
/api/v1/auth/tokens:
delete:
summary: ''
operationId: deleteApiV1AuthTokens
description: ''
parameters: []
responses: {}
tags:
- Authentication
/api/v1/businesses:
get:
summary: "List the active workspaces available under the caller's current plan and seat assignment."
operationId: listTheActiveWorkspacesAvailableUnderTheCallersCurrentPlanAndSeatAssignment
description: "Ended paid subscriptions automatically fall back to Free Essentials. The ended subscription remains in history.\nBusiness resources include `invoice_allowance` (limit, used, remaining, can_create, resets_at) and\nowner-only `free_transition` (subscription_id, reason, requires_acknowledgement; null otherwise).\nWhen acknowledgement is required, offer paid plans and Continue on Free via POST billing/continue-free.\nKeep viewing, downloading, and exporting existing invoices available throughout this flow.\n`is_active` describes workspace access, independently of paid subscription status."
parameters: []
responses:
401:
description: ''
content:
application/json:
schema:
type: object
example:
message: Unauthenticated.
properties:
message:
type: string
example: Unauthenticated.
tags:
- Businesses
post:
summary: 'Create a business for the signed-in account.'
operationId: createABusinessForTheSignedInAccount
description: "Any authenticated account can create its first or an additional business,\neven without an accessible workspace or while another workspace is in setup.\nThe caller becomes its owner. Creation uses the same setup service as web\nregistration: an active Free subscription, Walk-in Customer, and default\npayment accounts are created atomically. Existing memberships and the saved\ndefault business are unchanged; use the selection endpoint to change it.\nContinue guided onboarding from the language step using the returned ID.\nSupply contact phone, store type, and tax details through onboarding.\nDuplicate names are allowed and receive distinct slugs. Each successful\nrequest creates a new business; this endpoint is not idempotent.\n\nSend `skip_setup` to create a workspace that is immediately usable, with\nonboarding already marked complete and every step recorded as completed.\nNothing is guessed: only the name is set, and GST, address, and store\ndetails are entered later through the settings endpoint. This is what the\nweb account menu does, because an account adding a second workspace has\nalready been through guided setup once. Omit it to keep the guided flow."
parameters: []
responses:
201:
description: ''
content:
application/json:
schema:
type: object
example:
data:
id: 2
name: 'Priya Textiles'
slug: priya-textiles
role: owner
is_owner: true
onboarding:
current_step: language
completed_steps: []
skipped_steps: []
completed_at: null
subscription:
status: active
billing_interval: monthly
provider: manual
plan:
id: 1
name: Free
slug: free
properties:
data:
type: object
properties:
id:
type: integer
example: 2
name:
type: string
example: 'Priya Textiles'
slug:
type: string
example: priya-textiles
role:
type: string
example: owner
is_owner:
type: boolean
example: true
description: 'Always true; ownership is assigned to the authenticated account.'
onboarding:
type: object
properties:
current_step:
type: string
example: language
description: 'language for a guided business, or complete when skip_setup was sent.'
completed_steps:
type: array
example: []
skipped_steps:
type: array
example: []
completed_at:
type: string
example: null
nullable: true
subscription:
type: object
properties:
status:
type: string
example: active
billing_interval:
type: string
example: monthly
provider:
type: string
example: manual
plan:
type: object
properties:
id:
type: integer
example: 1
name:
type: string
example: Free
slug:
type: string
example: free
description: 'The standard business resource, including the new business ID, ownership, subscription, and onboarding state.'
401:
description: ''
content:
application/json:
schema:
type: object
example:
message: Unauthenticated.
properties:
message:
type: string
example: Unauthenticated.
409:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'Your plan covers 1 workspace.'
code: workspace_limit_reached
limit: 1
used: 1
properties:
message:
type: string
example: 'Your plan covers 1 workspace.'
code:
type: string
example: workspace_limit_reached
limit:
type: integer
example: 1
used:
type: integer
example: 1
422:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'The name field is required.'
errors:
name:
- 'The name field is required.'
properties:
message:
type: string
example: 'The name field is required.'
errors:
type: object
properties:
name:
type: array
example:
- 'The name field is required.'
items:
type: string
tags:
- Businesses
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
name:
type: string
description: 'Business name, up to 128 characters.'
example: 'Priya Textiles'
skip_setup:
type: boolean
description: 'Create the workspace ready to use instead of at the start of guided onboarding. Transactional endpoints work immediately. Defaults to false.'
example: true
required:
- name
'/api/v1/businesses/{business}/select':
post:
summary: 'Select the business to open by default on the next sign-in.'
operationId: selectTheBusinessToOpenByDefaultOnTheNextSignIn
description: "Only an active, plan-eligible membership can be selected. The preference\nis shared with web sign-in; existing browser sessions retain their current\nworkspace. API requests always use the explicit business ID in their URL.\nSelection is allowed before onboarding completes so clients can switch\nout of an unfinished workspace. Follow data.business.onboarding afterward."
parameters: []
responses:
200:
description: ''
content:
application/json:
schema:
type: object
example:
data:
default_business_id: 1
business:
id: 1
name: 'My Shop'
role: owner
is_owner: true
onboarding:
current_step: complete
properties:
data:
type: object
properties:
default_business_id:
type: integer
example: 1
description: 'The selected business to open on the next sign-in.'
business:
type: object
properties:
id:
type: integer
example: 1
name:
type: string
example: 'My Shop'
role:
type: string
example: owner
is_owner:
type: boolean
example: true
onboarding:
type: object
properties:
current_step:
type: string
example: complete
description: "The selected business, including the caller's role and onboarding state."
401:
description: ''
content:
application/json:
schema:
type: object
example:
message: Unauthenticated.
properties:
message:
type: string
example: Unauthenticated.
403:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'Your role requires an eligible team or accountant plan.'
properties:
message:
type: string
example: 'Your role requires an eligible team or accountant plan.'
404:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'Not Found'
properties:
message:
type: string
example: 'Not Found'
tags:
- Businesses
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
'/api/v1/businesses/{business}':
get:
summary: 'Show the workspace and its current subscription.'
operationId: showTheWorkspaceAndItsCurrentSubscription
description: "Ended paid subscriptions automatically fall back to Free Essentials. The ended subscription remains in history.\nFree features and limits apply on both web and API; `is_active` describes workspace access.\nScheduled cancellations retain paid access until `ends_at`. A renewal due date alone does not end access."
parameters: []
responses:
401:
description: ''
content:
application/json:
schema:
type: object
example:
message: Unauthenticated.
properties:
message:
type: string
example: Unauthenticated.
tags:
- Businesses
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
'/api/v1/businesses/{business}/settings':
patch:
summary: 'Update business settings.'
operationId: updateBusinessSettings
description: "The `default_locale` and manual-sharing template fields may only be changed by workspace owners\nand admins. Manual sharing itself is available on every plan.\n\nGSTIN is required for a regular or composition registration, and its first two digits must match `state_code`. An unregistered business has its GSTIN cleared. `state_code` must be an Indian state or union territory (96, 97 and 99 are refused), and `default_place_of_supply` refuses 99."
parameters: []
responses:
200:
description: ''
content:
application/json:
schema:
type: object
example:
data:
id: 1
name: 'Anika Stores'
slug: anika-stores
role: owner
phone: '9876543210'
email: anika@example.com
legal_name: 'Anika Stores Private Limited'
gstin: 27AAPFU0939F1ZV
gst_registration_type: regular
currency: INR
timezone: Asia/Kolkata
address:
line_1: '12 Market Road'
line_2: null
city: Pune
state_code: '27'
pincode: '411001'
default_place_of_supply: '27'
invoice_prefix: INV
prices_include_tax: true
upi_id: anikastores@bank
theme: '#2563EB'
default_locale: hi
is_active: true
manual_sharing:
enabled: true
invoice_share_message_template: 'Hello {customer_name}, invoice {invoice_number} for {invoice_total} is ready.'
subscription:
status: active
billing_interval: monthly
trial_ends_at: null
renews_at: '2026-09-19T00:00:00.000000Z'
ends_at: null
cancelled_at: null
plan:
id: 2
name: Smart
slug: smart
features:
- inventory
- pos
- expenses
limits: []
receiving_bank_account:
id: 2
institution: 'HDFC Bank'
account_name: 'Anika Stores'
account_number: '001234567890'
ifsc: HDFC0000123
branch: Pune
show_on_invoice: true
theme_colours:
label: 'Custom colour'
brand: '#2159D4'
brand_dark: '#19439F'
soft: '#E5ECFD'
properties:
data:
type: object
properties:
id:
type: integer
example: 1
name:
type: string
example: 'Anika Stores'
slug:
type: string
example: anika-stores
role:
type: string
example: owner
phone:
type: string
example: '9876543210'
email:
type: string
example: anika@example.com
legal_name:
type: string
example: 'Anika Stores Private Limited'
gstin:
type: string
example: 27AAPFU0939F1ZV
gst_registration_type:
type: string
example: regular
currency:
type: string
example: INR
timezone:
type: string
example: Asia/Kolkata
address:
type: object
properties:
line_1:
type: string
example: '12 Market Road'
line_2:
type: string
example: null
nullable: true
city:
type: string
example: Pune
state_code:
type: string
example: '27'
pincode:
type: string
example: '411001'
default_place_of_supply:
type: string
example: '27'
invoice_prefix:
type: string
example: INV
prices_include_tax:
type: boolean
example: true
upi_id:
type: string
example: anikastores@bank
theme:
type: string
example: '#2563EB'
description: 'Saved preset key or canonical custom #RRGGBB, independently per workspace.'
default_locale:
type: string
example: hi
is_active:
type: boolean
example: true
manual_sharing:
type: object
properties:
enabled:
type: boolean
example: true
invoice_share_message_template:
type: string
example: 'Hello {customer_name}, invoice {invoice_number} for {invoice_total} is ready.'
subscription:
type: object
properties:
status:
type: string
example: active
billing_interval:
type: string
example: monthly
trial_ends_at:
type: string
example: null
nullable: true
renews_at:
type: string
example: '2026-09-19T00:00:00.000000Z'
ends_at:
type: string
example: null
nullable: true
cancelled_at:
type: string
example: null
nullable: true
plan:
type: object
properties:
id:
type: integer
example: 2
name:
type: string
example: Smart
slug:
type: string
example: smart
features:
type: array
example:
- inventory
- pos
- expenses
items:
type: string
limits:
type: array
example: []
receiving_bank_account:
type: object
properties:
id:
type: integer
example: 2
institution:
type: string
example: 'HDFC Bank'
account_name:
type: string
example: 'Anika Stores'
account_number:
type: string
example: '001234567890'
ifsc:
type: string
example: HDFC0000123
branch:
type: string
example: Pune
show_on_invoice:
type: boolean
example: true
description: 'Current receiving bank, including full account details. Only returned to members with accounting permission. Details are encrypted at rest and omitted from audit snapshots.'
theme_colours:
type: object
properties:
label:
type: string
example: 'Custom colour'
brand:
type: string
example: '#2159D4'
brand_dark:
type: string
example: '#19439F'
soft:
type: string
example: '#E5ECFD'
description: 'Effective label, brand, brand_dark and soft tokens. Custom colours generate readable shades for white and tinted backgrounds; use these tokens instead of raw theme hex for interface text and buttons.'
tags:
- Businesses
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
name:
type: string
description: 'Must not be greater than 128 characters.'
example: b
legal_name:
type: string
description: 'Must not be greater than 191 characters.'
example: 'n'
nullable: true
phone:
type: string
description: 'Must not be greater than 32 characters.'
example: g
nullable: true
email:
type: string
description: 'Must be a valid email address. Must not be greater than 255 characters.'
example: rowan.gulgowski@example.com
nullable: true
gst_registration_type:
type: string
description: ''
example: unregistered
enum:
- unregistered
- regular
- composition
gstin:
type: string
description: '15-character GSTIN. Required when gst_registration_type is regular or composition; ignored and cleared when unregistered.'
example: 36ABCDE1234F1Z5
nullable: true
address_line_1:
type: string
description: 'Must not be greater than 191 characters.'
example: w
nullable: true
address_line_2:
type: string
description: 'Must not be greater than 191 characters.'
example: p
nullable: true
city:
type: string
description: 'Must not be greater than 96 characters.'
example: w
nullable: true
state_code:
type: string
description: 'Two-digit Indian state or union territory code of the business.'
example: '36'
nullable: true
pincode:
type: string
description: 'Must be 6 digits.'
example: '569775'
nullable: true
default_place_of_supply:
type: string
description: ''
example: 1
enum:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 26
- 27
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 96
- 97
nullable: true
invoice_prefix:
type: string
description: 'Invoice-series prefix, up to 15 characters. GST permits letters, numbers, hyphens, and slashes; the generated invoice number is limited to 16 characters.'
example: HAWIOT/26-27/
prices_include_tax:
type: boolean
description: ''
example: false
authorized_signatory:
type: string
description: 'Must not be greater than 128 characters.'
example: g
nullable: true
bank_details:
type: string
description: 'Must not be greater than 1000 characters.'
example: z
nullable: true
upi_id:
type: string
description: 'Must match the regex /^[a-zA-Z0-9._-]{2,191}@[a-zA-Z0-9.-]{2,63}$/. Must not be greater than 255 characters.'
example: m
nullable: true
receiving_bank_account:
type: object
description: 'Shop bank account for customer payments. Omit to retain the current account; send all text fields empty to clear instructions. Uses the active default receiving bank without creating a duplicate ledger.'
example:
institution: 'HDFC Bank'
account_name: 'Anika Stores'
account_number: '001234567890'
ifsc: HDFC0000123
branch: Pune
show_on_invoice: true
properties:
institution:
type: string
description: 'Bank name, required when any bank text field is filled.'
example: 'HDFC Bank'
nullable: true
account_name:
type: string
description: 'Account holder, required when any bank text field is filled.'
example: 'Anika Stores'
nullable: true
account_number:
type: string
description: 'Full account number as 6–34 digits; preserve leading zeroes. Required when any bank text field is filled.'
example: '001234567890'
nullable: true
ifsc:
type: string
description: 'Valid 11-character IFSC; normalised to uppercase. Required when any bank text field is filled.'
example: HDFC0000123
nullable: true
branch:
type: string
description: 'Optional branch, up to 128 characters.'
example: Pune
nullable: true
show_on_invoice:
type: boolean
description: 'Include the bank on invoice PDFs, buyer links and customer sharing/reminder messages. Defaults to true when bank text fields are supplied; a visibility-only object preserves saved details and empty accounts remain hidden.'
example: true
theme:
type: string
description: 'Workspace preset key (blue, emerald, teal, violet, rose, maroon, graphite) or a custom hex colour. Accepts #RGB/#RRGGBB or bare hex, normalised to uppercase #RRGGBB. Null resets to the default; omission preserves it.'
example: '#2563EB'
nullable: true
custom_theme_colour:
type: string
description: 'Optional web-form mirror for a hex theme. Used only when theme itself is a hex value; API clients normally send theme directly.'
example: '#2563EB'
nullable: true
default_locale:
type: string
description: ''
example: en
enum:
- en
- hi
- ta
- te
- ml
- kn
- mr
- gu
- bn
invoice_share_message_template:
type: string
description: 'Must not be greater than 2000 characters.'
example: w
nullable: true
notification_preferences:
type: array
description: ''
example: null
items:
type: object
properties:
enabled:
type: boolean
description: ''
example: false
days:
type: array
description: 'Must be between -365 and 365.'
example:
- -364
items:
type: integer
threshold:
type: number
description: 'Must be at least 0. Must not be greater than 999999999.'
example: 6
min_shelf_life_days:
type: integer
description: 'Must be at least 0. Must not be greater than 730.'
example: 25
short_expiry_action:
type: string
description: ''
example: warn
enum:
- warn
- block
required:
- name
- gst_registration_type
- invoice_prefix
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
'/api/v1/businesses/{business}/dashboard':
get:
summary: ''
operationId: getApiV1BusinessesBusinessDashboard
description: ''
parameters: []
responses:
401:
description: ''
content:
application/json:
schema:
type: object
example:
message: Unauthenticated.
properties:
message:
type: string
example: Unauthenticated.
tags:
- Businesses
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
'/api/v1/businesses/{business}/onboarding':
get:
summary: ''
operationId: getApiV1BusinessesBusinessOnboarding
description: ''
parameters: []
responses:
401:
description: ''
content:
application/json:
schema:
type: object
example:
message: Unauthenticated.
properties:
message:
type: string
example: Unauthenticated.
tags:
- 'Business onboarding'
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
'/api/v1/businesses/{business}/onboarding/language':
patch:
summary: ''
operationId: patchApiV1BusinessesBusinessOnboardingLanguage
description: ''
parameters: []
responses: {}
tags:
- 'Business onboarding'
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
locale:
type: string
description: 'Supported locale code.'
example: hi
required:
- locale
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
'/api/v1/businesses/{business}/onboarding/business':
post:
summary: ''
operationId: postApiV1BusinessesBusinessOnboardingBusiness
description: ''
parameters: []
responses: {}
tags:
- 'Business onboarding'
requestBody:
required: true
content:
multipart/form-data:
schema:
type: object
properties:
name:
type: string
description: 'Public business name.'
example: 'Veera Stores'
store_type:
type: string
description: 'Store type key.'
example: kirana-store
supply_type:
type: string
description: 'goods, services, or both.'
example: goods
phone:
type: string
description: 'Public phone in E.164 or a 10-digit Indian local format.'
example: '9876543210'
logo:
type: string
format: binary
description: 'Optional PNG, JPG, or WebP logo up to 2 MB.'
nullable: true
required:
- name
- store_type
- supply_type
- phone
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
'/api/v1/businesses/{business}/onboarding/tax':
patch:
summary: ''
operationId: patchApiV1BusinessesBusinessOnboardingTax
description: ''
parameters: []
responses: {}
tags:
- 'Business onboarding'
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
gst_status:
type: string
description: 'registered or not_registered.'
example: registered
gst_registration_type:
type: string
description: 'Required when registered: normal or composition. Ignored for not_registered.'
example: normal
nullable: true
gstin:
type: string
description: 'Required when registered. Ignored for not_registered. May match another business workspace.'
example: 29ABCDE1234F1Z5
nullable: true
legal_name:
type: string
description: 'Must not be greater than 191 characters.'
example: a
nullable: true
address_line_1:
type: string
description: ''
example: '12 Market Road'
address_line_2:
type: string
description: 'Must not be greater than 191 characters.'
example: k
nullable: true
city:
type: string
description: ''
example: Bengaluru
state_code:
type: string
description: 'Indian GST state code.'
example: '29'
pincode:
type: string
description: 'Six digit PIN code.'
example: '560001'
required:
- gst_status
- address_line_1
- city
- state_code
- pincode
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
'/api/v1/businesses/{business}/onboarding/steps/{step}':
post:
summary: ''
operationId: postApiV1BusinessesBusinessOnboardingStepsStep
description: ''
parameters: []
responses: {}
tags:
- 'Business onboarding'
requestBody:
required: false
content:
application/json:
schema:
type: object
properties:
skip:
type: boolean
description: 'Set true to finish this optional step later.'
example: true
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
-
in: path
name: step
description: 'The step.'
example: architecto
required: true
schema:
type: string
'/api/v1/businesses/{business}/onboarding/product-imports':
post:
summary: 'Check the setup product workbook against existing masters only; no masters are created.'
operationId: checkTheSetupProductWorkbookAgainstExistingMastersOnlyNoMastersAreCreated
description: "Nonblank category/subcategory/brand/manufacturer names must exist in this business.\nA supplied taxable GST percentage for a Regular GST business must match an existing master.\nBlank organisation columns may be assigned later. Preview rows include nullable resolved master IDs.\nReuploading an unconfirmed file refreshes its validation and returns the same record with HTTP 200.\nAlready imported files remain unchanged; fresh uploads return HTTP 201."
parameters: []
responses: {}
tags:
- 'Business onboarding'
requestBody:
required: true
content:
multipart/form-data:
schema:
type: object
properties:
file:
type: string
format: binary
description: 'Completed Dukanam XLSX template, maximum 5 MB.'
required:
- file
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
'/api/v1/businesses/{business}/onboarding/product-imports/{itemImport_id}/confirm':
post:
summary: "Import every validated product row without exceeding the current plan's total item allowance."
operationId: importEveryValidatedProductRowWithoutExceedingTheCurrentPlansTotalItemAllowance
description: "Rechecks the workbook before writing; new SKU/barcode conflicts return 422 under file.\nRenamed/deleted classification or GST masters also return 422 under file; no masters are created."
parameters: []
responses:
409:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'This file has already been imported.'
properties:
message:
type: string
example: 'This file has already been imported.'
422:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'Your current plan includes up to 5 items. Upgrade to Smart Books to add more.'
errors:
plan:
- 'Your current plan includes up to 5 items. Upgrade to Smart Books to add more.'
properties:
message:
type: string
example: 'Your current plan includes up to 5 items. Upgrade to Smart Books to add more.'
errors:
type: object
properties:
plan:
type: array
example:
- 'Your current plan includes up to 5 items. Upgrade to Smart Books to add more.'
items:
type: string
tags:
- 'Business onboarding'
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
-
in: path
name: itemImport_id
description: 'The ID of the itemImport.'
example: 1
required: true
schema:
type: integer
'/api/v1/businesses/{business}/gstin/lookup':
get:
summary: 'Look up and normalize an Indian GSTIN.'
operationId: lookUpAndNormalizeAnIndianGSTIN
description: ''
parameters:
-
in: query
name: gstin
description: 'The 15-character GSTIN to verify.'
example: 03AAFCE1234J1Z0
required: true
schema:
type: string
description: 'The 15-character GSTIN to verify.'
example: 03AAFCE1234J1Z0
responses:
200:
description: ''
content:
application/json:
schema:
type: object
example:
data:
gstin: 03AAFCE1234J1Z0
legal_name: 'Edsafe Logistics Private Limited'
trade_name: 'EdLogistics Private Limited'
status: Active
registration_type: Regular
constitution: 'Private Limited Company'
einvoice_status: 'Yes'
registration_date: '2019-04-01'
last_updated: '2026-01-15'
state_code: '03'
state_name: Punjab
address:
line1: 'Number 234, 55 Wide Road, Ground Floor, Opposite Upkar Transport'
line2: 'Godown Area, Zirakpur, SASNagar'
city: Zirakpur
district: SASNagar
postal_code: '140603'
state_code: '03'
country_code: IN
suggested_business:
legal_name: 'Edsafe Logistics Private Limited'
gst_registration_type: normal
gstin: 03AAFCE1234J1Z0
address_line_1: 'Number 234, 55 Wide Road, Ground Floor, Opposite Upkar Transport'
address_line_2: 'Godown Area, Zirakpur, SASNagar'
city: Zirakpur
state_code: '03'
pincode: '140603'
default_place_of_supply: '03'
suggested_contact:
name: 'EdLogistics Private Limited'
company_name: 'EdLogistics Private Limited'
gst_treatment: registered_regular
gstin: 03AAFCE1234J1Z0
billing_address_line_1: 'Number 234, 55 Wide Road, Ground Floor, Opposite Upkar Transport'
billing_address_line_2: 'Godown Area, Zirakpur, SASNagar'
billing_city: Zirakpur
billing_state_code: '03'
billing_pincode: '140603'
properties:
data:
type: object
properties:
gstin:
type: string
example: 03AAFCE1234J1Z0
legal_name:
type: string
example: 'Edsafe Logistics Private Limited'
trade_name:
type: string
example: 'EdLogistics Private Limited'
status:
type: string
example: Active
registration_type:
type: string
example: Regular
constitution:
type: string
example: 'Private Limited Company'
einvoice_status:
type: string
example: 'Yes'
registration_date:
type: string
example: '2019-04-01'
last_updated:
type: string
example: '2026-01-15'
state_code:
type: string
example: '03'
state_name:
type: string
example: Punjab
address:
type: object
properties:
line1:
type: string
example: 'Number 234, 55 Wide Road, Ground Floor, Opposite Upkar Transport'
line2:
type: string
example: 'Godown Area, Zirakpur, SASNagar'
city:
type: string
example: Zirakpur
district:
type: string
example: SASNagar
postal_code:
type: string
example: '140603'
state_code:
type: string
example: '03'
country_code:
type: string
example: IN
suggested_business:
type: object
properties:
legal_name:
type: string
example: 'Edsafe Logistics Private Limited'
gst_registration_type:
type: string
example: normal
gstin:
type: string
example: 03AAFCE1234J1Z0
address_line_1:
type: string
example: 'Number 234, 55 Wide Road, Ground Floor, Opposite Upkar Transport'
address_line_2:
type: string
example: 'Godown Area, Zirakpur, SASNagar'
city:
type: string
example: Zirakpur
state_code:
type: string
example: '03'
pincode:
type: string
example: '140603'
default_place_of_supply:
type: string
example: '03'
suggested_contact:
type: object
properties:
name:
type: string
example: 'EdLogistics Private Limited'
company_name:
type: string
example: 'EdLogistics Private Limited'
gst_treatment:
type: string
example: registered_regular
gstin:
type: string
example: 03AAFCE1234J1Z0
billing_address_line_1:
type: string
example: 'Number 234, 55 Wide Road, Ground Floor, Opposite Upkar Transport'
billing_address_line_2:
type: string
example: 'Godown Area, Zirakpur, SASNagar'
billing_city:
type: string
example: Zirakpur
billing_state_code:
type: string
example: '03'
billing_pincode:
type: string
example: '140603'
422:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'We could not verify this GSTIN. Check it and try again.'
properties:
message:
type: string
example: 'We could not verify this GSTIN. Check it and try again.'
503:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'GST verification is temporarily unavailable. Please try again shortly.'
properties:
message:
type: string
example: 'GST verification is temporarily unavailable. Please try again shortly.'
tags:
- 'GST verification'
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
'/api/v1/businesses/{business}/contacts/{contact_id}/ledger.pdf':
get:
summary: 'Download a contact ledger statement PDF.'
operationId: downloadAContactLedgerStatementPDF
description: 'The statement includes dated movements, running receivable and payable balances, and period totals.'
parameters:
-
in: query
name: from
description: 'Start date, inclusive.'
example: '2026-04-01'
required: false
schema:
type: string
description: 'Start date, inclusive.'
example: '2026-04-01'
-
in: query
name: to
description: 'End date, inclusive. Must be on or after `from`.'
example: '2027-03-31'
required: false
schema:
type: string
description: 'End date, inclusive. Must be on or after `from`.'
example: '2027-03-31'
responses:
200:
description: 'The generated ledger statement PDF.'
content:
application/pdf:
schema:
type: string
format: binary
tags:
- Contacts
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
-
in: path
name: contact_id
description: 'The ID of the contact.'
example: 1
required: true
schema:
type: integer
'/api/v1/businesses/{business}/contacts/{contact_id}/ledger/email':
post:
summary: 'Email a contact ledger statement.'
operationId: emailAContactLedgerStatement
description: 'Sends one email to each chosen person at the party with the statement PDF for the period attached and a button to the signed public statement. Choose people by the `key` values in `send_recipients` from GET `contacts/{contact}`; each must have an email address. Leave `from` and `to` empty for all dates. Every email send is recorded in the audit log.'
parameters: []
responses:
202:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'Emailed to Ravi Kumar.'
data:
sent_to:
-
key: primary
name: 'Ravi Kumar'
email: ravi@example.test
properties:
message:
type: string
example: 'Emailed to Ravi Kumar.'
data:
type: object
properties:
sent_to:
type: array
example:
- key: primary
name: 'Ravi Kumar'
email: ravi@example.test
items:
type: object
properties:
key:
type: string
example: primary
name:
type: string
example: 'Ravi Kumar'
email:
type: string
example: ravi@example.test
422:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'Ravi Kumar has no email address.'
errors:
recipients.0:
- 'Ravi Kumar has no email address.'
properties:
message:
type: string
example: 'Ravi Kumar has no email address.'
errors:
type: object
properties:
recipients.0:
type: array
example:
- 'Ravi Kumar has no email address.'
items:
type: string
tags:
- Contacts
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
recipients:
type: array
description: 'A recipient key: `primary` for the party''s primary contact, or `person-{id}` for an additional contact. Must match the regex /^(primary|person-\d+)$/.'
example:
- primary
- person-12
items:
type: string
recipient_emails:
type: array
description: 'Must be a valid email address. Must not be greater than 254 characters.'
example:
primary: accounts@example.com
items:
type: string
nullable: true
subject:
type: string
description: 'Email subject line. Must not be greater than 150 characters.'
example: 'Sri Lakshmi Traders | Purchase order: PO-0007'
message:
type: string
description: 'Message body. Blank lines separate paragraphs. The PDF is attached and a button links to the signed public page. Must not be greater than 2000 characters.'
example: 'Hi Ravi, please find purchase order PO-0007 for ₹12,500.00. Please confirm the order and the delivery date.'
from:
type: string
description: 'Ledger statements only: start date, inclusive. Must be a valid date.'
example: '2026-04-01'
nullable: true
to:
type: string
description: 'Ledger statements only: end date, inclusive. Must be on or after `from`. Must be a valid date. Must be a date after or equal to from.'
example: '2027-03-31'
nullable: true
required:
- recipients
- subject
- message
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
-
in: path
name: contact_id
description: 'The ID of the contact.'
example: 1
required: true
schema:
type: integer
'/api/v1/businesses/{business}/contacts/{contact_id}/active':
patch:
summary: 'Stop trading with a party, or start again.'
operationId: stopTradingWithAPartyOrStartAgain
description: "Its own endpoint rather than a field on update, which re-records the opening balance and\nwill reverse and replace its accounting entry when it looks changed. Retiring a party is a one-word\ndecision and has no business touching their khata. An inactive party keeps its balance and\nevery document it already sits on; it is simply no longer offered when raising a new one."
parameters: []
responses:
422:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'The walk-in customer is where every untagged counter sale is billed, so it cannot be made inactive.'
errors:
is_active:
- 'The walk-in customer is where every untagged counter sale is billed, so it cannot be made inactive.'
properties:
message:
type: string
example: 'The walk-in customer is where every untagged counter sale is billed, so it cannot be made inactive.'
errors:
type: object
properties:
is_active:
type: array
example:
- 'The walk-in customer is where every untagged counter sale is billed, so it cannot be made inactive.'
items:
type: string
tags:
- Contacts
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
is_active:
type: boolean
description: 'Whether the shop still trades with this party.'
example: false
required:
- is_active
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
-
in: path
name: contact_id
description: 'The ID of the contact.'
example: 1
required: true
schema:
type: integer
'/api/v1/businesses/{business}/contacts/bulk':
patch:
summary: 'Bulk update contact status or party role.'
operationId: bulkUpdateContactStatusOrPartyRole
description: 'Atomic: all IDs must belong to this business and the caller must be authorized for every current and target role. Missing/cross-business IDs return 404; denied roles return 403; invalid actions, duplicate IDs and protected walk-in changes return 422. Existing documents, balances, journals and profile fields are preserved. Use one ID for an individual role change.'
parameters: []
responses:
200:
description: ''
content:
application/json:
schema:
type: object
example:
data:
updated_count: 2
properties:
data:
type: object
properties:
updated_count:
type: integer
example: 2
tags:
- Contacts
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
contact_ids:
type: array
description: ''
example:
- 1
- 2
items:
type: integer
action:
type: string
description: 'activate or deactivate changes only status. customer, supplier or both changes only the party role. Documents, opening balances, journals and other profile fields are preserved.'
example: both
enum:
- activate
- deactivate
- customer
- supplier
- both
required:
- contact_ids
- action
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
'/api/v1/businesses/{business}/contacts/{contact}/photo':
get:
summary: "View the party's private profile photo."
operationId: viewThePartysPrivateProfilePhoto
description: "Requires membership, khata access and sales permission for customers or purchases permission\nfor suppliers. Both parties permit either permission. Missing photos return 404.\nNo public storage path or signed public link is exposed. Production photos use private S3;\nlegacy images are read from their recorded disk through this same authenticated endpoint."
parameters: []
responses:
200:
description: 'The saved JPEG, PNG or WebP photo, with private/no-store headers.'
content:
application/octet-stream:
schema:
type: string
format: binary
tags:
- Contacts
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
-
in: path
name: contact
description: 'The contact.'
example: 1
required: true
schema:
type: integer
'/api/v1/businesses/{business}/contacts':
get:
summary: ''
operationId: getApiV1BusinessesBusinessContacts
description: ''
parameters:
-
in: query
name: status
description: 'Which parties to list by state: `all` (the default) lists both, with inactive parties last, `active` lists only parties the shop still trades with, and `inactive` lists only inactive ones. Use `active` when building a picker.'
example: "active\n\nThe `search` term also matches the name and phone of a contact's additional contacts."
required: false
schema:
type: string
description: 'Which parties to list by state: `all` (the default) lists both, with inactive parties last, `active` lists only parties the shop still trades with, and `inactive` lists only inactive ones. Use `active` when building a picker.'
example: "active\n\nThe `search` term also matches the name and phone of a contact's additional contacts."
responses:
401:
description: ''
content:
application/json:
schema:
type: object
example:
message: Unauthenticated.
properties:
message:
type: string
example: Unauthenticated.
tags:
- Contacts
requestBody:
required: false
content:
application/json:
schema:
type: object
properties:
type:
type: string
description: ''
example: customer
enum:
- customer
- supplier
- both
nullable: true
search:
type: string
description: 'Must not be greater than 128 characters.'
example: b
nullable: true
status:
type: string
description: ''
example: all
enum:
- all
- active
- inactive
nullable: true
per_page:
type: integer
description: 'Must be at least 1. Must not be greater than 100.'
example: 22
nullable: true
post:
summary: 'Create a contact and record its opening balance.'
operationId: createAContactAndRecordItsOpeningBalance
description: "Customer opening balances are recorded in Accounts receivable; supplier opening balances are recorded in Accounts payable, offset by Owner equity. A customer may also carry a `credit_limit`, the most it may owe at once; sales invoices and manual khata credits past it are refused with HTTP 422.\n\nA business party keeps its primary contact in `contact_person`, `phone` and `email`, and may list more people in `additional_contacts`."
parameters: []
responses: {}
tags:
- Contacts
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
type:
type: string
description: ''
example: customer
enum:
- customer
- supplier
- both
is_active:
type: boolean
description: ''
example: false
profile_type:
type: string
description: ''
example: consumer
enum:
- consumer
- business
name:
type: string
description: 'Must not be greater than 128 characters.'
example: b
company_name:
type: string
description: 'Must not be greater than 191 characters.'
example: 'n'
nullable: true
contact_person:
type: string
description: "Primary contact person at a business party. Ignored for consumer contacts. The primary contact phone and email are the contact's own `phone` and `email`. Must not be greater than 128 characters."
example: 'Anita Rao'
nullable: true
phone:
type: string
description: 'Must not be greater than 32 characters.'
example: g
nullable: true
email:
type: string
description: 'Must be a valid email address. Must not be greater than 255 characters.'
example: rowan.gulgowski@example.com
nullable: true
gst_treatment:
type: string
description: 'GST registration status. Use unregistered for an Indian business without GST registration and overseas for a contact outside India.'
example: unregistered
enum:
- unregistered
- registered_regular
- registered_composition
- consumer
- overseas
- sez
gstin:
type: string
description: '15-character Indian GSTIN. Required for registered_regular, registered_composition, and sez. Optional for an overseas contact registered under Indian GST; ignored for unregistered and consumer contacts. The same GSTIN may be used by multiple contacts. Must match the regex /^[0-9]{2}[A-Z]{5}[0-9]{4}[A-Z][1-9A-Z]Z[0-9A-Z]$/. Must be 15 characters.'
example: 29ABCDE1234F1Z5
nullable: true
pan:
type: string
description: 'Optional Indian PAN. Ignored for consumer and overseas contacts. Must match the regex /^[A-Z]{5}[0-9]{4}[A-Z]$/. Must be 10 characters.'
example: ABCDE1234F
nullable: true
country:
type: string
description: 'Country where the overseas business is registered. Required when gst_treatment is overseas; ignored otherwise. Must not be greater than 96 characters.'
example: 'United Arab Emirates'
nullable: true
foreign_tax_id:
type: string
description: 'Optional foreign Tax, VAT, or government-issued business identification number. Used only for overseas contacts. Must not be greater than 64 characters.'
example: '100123456700003'
nullable: true
latitude:
type: number
description: 'Optional contact location latitude in decimal degrees, from -90 to 90. Send both latitude and longitude together. Omit both on update to preserve the location; send both as null to remove it. Mobile map pickers submit the selected coordinates here. This field is required when longitude is present. Must be between -90 and 90.'
example: 17.448583
nullable: true
longitude:
type: number
description: 'Optional contact location longitude in decimal degrees, from -180 to 180. Required with latitude; send both fields together, including when clearing them. This field is required when latitude is present. Must be between -180 and 180.'
example: 78.390803
nullable: true
address:
type: string
description: 'Must not be greater than 1000 characters.'
example: d
nullable: true
billing_address_line_1:
type: string
description: 'Must not be greater than 191 characters.'
example: l
nullable: true
billing_address_line_2:
type: string
description: 'Must not be greater than 191 characters.'
example: j
nullable: true
billing_city:
type: string
description: 'Must not be greater than 96 characters.'
example: 'n'
nullable: true
billing_state_code:
type: string
description: ''
example: 1
enum:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 26
- 27
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 96
- 97
- 99
nullable: true
billing_region:
type: string
description: 'State, province, emirate, or other first-level region for an overseas billing address. Ignored for contacts in India. Must not be greater than 96 characters.'
example: Dubai
nullable: true
billing_pincode:
type: string
description: 'Must be 6 digits.'
example: '569775'
nullable: true
billing_postal_code:
type: string
description: 'Postal or ZIP code for an overseas billing address. Ignored for contacts in India. Must not be greater than 32 characters.'
example: 'SW1A 1AA'
nullable: true
shipping_same_as_billing:
type: boolean
description: ''
example: false
shipping_address_line_1:
type: string
description: 'Must not be greater than 191 characters.'
example: 'n'
nullable: true
shipping_address_line_2:
type: string
description: 'Must not be greater than 191 characters.'
example: g
nullable: true
shipping_city:
type: string
description: 'Must not be greater than 96 characters.'
example: z
nullable: true
shipping_state_code:
type: string
description: ''
example: 1
enum:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 26
- 27
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 96
- 97
- 99
nullable: true
shipping_region:
type: string
description: 'State, province, emirate, or other first-level region for an overseas shipping address. Ignored for contacts in India. Must not be greater than 96 characters.'
example: Dubai
nullable: true
shipping_pincode:
type: string
description: 'Must be 6 digits.'
example: '569775'
nullable: true
shipping_postal_code:
type: string
description: 'Postal or ZIP code for an overseas shipping address. Ignored for contacts in India. Must not be greater than 32 characters.'
example: 'SW1A 1AA'
nullable: true
price_list_id:
type: integer
description: 'Rate card this party buys at. Item lines raised for the party take their rate from it unless the request quotes one. Requires the party pricing feature; leave empty to bill at the item master rate.'
example: 7
nullable: true
default_discount_percent:
type: number
description: 'Blanket percentage off whatever rate the party would otherwise pay, applied after the rate card. Defaults to 0. Must be at least 0. Must not be greater than 100.'
example: 2
nullable: true
credit_limit:
type: number
description: 'Most this customer may owe at any moment, in rupees. Omit or send null for no limit; send 0 to put the party on cash only. Ignored for supplier contacts. A sales invoice or manual khata credit that would take the receivable balance past it is refused with HTTP 422. Must be at least 0. Must not be greater than 999999999.'
example: 50000
nullable: true
opening_balance:
type: number
description: 'Must be at least 0. Must not be greater than 999999999.'
example: 22
nullable: true
opening_balance_side:
type: string
description: ''
example: receivable
enum:
- receivable
- payable
opening_balance_date:
type: string
description: 'Must be a valid date.'
example: '2026-01-15'
nullable: true
additional_contacts:
type: array
description: ''
example:
- name: 'Suresh Gupta'
designation: Accounts
phone: '9811111111'
email: accounts@example.com
items:
type: object
properties:
name:
type: string
description: 'Must not be greater than 128 characters.'
example: 'Suresh Gupta'
designation:
type: string
description: 'Must not be greater than 96 characters.'
example: Accounts
nullable: true
phone:
type: string
description: 'Must not be greater than 32 characters.'
example: '9811111111'
nullable: true
email:
type: string
description: 'Must be a valid email address. Must not be greater than 255 characters.'
example: accounts@example.com
nullable: true
required:
- name
remove_photo:
type: boolean
description: 'Set true to remove the saved photo. Cannot be combined with a photo upload. Defaults to false.'
example: false
required:
- type
- profile_type
- name
- gst_treatment
- opening_balance_side
multipart/form-data:
schema:
type: object
properties:
type:
type: string
description: ''
example: customer
enum:
- customer
- supplier
- both
is_active:
type: boolean
description: ''
example: false
profile_type:
type: string
description: ''
example: consumer
enum:
- consumer
- business
name:
type: string
description: 'Must not be greater than 128 characters.'
example: b
company_name:
type: string
description: 'Must not be greater than 191 characters.'
example: 'n'
nullable: true
contact_person:
type: string
description: "Primary contact person at a business party. Ignored for consumer contacts. The primary contact phone and email are the contact's own `phone` and `email`. Must not be greater than 128 characters."
example: 'Anita Rao'
nullable: true
phone:
type: string
description: 'Must not be greater than 32 characters.'
example: g
nullable: true
email:
type: string
description: 'Must be a valid email address. Must not be greater than 255 characters.'
example: rowan.gulgowski@example.com
nullable: true
gst_treatment:
type: string
description: 'GST registration status. Use unregistered for an Indian business without GST registration and overseas for a contact outside India.'
example: unregistered
enum:
- unregistered
- registered_regular
- registered_composition
- consumer
- overseas
- sez
gstin:
type: string
description: '15-character Indian GSTIN. Required for registered_regular, registered_composition, and sez. Optional for an overseas contact registered under Indian GST; ignored for unregistered and consumer contacts. The same GSTIN may be used by multiple contacts. Must match the regex /^[0-9]{2}[A-Z]{5}[0-9]{4}[A-Z][1-9A-Z]Z[0-9A-Z]$/. Must be 15 characters.'
example: 29ABCDE1234F1Z5
nullable: true
pan:
type: string
description: 'Optional Indian PAN. Ignored for consumer and overseas contacts. Must match the regex /^[A-Z]{5}[0-9]{4}[A-Z]$/. Must be 10 characters.'
example: ABCDE1234F
nullable: true
country:
type: string
description: 'Country where the overseas business is registered. Required when gst_treatment is overseas; ignored otherwise. Must not be greater than 96 characters.'
example: 'United Arab Emirates'
nullable: true
foreign_tax_id:
type: string
description: 'Optional foreign Tax, VAT, or government-issued business identification number. Used only for overseas contacts. Must not be greater than 64 characters.'
example: '100123456700003'
nullable: true
latitude:
type: number
description: 'Optional contact location latitude in decimal degrees, from -90 to 90. Send both latitude and longitude together. Omit both on update to preserve the location; send both as null to remove it. Mobile map pickers submit the selected coordinates here. This field is required when longitude is present. Must be between -90 and 90.'
example: 17.448583
nullable: true
longitude:
type: number
description: 'Optional contact location longitude in decimal degrees, from -180 to 180. Required with latitude; send both fields together, including when clearing them. This field is required when latitude is present. Must be between -180 and 180.'
example: 78.390803
nullable: true
address:
type: string
description: 'Must not be greater than 1000 characters.'
example: d
nullable: true
billing_address_line_1:
type: string
description: 'Must not be greater than 191 characters.'
example: l
nullable: true
billing_address_line_2:
type: string
description: 'Must not be greater than 191 characters.'
example: j
nullable: true
billing_city:
type: string
description: 'Must not be greater than 96 characters.'
example: 'n'
nullable: true
billing_state_code:
type: string
description: ''
example: 1
enum:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 26
- 27
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 96
- 97
- 99
nullable: true
billing_region:
type: string
description: 'State, province, emirate, or other first-level region for an overseas billing address. Ignored for contacts in India. Must not be greater than 96 characters.'
example: Dubai
nullable: true
billing_pincode:
type: string
description: 'Must be 6 digits.'
example: '569775'
nullable: true
billing_postal_code:
type: string
description: 'Postal or ZIP code for an overseas billing address. Ignored for contacts in India. Must not be greater than 32 characters.'
example: 'SW1A 1AA'
nullable: true
shipping_same_as_billing:
type: boolean
description: ''
example: false
shipping_address_line_1:
type: string
description: 'Must not be greater than 191 characters.'
example: 'n'
nullable: true
shipping_address_line_2:
type: string
description: 'Must not be greater than 191 characters.'
example: g
nullable: true
shipping_city:
type: string
description: 'Must not be greater than 96 characters.'
example: z
nullable: true
shipping_state_code:
type: string
description: ''
example: 1
enum:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 26
- 27
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 96
- 97
- 99
nullable: true
shipping_region:
type: string
description: 'State, province, emirate, or other first-level region for an overseas shipping address. Ignored for contacts in India. Must not be greater than 96 characters.'
example: Dubai
nullable: true
shipping_pincode:
type: string
description: 'Must be 6 digits.'
example: '569775'
nullable: true
shipping_postal_code:
type: string
description: 'Postal or ZIP code for an overseas shipping address. Ignored for contacts in India. Must not be greater than 32 characters.'
example: 'SW1A 1AA'
nullable: true
price_list_id:
type: integer
description: 'Rate card this party buys at. Item lines raised for the party take their rate from it unless the request quotes one. Requires the party pricing feature; leave empty to bill at the item master rate.'
example: 7
nullable: true
default_discount_percent:
type: number
description: 'Blanket percentage off whatever rate the party would otherwise pay, applied after the rate card. Defaults to 0. Must be at least 0. Must not be greater than 100.'
example: 2
nullable: true
credit_limit:
type: number
description: 'Most this customer may owe at any moment, in rupees. Omit or send null for no limit; send 0 to put the party on cash only. Ignored for supplier contacts. A sales invoice or manual khata credit that would take the receivable balance past it is refused with HTTP 422. Must be at least 0. Must not be greater than 999999999.'
example: 50000
nullable: true
opening_balance:
type: number
description: 'Must be at least 0. Must not be greater than 999999999.'
example: 22
nullable: true
opening_balance_side:
type: string
description: ''
example: receivable
enum:
- receivable
- payable
opening_balance_date:
type: string
description: 'Must be a valid date.'
example: '2026-01-15'
nullable: true
additional_contacts:
type: array
description: ''
example:
- name: 'Suresh Gupta'
designation: Accounts
phone: '9811111111'
email: accounts@example.com
items:
type: object
properties:
name:
type: string
description: 'Must not be greater than 128 characters.'
example: 'Suresh Gupta'
designation:
type: string
description: 'Must not be greater than 96 characters.'
example: Accounts
nullable: true
phone:
type: string
description: 'Must not be greater than 32 characters.'
example: '9811111111'
nullable: true
email:
type: string
description: 'Must be a valid email address. Must not be greater than 255 characters.'
example: accounts@example.com
nullable: true
required:
- name
photo:
type: string
format: binary
description: 'Optional private JPEG, PNG or WebP profile photo, up to 2 MB and 8192 pixels per side. Multipart upload. Omit to retain the saved photo; null does not remove it. Clients may crop before uploading. Must be an image. Must not be greater than 2048 kilobytes.'
nullable: true
remove_photo:
type: boolean
description: 'Set true to remove the saved photo. Cannot be combined with a photo upload. Defaults to false.'
example: false
required:
- type
- profile_type
- name
- gst_treatment
- opening_balance_side
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
'/api/v1/businesses/{business}/contacts/{id}':
get:
summary: ''
operationId: getApiV1BusinessesBusinessContactsId
description: ''
parameters: []
responses:
401:
description: ''
content:
application/json:
schema:
type: object
example:
message: Unauthenticated.
properties:
message:
type: string
example: Unauthenticated.
tags:
- Contacts
put:
summary: 'Update a contact and replace its opening-balance journal.'
operationId: updateAContactAndReplaceItsOpeningBalanceJournal
description: 'Omit `additional_contacts` to leave the list as it is; send the complete list to replace it, or an empty array to remove every additional contact.'
parameters: []
responses: {}
tags:
- Contacts
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
type:
type: string
description: ''
example: customer
enum:
- customer
- supplier
- both
is_active:
type: boolean
description: ''
example: false
profile_type:
type: string
description: ''
example: consumer
enum:
- consumer
- business
name:
type: string
description: 'Must not be greater than 128 characters.'
example: b
company_name:
type: string
description: 'Must not be greater than 191 characters.'
example: 'n'
nullable: true
contact_person:
type: string
description: "Primary contact person at a business party. Ignored for consumer contacts. The primary contact phone and email are the contact's own `phone` and `email`. Must not be greater than 128 characters."
example: 'Anita Rao'
nullable: true
phone:
type: string
description: 'Must not be greater than 32 characters.'
example: g
nullable: true
email:
type: string
description: 'Must be a valid email address. Must not be greater than 255 characters.'
example: rowan.gulgowski@example.com
nullable: true
gst_treatment:
type: string
description: 'GST registration status. Use unregistered for an Indian business without GST registration and overseas for a contact outside India.'
example: unregistered
enum:
- unregistered
- registered_regular
- registered_composition
- consumer
- overseas
- sez
gstin:
type: string
description: '15-character Indian GSTIN. Required for registered_regular, registered_composition, and sez. Optional for an overseas contact registered under Indian GST; ignored for unregistered and consumer contacts. The same GSTIN may be used by multiple contacts. Must match the regex /^[0-9]{2}[A-Z]{5}[0-9]{4}[A-Z][1-9A-Z]Z[0-9A-Z]$/. Must be 15 characters.'
example: 29ABCDE1234F1Z5
nullable: true
pan:
type: string
description: 'Optional Indian PAN. Ignored for consumer and overseas contacts. Must match the regex /^[A-Z]{5}[0-9]{4}[A-Z]$/. Must be 10 characters.'
example: ABCDE1234F
nullable: true
country:
type: string
description: 'Country where the overseas business is registered. Required when gst_treatment is overseas; ignored otherwise. Must not be greater than 96 characters.'
example: 'United Arab Emirates'
nullable: true
foreign_tax_id:
type: string
description: 'Optional foreign Tax, VAT, or government-issued business identification number. Used only for overseas contacts. Must not be greater than 64 characters.'
example: '100123456700003'
nullable: true
latitude:
type: number
description: 'Optional contact location latitude in decimal degrees, from -90 to 90. Send both latitude and longitude together. Omit both on update to preserve the location; send both as null to remove it. Mobile map pickers submit the selected coordinates here. This field is required when longitude is present. Must be between -90 and 90.'
example: 17.448583
nullable: true
longitude:
type: number
description: 'Optional contact location longitude in decimal degrees, from -180 to 180. Required with latitude; send both fields together, including when clearing them. This field is required when latitude is present. Must be between -180 and 180.'
example: 78.390803
nullable: true
address:
type: string
description: 'Must not be greater than 1000 characters.'
example: d
nullable: true
billing_address_line_1:
type: string
description: 'Must not be greater than 191 characters.'
example: l
nullable: true
billing_address_line_2:
type: string
description: 'Must not be greater than 191 characters.'
example: j
nullable: true
billing_city:
type: string
description: 'Must not be greater than 96 characters.'
example: 'n'
nullable: true
billing_state_code:
type: string
description: ''
example: 1
enum:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 26
- 27
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 96
- 97
- 99
nullable: true
billing_region:
type: string
description: 'State, province, emirate, or other first-level region for an overseas billing address. Ignored for contacts in India. Must not be greater than 96 characters.'
example: Dubai
nullable: true
billing_pincode:
type: string
description: 'Must be 6 digits.'
example: '569775'
nullable: true
billing_postal_code:
type: string
description: 'Postal or ZIP code for an overseas billing address. Ignored for contacts in India. Must not be greater than 32 characters.'
example: 'SW1A 1AA'
nullable: true
shipping_same_as_billing:
type: boolean
description: ''
example: false
shipping_address_line_1:
type: string
description: 'Must not be greater than 191 characters.'
example: 'n'
nullable: true
shipping_address_line_2:
type: string
description: 'Must not be greater than 191 characters.'
example: g
nullable: true
shipping_city:
type: string
description: 'Must not be greater than 96 characters.'
example: z
nullable: true
shipping_state_code:
type: string
description: ''
example: 1
enum:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 26
- 27
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 96
- 97
- 99
nullable: true
shipping_region:
type: string
description: 'State, province, emirate, or other first-level region for an overseas shipping address. Ignored for contacts in India. Must not be greater than 96 characters.'
example: Dubai
nullable: true
shipping_pincode:
type: string
description: 'Must be 6 digits.'
example: '569775'
nullable: true
shipping_postal_code:
type: string
description: 'Postal or ZIP code for an overseas shipping address. Ignored for contacts in India. Must not be greater than 32 characters.'
example: 'SW1A 1AA'
nullable: true
price_list_id:
type: integer
description: 'Rate card this party buys at. Item lines raised for the party take their rate from it unless the request quotes one. Requires the party pricing feature; leave empty to bill at the item master rate.'
example: 7
nullable: true
default_discount_percent:
type: number
description: 'Blanket percentage off whatever rate the party would otherwise pay, applied after the rate card. Defaults to 0. Must be at least 0. Must not be greater than 100.'
example: 2
nullable: true
credit_limit:
type: number
description: 'Most this customer may owe at any moment, in rupees. Omit or send null for no limit; send 0 to put the party on cash only. Ignored for supplier contacts. A sales invoice or manual khata credit that would take the receivable balance past it is refused with HTTP 422. Must be at least 0. Must not be greater than 999999999.'
example: 50000
nullable: true
opening_balance:
type: number
description: 'Must be at least 0. Must not be greater than 999999999.'
example: 22
nullable: true
opening_balance_side:
type: string
description: ''
example: receivable
enum:
- receivable
- payable
opening_balance_date:
type: string
description: 'Must be a valid date.'
example: '2026-01-15'
nullable: true
additional_contacts:
type: array
description: ''
example:
- name: 'Suresh Gupta'
designation: Accounts
phone: '9811111111'
email: accounts@example.com
items:
type: object
properties:
name:
type: string
description: 'Must not be greater than 128 characters.'
example: 'Suresh Gupta'
designation:
type: string
description: 'Must not be greater than 96 characters.'
example: Accounts
nullable: true
phone:
type: string
description: 'Must not be greater than 32 characters.'
example: '9811111111'
nullable: true
email:
type: string
description: 'Must be a valid email address. Must not be greater than 255 characters.'
example: accounts@example.com
nullable: true
required:
- name
remove_photo:
type: boolean
description: 'Set true to remove the saved photo. Cannot be combined with a photo upload. Defaults to false.'
example: false
required:
- type
- profile_type
- name
- gst_treatment
- opening_balance_side
multipart/form-data:
schema:
type: object
properties:
type:
type: string
description: ''
example: customer
enum:
- customer
- supplier
- both
is_active:
type: boolean
description: ''
example: false
profile_type:
type: string
description: ''
example: consumer
enum:
- consumer
- business
name:
type: string
description: 'Must not be greater than 128 characters.'
example: b
company_name:
type: string
description: 'Must not be greater than 191 characters.'
example: 'n'
nullable: true
contact_person:
type: string
description: "Primary contact person at a business party. Ignored for consumer contacts. The primary contact phone and email are the contact's own `phone` and `email`. Must not be greater than 128 characters."
example: 'Anita Rao'
nullable: true
phone:
type: string
description: 'Must not be greater than 32 characters.'
example: g
nullable: true
email:
type: string
description: 'Must be a valid email address. Must not be greater than 255 characters.'
example: rowan.gulgowski@example.com
nullable: true
gst_treatment:
type: string
description: 'GST registration status. Use unregistered for an Indian business without GST registration and overseas for a contact outside India.'
example: unregistered
enum:
- unregistered
- registered_regular
- registered_composition
- consumer
- overseas
- sez
gstin:
type: string
description: '15-character Indian GSTIN. Required for registered_regular, registered_composition, and sez. Optional for an overseas contact registered under Indian GST; ignored for unregistered and consumer contacts. The same GSTIN may be used by multiple contacts. Must match the regex /^[0-9]{2}[A-Z]{5}[0-9]{4}[A-Z][1-9A-Z]Z[0-9A-Z]$/. Must be 15 characters.'
example: 29ABCDE1234F1Z5
nullable: true
pan:
type: string
description: 'Optional Indian PAN. Ignored for consumer and overseas contacts. Must match the regex /^[A-Z]{5}[0-9]{4}[A-Z]$/. Must be 10 characters.'
example: ABCDE1234F
nullable: true
country:
type: string
description: 'Country where the overseas business is registered. Required when gst_treatment is overseas; ignored otherwise. Must not be greater than 96 characters.'
example: 'United Arab Emirates'
nullable: true
foreign_tax_id:
type: string
description: 'Optional foreign Tax, VAT, or government-issued business identification number. Used only for overseas contacts. Must not be greater than 64 characters.'
example: '100123456700003'
nullable: true
latitude:
type: number
description: 'Optional contact location latitude in decimal degrees, from -90 to 90. Send both latitude and longitude together. Omit both on update to preserve the location; send both as null to remove it. Mobile map pickers submit the selected coordinates here. This field is required when longitude is present. Must be between -90 and 90.'
example: 17.448583
nullable: true
longitude:
type: number
description: 'Optional contact location longitude in decimal degrees, from -180 to 180. Required with latitude; send both fields together, including when clearing them. This field is required when latitude is present. Must be between -180 and 180.'
example: 78.390803
nullable: true
address:
type: string
description: 'Must not be greater than 1000 characters.'
example: d
nullable: true
billing_address_line_1:
type: string
description: 'Must not be greater than 191 characters.'
example: l
nullable: true
billing_address_line_2:
type: string
description: 'Must not be greater than 191 characters.'
example: j
nullable: true
billing_city:
type: string
description: 'Must not be greater than 96 characters.'
example: 'n'
nullable: true
billing_state_code:
type: string
description: ''
example: 1
enum:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 26
- 27
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 96
- 97
- 99
nullable: true
billing_region:
type: string
description: 'State, province, emirate, or other first-level region for an overseas billing address. Ignored for contacts in India. Must not be greater than 96 characters.'
example: Dubai
nullable: true
billing_pincode:
type: string
description: 'Must be 6 digits.'
example: '569775'
nullable: true
billing_postal_code:
type: string
description: 'Postal or ZIP code for an overseas billing address. Ignored for contacts in India. Must not be greater than 32 characters.'
example: 'SW1A 1AA'
nullable: true
shipping_same_as_billing:
type: boolean
description: ''
example: false
shipping_address_line_1:
type: string
description: 'Must not be greater than 191 characters.'
example: 'n'
nullable: true
shipping_address_line_2:
type: string
description: 'Must not be greater than 191 characters.'
example: g
nullable: true
shipping_city:
type: string
description: 'Must not be greater than 96 characters.'
example: z
nullable: true
shipping_state_code:
type: string
description: ''
example: 1
enum:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 26
- 27
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 96
- 97
- 99
nullable: true
shipping_region:
type: string
description: 'State, province, emirate, or other first-level region for an overseas shipping address. Ignored for contacts in India. Must not be greater than 96 characters.'
example: Dubai
nullable: true
shipping_pincode:
type: string
description: 'Must be 6 digits.'
example: '569775'
nullable: true
shipping_postal_code:
type: string
description: 'Postal or ZIP code for an overseas shipping address. Ignored for contacts in India. Must not be greater than 32 characters.'
example: 'SW1A 1AA'
nullable: true
price_list_id:
type: integer
description: 'Rate card this party buys at. Item lines raised for the party take their rate from it unless the request quotes one. Requires the party pricing feature; leave empty to bill at the item master rate.'
example: 7
nullable: true
default_discount_percent:
type: number
description: 'Blanket percentage off whatever rate the party would otherwise pay, applied after the rate card. Defaults to 0. Must be at least 0. Must not be greater than 100.'
example: 2
nullable: true
credit_limit:
type: number
description: 'Most this customer may owe at any moment, in rupees. Omit or send null for no limit; send 0 to put the party on cash only. Ignored for supplier contacts. A sales invoice or manual khata credit that would take the receivable balance past it is refused with HTTP 422. Must be at least 0. Must not be greater than 999999999.'
example: 50000
nullable: true
opening_balance:
type: number
description: 'Must be at least 0. Must not be greater than 999999999.'
example: 22
nullable: true
opening_balance_side:
type: string
description: ''
example: receivable
enum:
- receivable
- payable
opening_balance_date:
type: string
description: 'Must be a valid date.'
example: '2026-01-15'
nullable: true
additional_contacts:
type: array
description: ''
example:
- name: 'Suresh Gupta'
designation: Accounts
phone: '9811111111'
email: accounts@example.com
items:
type: object
properties:
name:
type: string
description: 'Must not be greater than 128 characters.'
example: 'Suresh Gupta'
designation:
type: string
description: 'Must not be greater than 96 characters.'
example: Accounts
nullable: true
phone:
type: string
description: 'Must not be greater than 32 characters.'
example: '9811111111'
nullable: true
email:
type: string
description: 'Must be a valid email address. Must not be greater than 255 characters.'
example: accounts@example.com
nullable: true
required:
- name
photo:
type: string
format: binary
description: 'Optional private JPEG, PNG or WebP profile photo, up to 2 MB and 8192 pixels per side. Multipart upload. Omit to retain the saved photo; null does not remove it. Clients may crop before uploading. Must be an image. Must not be greater than 2048 kilobytes.'
nullable: true
remove_photo:
type: boolean
description: 'Set true to remove the saved photo. Cannot be combined with a photo upload. Defaults to false.'
example: false
required:
- type
- profile_type
- name
- gst_treatment
- opening_balance_side
delete:
summary: ''
operationId: deleteApiV1BusinessesBusinessContactsId
description: ''
parameters: []
responses: {}
tags:
- Contacts
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
-
in: path
name: id
description: 'The ID of the contact.'
example: 1
required: true
schema:
type: integer
'/api/v1/businesses/{business}/contact-imports/template':
get:
summary: 'Download the party import template.'
operationId: downloadThePartyImportTemplate
description: "A CSV carrying the documented headers and two example rows. The headers are not the only\nones accepted — common labels from other billing software are recognised too, and a\n`column_map` on upload settles anything that cannot be guessed."
parameters: []
responses:
200:
description: 'The party import template as CSV.'
content:
application/octet-stream:
schema:
type: string
format: binary
tags:
- Contacts
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
'/api/v1/businesses/{business}/contact-imports':
post:
summary: 'Upload a party file and stage it for review.'
operationId: uploadAPartyFileAndStageItForReview
description: "Creates no contacts. The file is stored, checksummed, and checked row by row away from this\nrequest, so the response may come back still `uploaded`; poll the read endpoint until the\nstatus settles on `validated` or `invalid`.\n\n`profile_type` and `gst_treatment` need not be in the file. A column stating either always\nwins; otherwise a row with a GSTIN is taken as a registered business, one with a company\nname as an unregistered business, and one with neither as a walk-in consumer.\n\nAn `Other Contacts` column lists a business party's additional contacts in one cell, each\nperson as `Name | Role | Phone | Email` and people separated by `;`. A file without the\ncolumn leaves an updated party's additional contacts alone; with it, the cell replaces them,\nand a blank cell removes them."
parameters: []
responses: {}
tags:
- Contacts
requestBody:
required: true
content:
multipart/form-data:
schema:
type: object
properties:
file:
type: string
format: binary
description: "The party list, `.csv` or `.xlsx`, up to 5 MB and 5,000 rows. The format is detected from the file's contents, not its name."
duplicate_mode:
type: string
description: 'What to do with a row naming a party the books already hold: `skip`, `update`, or `create`. Defaults to `skip`.'
example: skip
nullable: true
column_map:
type: object
description: 'Our field name to the column heading in your file, for a file whose headings cannot be guessed.'
example:
name: 'Party Name'
phone: 'Mobile No'
properties: {}
required:
- file
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
'/api/v1/businesses/{business}/contact-imports/{contactImport_uuid}':
get:
summary: 'Read a staged import.'
operationId: readAStagedImport
description: "Returns the status, the row counts, every validation error with its row number in the\nsheet, and a preview sample saying what each row would do."
parameters: []
responses:
401:
description: ''
content:
application/json:
schema:
type: object
example:
message: Unauthenticated.
properties:
message:
type: string
example: Unauthenticated.
tags:
- Contacts
delete:
summary: 'Discard a staged import.'
operationId: discardAStagedImport
description: "Removes the stored file and its checked rows. An import that has already created parties\ncannot be discarded, because it is the record of what was loaded."
parameters: []
responses:
409:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'An imported file cannot be discarded.'
properties:
message:
type: string
example: 'An imported file cannot be discarded.'
tags:
- Contacts
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
-
in: path
name: contactImport_uuid
description: ''
example: 6ff8f7f6-1eb3-3525-be4a-3932c805afed
required: true
schema:
type: string
'/api/v1/businesses/{business}/contact-imports/{contactImport_uuid}/commit':
post:
summary: 'Create and update every checked row.'
operationId: createAndUpdateEveryCheckedRow
description: "The whole file is written in one transaction: a failure anywhere leaves the party list\nexactly as it was. Rows carrying an opening balance record a dated khata entry, never a\nsilent balance on the record."
parameters: []
responses:
409:
description: ''
content:
application/json:
schema:
oneOf:
-
description: ''
type: object
example:
message: 'This file has not passed validation.'
properties:
message:
type: string
example: 'This file has not passed validation.'
-
description: ''
type: object
example:
message: 'This file has already been imported.'
properties:
message:
type: string
example: 'This file has already been imported.'
tags:
- Contacts
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
-
in: path
name: contactImport_uuid
description: ''
example: 6ff8f7f6-1eb3-3525-be4a-3932c805afed
required: true
schema:
type: string
'/api/v1/businesses/{business}/ledger-entries':
get:
summary: ''
operationId: getApiV1BusinessesBusinessLedgerEntries
description: ''
parameters: []
responses:
401:
description: ''
content:
application/json:
schema:
type: object
example:
message: Unauthenticated.
properties:
message:
type: string
example: Unauthenticated.
tags:
- 'Khata ledger'
requestBody:
required: false
content:
application/json:
schema:
type: object
properties:
contact_id:
type: integer
description: ''
example: 16
nullable: true
kind:
type: string
description: 'Must not be greater than 32 characters.'
example: 'n'
nullable: true
from:
type: string
description: 'Must be a valid date.'
example: '2026-01-15'
nullable: true
to:
type: string
description: 'Must be a valid date. Must be a date after or equal to from.'
example: '2026-01-15'
nullable: true
per_page:
type: integer
description: 'Must be at least 1. Must not be greater than 100.'
example: 22
nullable: true
post:
summary: 'Record a manual khata adjustment.'
operationId: recordAManualKhataAdjustment
description: 'The receivable or payable adjustment is offset to Owner equity. Use invoice, purchase, expense, and payment endpoints for operational transactions.'
parameters: []
responses: {}
tags:
- 'Khata ledger'
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
contact_id:
type: integer
description: ''
example: 16
kind:
type: string
description: ''
example: customer_credit
enum:
- customer_credit
- customer_payment
- supplier_credit
- supplier_payment
amount:
type: number
description: 'Must not be greater than 999999999.'
example: 22
occurred_on:
type: string
description: 'Must be a valid date.'
example: '2026-01-15'
reference:
type: string
description: 'Must not be greater than 64 characters.'
example: g
nullable: true
note:
type: string
description: 'Must not be greater than 1000 characters.'
example: z
nullable: true
required:
- contact_id
- kind
- amount
- occurred_on
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
'/api/v1/businesses/{business}/ledger-entries/{ledgerEntry_id}':
get:
summary: ''
operationId: getApiV1BusinessesBusinessLedgerEntriesLedgerEntry_id
description: ''
parameters: []
responses:
401:
description: ''
content:
application/json:
schema:
type: object
example:
message: Unauthenticated.
properties:
message:
type: string
example: Unauthenticated.
tags:
- 'Khata ledger'
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
-
in: path
name: ledgerEntry_id
description: 'The ID of the ledgerEntry.'
example: 1
required: true
schema:
type: integer
'/api/v1/businesses/{business}/ledger-entries/{ledgerEntry_id}/reverse':
post:
summary: 'Reverse a manual khata adjustment and its journal.'
operationId: reverseAManualKhataAdjustmentAndItsJournal
description: ''
parameters: []
responses: {}
tags:
- 'Khata ledger'
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
reason:
type: string
description: 'Must be at least 3 characters. Must not be greater than 255 characters.'
example: b
required:
- reason
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
-
in: path
name: ledgerEntry_id
description: 'The ID of the ledgerEntry.'
example: 1
required: true
schema:
type: integer
'/api/v1/businesses/{business}/items/{item_id}/transactions':
get:
summary: 'List item transactions, newest first.'
operationId: listItemTransactionsNewestFirst
description: "Includes stock movements and invoices, purchase invoices, and returns with no\nrecorded movement for this item, including untracked goods and services.\nDocuments are restricted by tenant, workspace role, and plan. A document with\na stock movement is represented only by its movements.\nFully cancelled manual adjustments and their exact same-date, same-warehouse\nreversal are omitted; their audit records remain stored."
parameters:
-
in: query
name: limit
description: 'Number of transactions to return, 1-100. Defaults to 25.'
example: 10
required: false
schema:
type: integer
description: 'Number of transactions to return, 1-100. Defaults to 25.'
example: 10
responses:
401:
description: ''
content:
application/json:
schema:
type: object
example:
message: Unauthenticated.
properties:
message:
type: string
example: Unauthenticated.
tags:
- Inventory
requestBody:
required: false
content:
application/json:
schema:
type: object
properties:
limit:
type: integer
description: 'Must be at least 1. Must not be greater than 100.'
example: 1
nullable: true
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
-
in: path
name: item_id
description: 'The ID of the item.'
example: 1
required: true
schema:
type: integer
'/api/v1/businesses/{business}/items/{item_id}/ledger':
get:
summary: 'Item stock ledger.'
operationId: itemStockLedger
description: "Effective stock movements of the item with their source documents, party, quantity in and out,\nthe stock held straight after it, and its cost. Closing stock is anchored to the item's\ncurrent stock, so the newest row always matches `stock_quantity`. `meta.summary` totals\nthe filtered movements. Document links respect workspace role and plan: `web_url` is null\nwhen the caller may not open that document.\nFully cancelled manual adjustments and their exact same-date, same-warehouse reversal\nare omitted from rows, totals and closing balances. The original audit records remain.\nReal opening stock, active adjustments, sales voids and invoice revisions remain visible."
parameters:
-
in: query
name: from
description: 'date Movements on or after this date.'
example: '2026-09-01'
required: false
schema:
type: string
description: 'date Movements on or after this date.'
example: '2026-09-01'
-
in: query
name: to
description: 'date Movements on or before this date.'
example: '2026-09-30'
required: false
schema:
type: string
description: 'date Movements on or before this date.'
example: '2026-09-30'
-
in: query
name: type
description: 'Narrow to one document type: sale, purchase, sales_return, purchase_return, delivery_challan, adjustment, opening, or transfer.'
example: purchase
required: false
schema:
type: string
description: 'Narrow to one document type: sale, purchase, sales_return, purchase_return, delivery_challan, adjustment, opening, or transfer.'
example: purchase
-
in: query
name: warehouse
description: "Only movements in this warehouse. Closing stock then becomes that warehouse's balance."
example: 1
required: false
schema:
type: integer
description: "Only movements in this warehouse. Closing stock then becomes that warehouse's balance."
example: 1
-
in: query
name: sort
description: 'date (default), unit_cost, or total_cost.'
example: date
required: false
schema:
type: string
description: 'date (default), unit_cost, or total_cost.'
example: date
-
in: query
name: direction
description: 'desc (default) or asc.'
example: desc
required: false
schema:
type: string
description: 'desc (default) or asc.'
example: desc
-
in: query
name: per_page
description: 'Rows per page, 1-100. Defaults to 25.'
example: 25
required: false
schema:
type: integer
description: 'Rows per page, 1-100. Defaults to 25.'
example: 25
-
in: query
name: page
description: 'Page number.'
example: 1
required: false
schema:
type: integer
description: 'Page number.'
example: 1
responses:
200:
description: ''
content:
application/json:
schema:
type: object
example:
data:
-
id: 1
occurred_on: '2026-10-02'
movement_type: opening
label: 'Opening stock'
documents: []
party: null
stock_in: 12.5
stock_out: 0
closing_stock: 12.5
unit: pcs
unit_cost_paise: 8000
total_cost_paise: 100000
batch_number: null
warehouse:
id: 1
name: 'Main warehouse'
location: null
reason: 'Opening stock'
can_edit_opening_stock: true
meta:
current_page: 1
per_page: 25
total: 1
summary:
in_quantity: 12.5
in_value_paise: 100000
out_quantity: 0
out_value_paise: 0
net_quantity: 12.5
net_value_paise: 100000
count: 1
stock_quantity: 12.5
track_inventory: true
properties:
data:
type: array
example:
-
id: 1
occurred_on: '2026-10-02'
movement_type: opening
label: 'Opening stock'
documents: []
party: null
stock_in: 12.5
stock_out: 0
closing_stock: 12.5
unit: pcs
unit_cost_paise: 8000
total_cost_paise: 100000
batch_number: null
warehouse:
id: 1
name: 'Main warehouse'
location: null
reason: 'Opening stock'
can_edit_opening_stock: true
items:
type: object
properties:
id:
type: integer
example: 1
occurred_on:
type: string
example: '2026-10-02'
movement_type:
type: string
example: opening
label:
type: string
example: 'Opening stock'
documents:
type: array
example: []
party:
type: string
example: null
nullable: true
stock_in:
type: number
example: 12.5
stock_out:
type: integer
example: 0
closing_stock:
type: number
example: 12.5
unit:
type: string
example: pcs
unit_cost_paise:
type: integer
example: 8000
total_cost_paise:
type: integer
example: 100000
batch_number:
type: string
example: null
nullable: true
warehouse:
type: object
properties:
id:
type: integer
example: 1
name:
type: string
example: 'Main warehouse'
location:
type: string
example: null
nullable: true
reason:
type: string
example: 'Opening stock'
can_edit_opening_stock:
type: boolean
example: true
meta:
type: object
properties:
current_page:
type: integer
example: 1
per_page:
type: integer
example: 25
total:
type: integer
example: 1
summary:
type: object
properties:
in_quantity:
type: number
example: 12.5
description: 'Total effective quantity received in the filtered range, excluding fully cancelled manual adjustment pairs.'
in_value_paise:
type: integer
example: 100000
description: 'Stock cost of what was received.'
out_quantity:
type: integer
example: 0
description: 'Total quantity issued in the filtered range.'
out_value_paise:
type: integer
example: 0
description: 'Stock cost of what was issued.'
net_quantity:
type: number
example: 12.5
description: 'in_quantity minus out_quantity.'
net_value_paise:
type: integer
example: 100000
description: 'in_value_paise minus out_value_paise.'
count:
type: integer
example: 1
description: 'Number of movements in the filtered range. Without a warehouse filter, stock transfers are left out of the totals because they net to zero; filter by `type=transfer` or a warehouse to count them.'
stock_quantity:
type: number
example: 12.5
track_inventory:
type: boolean
example: true
tags:
- Inventory
requestBody:
required: false
content:
application/json:
schema:
type: object
properties:
per_page:
type: integer
description: 'Must be at least 1. Must not be greater than 100.'
example: 1
nullable: true
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
-
in: path
name: item_id
description: 'The ID of the item.'
example: 1
required: true
schema:
type: integer
'/api/v1/businesses/{business}/items/{item_id}/opening-stock/{movement_id}':
patch:
summary: 'Correct opening stock.'
operationId: correctOpeningStock
description: "Replaces this opening entry's quantity and cost, retaining its date, warehouse, and batch.\nStock and balanced Inventory / Owner equity journals are corrected atomically. Original\njournals and before/after values remain in the audit trail. No current-stock adjustment\nis created. Zero quantity removes the entry from ledger rows and totals. Corrections that\nwould make subsequent item, warehouse, or batch stock negative return 422. Only item-sourced,\nunreversed opening entries are supported; serialised items and stock placed into batches\nafter opening must use their unit/batch workflows. Requires inventory feature and permission."
parameters: []
responses:
200:
description: ''
content:
application/json:
schema:
type: object
example:
data:
id: 1
quantity: 12.5
unit_cost_paise: 8000
total_cost_paise: 100000
stock_quantity: 12.5
properties:
data:
type: object
properties:
id:
type: integer
example: 1
quantity:
type: number
example: 12.5
unit_cost_paise:
type: integer
example: 8000
total_cost_paise:
type: integer
example: 100000
stock_quantity:
type: number
example: 12.5
422:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'This opening quantity is needed by later stock movements.'
errors:
quantity:
- 'This opening quantity is needed by later stock movements.'
properties:
message:
type: string
example: 'This opening quantity is needed by later stock movements.'
errors:
type: object
properties:
quantity:
type: array
example:
- 'This opening quantity is needed by later stock movements.'
items:
type: string
tags:
- Inventory
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
quantity:
type: number
description: 'Corrected opening quantity, with at most three decimal places. Zero removes the entry from the visible ledger. Must be at least 0. Must not be greater than 999999999.'
example: 12.5
unit_cost:
type: number
description: 'Cost per opening unit in rupees, with at most two decimal places. The original date, warehouse, and batch are retained. Must be at least 0. Must not be greater than 999999999.'
example: 80
reason:
type: string
description: 'Reason retained with the old and new values in the audit trail. Must not be greater than 500 characters.'
example: 'Corrected the initial stock count'
required:
- quantity
- unit_cost
- reason
delete:
summary: 'Remove opening stock.'
operationId: removeOpeningStock
description: "Sets this opening entry's quantity and value to zero and omits it from ledger rows,\ntransaction history, and totals. Adjusts current stock in its original warehouse and batch,\nreverses its opening journal on the original date, and retains the audit trail. Does not\ndelete purchases or sales. The same eligibility and nonnegative-stock rules as correction\napply. Repeating removal is safe. Requires inventory feature and permission."
parameters: []
responses:
204:
description: ''
content:
application/json:
schema:
type: object
example: {}
properties: {}
tags:
- Inventory
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
reason:
type: string
description: 'Reason recorded in the audit trail, maximum 500 characters.'
example: 'Opening stock was entered twice'
required:
- reason
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
-
in: path
name: item_id
description: 'The ID of the item.'
example: 1
required: true
schema:
type: integer
-
in: path
name: movement_id
description: "Opening stock movement ID from the item's ledger."
example: 1
required: true
schema:
type: integer
'/api/v1/businesses/{business}/items/{item_id}/suppliers':
get:
summary: "List an item's suppliers."
operationId: listAnItemsSuppliers
description: "Linked suppliers and every supplier a purchase invoice shows the item was bought from,\npreferred first, then linked, then most recently bought from. A supplier seen only in\npurchase history has `linked` false and a null `id`; link it with the create endpoint."
parameters: []
responses:
401:
description: ''
content:
application/json:
schema:
type: object
example:
message: Unauthenticated.
properties:
message:
type: string
example: Unauthenticated.
tags:
- Inventory
post:
summary: 'Link a supplier to an item.'
operationId: linkASupplierToAnItem
description: "Marking a supplier preferred clears the flag on the item's other suppliers."
parameters: []
responses:
422:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'This supplier is already linked to the item.'
errors:
contact_id:
- 'This supplier is already linked to the item.'
properties:
message:
type: string
example: 'This supplier is already linked to the item.'
errors:
type: object
properties:
contact_id:
type: array
example:
- 'This supplier is already linked to the item.'
items:
type: string
tags:
- Inventory
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
contact_id:
type: integer
description: 'Supplier or customer-and-supplier contact.'
example: 17
supplier_sku:
type: string
description: "The supplier's own code for the item."
example: MC-TAP-01
nullable: true
purchase_price:
type: number
description: 'Agreed rate in rupees.'
example: 110.5
nullable: true
minimum_order_quantity:
type: number
description: "Minimum order in the item's unit."
example: 12.0
nullable: true
lead_time_days:
type: integer
description: 'Days from order to delivery, 0-365.'
example: 7
nullable: true
is_preferred:
type: boolean
description: "Use this supplier's rate when no supplier is chosen."
example: true
notes:
type: string
description: 'Free-text notes, up to 1000 characters.'
example: 'Delivers on Tuesdays.'
nullable: true
required:
- contact_id
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
-
in: path
name: item_id
description: 'The ID of the item.'
example: 1
required: true
schema:
type: integer
'/api/v1/businesses/{business}/items/{item_id}/suppliers/{itemSupplier_id}':
patch:
summary: 'Update the terms agreed with a supplier.'
operationId: updateTheTermsAgreedWithASupplier
description: 'Only the fields sent change. Send a field as null to clear it.'
parameters: []
responses: {}
tags:
- Inventory
requestBody:
required: false
content:
application/json:
schema:
type: object
properties:
contact_id:
type: integer
description: 'Must be at least 1.'
example: 16
supplier_sku:
type: string
description: 'Must not be greater than 64 characters.'
example: 'n'
nullable: true
purchase_price:
type: number
description: 'Agreed rate in rupees.'
example: 108.0
nullable: true
minimum_order_quantity:
type: number
description: 'Must be at least 0. Must not be greater than 99999999999.'
example: 16
nullable: true
lead_time_days:
type: integer
description: 'Days from order to delivery, 0-365.'
example: 5
nullable: true
is_preferred:
type: boolean
description: ''
example: true
notes:
type: string
description: 'Must not be greater than 1000 characters.'
example: i
nullable: true
delete:
summary: 'Unlink a supplier from an item.'
operationId: unlinkASupplierFromAnItem
description: "Purchase history is kept, so the supplier still appears in the list with `linked` false\nif a purchase invoice shows the item was bought from them."
parameters: []
responses: {}
tags:
- Inventory
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
-
in: path
name: item_id
description: 'The ID of the item.'
example: 1
required: true
schema:
type: integer
-
in: path
name: itemSupplier_id
description: 'The ID of the itemSupplier.'
example: 1
required: true
schema:
type: integer
'/api/v1/businesses/{business}/warehouses':
get:
summary: 'List warehouses.'
operationId: listWarehouses
description: 'Default first, then by name, including inactive ones.'
parameters: []
responses:
401:
description: ''
content:
application/json:
schema:
type: object
example:
message: Unauthenticated.
properties:
message:
type: string
example: Unauthenticated.
tags:
- Inventory
post:
summary: 'Create a warehouse.'
operationId: createAWarehouse
description: 'Marking it default takes the flag off the current default.'
parameters: []
responses:
422:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'Another warehouse already has this name.'
errors:
name:
- 'Another warehouse already has this name.'
properties:
message:
type: string
example: 'Another warehouse already has this name.'
errors:
type: object
properties:
name:
type: array
example:
- 'Another warehouse already has this name.'
items:
type: string
tags:
- Inventory
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
name:
type: string
description: 'Unique within the business, up to 100 characters.'
example: 'Sanitary godown'
location:
type: string
description: 'Short location, up to 150 characters.'
example: 'Gomaty Nagar'
nullable: true
address:
type: string
description: 'Optional address, up to 1000 characters.'
example: '12 Station Road, Lucknow'
nullable: true
is_default:
type: boolean
description: 'Make this the default warehouse.'
example: false
is_active:
type: boolean
description: ''
example: false
required:
- name
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
'/api/v1/businesses/{business}/warehouses/{id}':
put:
summary: 'Update a warehouse.'
operationId: updateAWarehouse
description: "Only the fields sent change. The default warehouse cannot be deactivated or un-defaulted\ndirectly: make another warehouse the default instead. A warehouse holding stock cannot be\ndeactivated until its stock is transferred out."
parameters: []
responses: {}
tags:
- Inventory
requestBody:
required: false
content:
application/json:
schema:
type: object
properties:
name:
type: string
description: 'Must not be greater than 100 characters.'
example: b
location:
type: string
description: 'Must not be greater than 150 characters.'
example: 'n'
nullable: true
address:
type: string
description: 'Must not be greater than 1000 characters.'
example: g
nullable: true
is_default:
type: boolean
description: ''
example: false
is_active:
type: boolean
description: ''
example: false
delete:
summary: 'Delete a warehouse.'
operationId: deleteAWarehouse
description: "Only a warehouse that is not the default and never held stock can be deleted; otherwise\ndeactivate it."
parameters: []
responses:
422:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'This warehouse has stock history. Deactivate it instead.'
errors:
warehouse:
- 'This warehouse has stock history. Deactivate it instead.'
properties:
message:
type: string
example: 'This warehouse has stock history. Deactivate it instead.'
errors:
type: object
properties:
warehouse:
type: array
example:
- 'This warehouse has stock history. Deactivate it instead.'
items:
type: string
tags:
- Inventory
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
-
in: path
name: id
description: 'The ID of the warehouse.'
example: 1
required: true
schema:
type: integer
'/api/v1/businesses/{business}/items/{item_id}/stock-transfers':
post:
summary: 'Transfer stock between warehouses.'
operationId: transferStockBetweenWarehouses
description: "Moves stock of one item from one warehouse to another. It records a `transfer_out` and a\n`transfer_in` movement in the item ledger; the item's total stock, cost, and accounts are\nunchanged. The source warehouse must hold the quantity."
parameters: []
responses:
422:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'Main warehouse holds only 2 PCS of Tap.'
errors:
quantity:
- 'Main warehouse holds only 2 PCS of Tap.'
properties:
message:
type: string
example: 'Main warehouse holds only 2 PCS of Tap.'
errors:
type: object
properties:
quantity:
type: array
example:
- 'Main warehouse holds only 2 PCS of Tap.'
items:
type: string
tags:
- Inventory
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
from_warehouse_id:
type: integer
description: 'Active warehouse the stock leaves.'
example: 1
to_warehouse_id:
type: integer
description: 'A different active warehouse.'
example: 2
quantity:
type: number
description: 'More than 0, up to three decimals.'
example: 5.0
transferred_on:
type: date
description: 'Defaults to today; cannot be in the future.'
example: '2026-09-29'
nullable: true
note:
type: string
description: 'Up to 255 characters.'
example: 'Moved for the weekend sale'
nullable: true
item_batch_id:
type: integer
description: 'For a batch-tracked item, the one lot to move. Left out, lots in the source warehouse move earliest expiry first.'
example: 3
nullable: true
serial_numbers:
type: array
description: 'For a serial-tracked item, the units to move, one per unit of quantity. Left out, the oldest units in the source warehouse move.'
example:
- IMEI-001
items:
type: string
required:
- from_warehouse_id
- to_warehouse_id
- quantity
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
-
in: path
name: item_id
description: 'The ID of the item.'
example: 1
required: true
schema:
type: integer
'/api/v1/businesses/{business}/items/{item_id}/batches':
get:
summary: "List an item's batches."
operationId: listAnItemsBatches
description: "Ordered first-expiry-first-out, so the first sellable row is the lot the counter should\npre-select. Expired lots are listed — the shop still has to see and clear them — but\ncarry `is_expired`, and they can never be billed."
parameters:
-
in: query
name: in_stock
description: 'Only batches still holding stock.'
example: true
required: false
schema:
type: boolean
description: 'Only batches still holding stock.'
example: true
-
in: query
name: sellable
description: 'Only batches that can be billed today: in stock, not expired, not on hold or recalled, and — when the business blocks short expiry — clear of the minimum shelf life.'
example: true
required: false
schema:
type: boolean
description: 'Only batches that can be billed today: in stock, not expired, not on hold or recalled, and — when the business blocks short expiry — clear of the minimum shelf life.'
example: true
responses:
401:
description: ''
content:
application/json:
schema:
type: object
example:
message: Unauthenticated.
properties:
message:
type: string
example: Unauthenticated.
tags:
- Inventory
requestBody:
required: false
content:
application/json:
schema:
type: object
properties:
in_stock:
type: boolean
description: ''
example: false
nullable: true
sellable:
type: boolean
description: ''
example: false
nullable: true
post:
summary: 'Open a batch, optionally with the stock already on the shelf.'
operationId: openABatchOptionallyWithTheStockAlreadyOnTheShelf
description: "Batches are usually created implicitly by a purchase invoice. This is for opening stock\nand for a lot the shop is recording by hand. A positive `quantity` records an opening stock\nmovement and its inventory journal, exactly as opening stock on the item master does."
parameters: []
responses:
422:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'Turn on batch tracking for this item first.'
errors:
item_id:
- 'Turn on batch tracking for this item first.'
properties:
message:
type: string
example: 'Turn on batch tracking for this item first.'
errors:
type: object
properties:
item_id:
type: array
example:
- 'Turn on batch tracking for this item first.'
items:
type: string
tags:
- Inventory
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
warehouse_id:
type: integer
description: 'Must be at least 1.'
example: 16
nullable: true
batch_number:
type: string
description: 'Must not be greater than 64 characters.'
example: 'n'
expiry_date:
type: string
description: 'Must be a valid date.'
example: '2026-01-15'
manufactured_on:
type: string
description: 'Must be a valid date.'
example: '2026-01-15'
nullable: true
mrp:
type: number
description: 'Must be at least 0. Must not be greater than 999999999.'
example: 7
nullable: true
purchase_price:
type: number
description: 'Must be at least 0. Must not be greater than 999999999.'
example: 16
nullable: true
quantity:
type: number
description: 'Must be at least 0. Must not be greater than 999999999.'
example: 17
nullable: true
required:
- batch_number
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
-
in: path
name: item_id
description: 'The ID of the item.'
example: 1
required: true
schema:
type: integer
'/api/v1/businesses/{business}/items/{item_id}/batches/{itemBatch_id}':
patch:
summary: 'Correct a batch, and adjust the stock it holds.'
operationId: correctABatchAndAdjustTheStockItHolds
description: "A changed `quantity` records an inventory adjustment for the difference, the same journal an\nitem-master stock correction records, so the books follow the shelf.\n\n`status` pulls the lot from sale or puts it back. `on_hold` and `recalled` both stop it being\nbilled — on invoices, POS, and delivery challans — while the stock stays on the shelf and in\nthe books; a `status_reason` is required for either and is shown to whoever tries to sell\nthe lot. `available` releases it. Sales returns into the lot and purchase returns out of it\nstill work, so recalled stock can go back to the supplier."
parameters: []
responses:
422:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'Say why this batch is being held, so the counter knows.'
errors:
status_reason:
- 'Say why this batch is being held, so the counter knows.'
properties:
message:
type: string
example: 'Say why this batch is being held, so the counter knows.'
errors:
type: object
properties:
status_reason:
type: array
example:
- 'Say why this batch is being held, so the counter knows.'
items:
type: string
tags:
- Inventory
requestBody:
required: false
content:
application/json:
schema:
type: object
properties:
warehouse_id:
type: integer
description: 'Must be at least 1.'
example: 16
nullable: true
batch_number:
type: string
description: 'Must not be greater than 64 characters.'
example: 'n'
expiry_date:
type: string
description: 'Must be a valid date.'
example: '2026-01-15'
manufactured_on:
type: string
description: 'Must be a valid date.'
example: '2026-01-15'
nullable: true
mrp:
type: number
description: 'Must be at least 0. Must not be greater than 999999999.'
example: 7
nullable: true
purchase_price:
type: number
description: 'Must be at least 0. Must not be greater than 999999999.'
example: 16
nullable: true
quantity:
type: number
description: 'Must be at least 0. Must not be greater than 999999999.'
example: 17
status:
type: string
description: '`available`, `on_hold`, or `recalled`.'
example: on_hold
status_reason:
type: string
description: 'Why the lot is held or recalled, up to 255 characters. Required with `on_hold` or `recalled`.'
example: 'Customer complaint, checking the carton'
nullable: true
delete:
summary: 'Remove an empty batch.'
operationId: removeAnEmptyBatch
description: "A lot that ever held stock is part of the audit trail, so only an empty batch with no\nmovements against it can be deleted. Everything else stays and simply reads as zero."
parameters: []
responses: {}
tags:
- Inventory
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
-
in: path
name: item_id
description: 'The ID of the item.'
example: 1
required: true
schema:
type: integer
-
in: path
name: itemBatch_id
description: 'The ID of the itemBatch.'
example: 1
required: true
schema:
type: integer
'/api/v1/businesses/{business}/items/{item_id}/batches/{itemBatch_id}/write-offs':
post:
summary: 'Write off stock from a batch.'
operationId: writeOffStockFromABatch
description: "For goods leaving without a sale — expired, damaged, or recalled stock being destroyed. The\nquantity leaves the lot and the item, a `write_off` stock movement is recorded, and the cost\nat the item's weighted average moves from Inventory to the Stock written off expense\naccount. Any lot may be written off, including an expired, held, or recalled one. It cannot\nbe undone; stock found again is added back with a batch quantity correction."
parameters: []
responses:
201:
description: ''
content:
application/json:
schema:
type: object
example:
data:
batch:
id: 18
batch_number: AMX-2291
quantity: 0
status: available
sale_state: empty
is_sellable: false
write_off:
quantity: 4
value_paise: 32000
reason: expired
note: null
stock_movement_id: 311
occurred_on: '2026-09-28'
properties:
data:
type: object
properties:
batch:
type: object
properties:
id:
type: integer
example: 18
batch_number:
type: string
example: AMX-2291
quantity:
type: integer
example: 0
status:
type: string
example: available
sale_state:
type: string
example: empty
is_sellable:
type: boolean
example: false
description: 'The lot after the write-off, in the same shape as the batch list.'
write_off:
type: object
properties:
quantity:
type: integer
example: 4
description: 'Quantity removed.'
value_paise:
type: integer
example: 32000
description: 'Loss booked at cost, in paise.'
reason:
type: string
example: expired
description: 'The reason code sent.'
note:
type: string
example: null
nullable: true
stock_movement_id:
type: integer
example: 311
description: 'The `write_off` stock movement recorded.'
occurred_on:
type: string
example: '2026-09-28'
422:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'Batch AMX-2291 holds only 4 — you cannot write off more than that.'
errors:
quantity:
- 'Batch AMX-2291 holds only 4 — you cannot write off more than that.'
properties:
message:
type: string
example: 'Batch AMX-2291 holds only 4 — you cannot write off more than that.'
errors:
type: object
properties:
quantity:
type: array
example:
- 'Batch AMX-2291 holds only 4 — you cannot write off more than that.'
items:
type: string
tags:
- Inventory
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
quantity:
type: number
description: 'Quantity to remove, no more than the lot holds.'
example: 4.0
reason:
type: string
description: 'Why: `expired`, `damaged`, `recalled`, or `other`.'
example: expired
note:
type: string
description: 'Free text kept with the movement, up to 255 characters.'
example: "Destroyed with the chemists' association"
nullable: true
required:
- quantity
- reason
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
-
in: path
name: item_id
description: 'The ID of the item.'
example: 1
required: true
schema:
type: integer
-
in: path
name: itemBatch_id
description: 'The ID of the itemBatch.'
example: 1
required: true
schema:
type: integer
'/api/v1/businesses/{business}/items/{item_id}/serials':
get:
summary: "List an item's units."
operationId: listAnItemsUnits
description: "Ordered oldest first, so the shelf clears in the order it filled and the first row is a\nsensible default pick. Sold and written-off units are listed too — the shop still has to\nsee them — but carry `on_shelf: false` and can never be billed."
parameters:
-
in: query
name: on_shelf
description: 'Only units that can be billed: in stock or returned.'
example: true
required: false
schema:
type: boolean
description: 'Only units that can be billed: in stock or returned.'
example: true
-
in: query
name: status
description: 'One exact status: in_stock, sold, returned, or void.'
example: sold
required: false
schema:
type: string
description: 'One exact status: in_stock, sold, returned, or void.'
example: sold
-
in: query
name: q
description: 'Match part of a serial number.'
example: '3567'
required: false
schema:
type: string
description: 'Match part of a serial number.'
example: '3567'
responses:
401:
description: ''
content:
application/json:
schema:
type: object
example:
message: Unauthenticated.
properties:
message:
type: string
example: Unauthenticated.
tags:
- Inventory
requestBody:
required: false
content:
application/json:
schema:
type: object
properties:
on_shelf:
type: boolean
description: ''
example: false
nullable: true
status:
type: string
description: ''
example: null
nullable: true
q:
type: string
description: 'Must not be greater than 64 characters.'
example: b
nullable: true
post:
summary: 'Record units the shop is already holding.'
operationId: recordUnitsTheShopIsAlreadyHolding
description: "Units normally arrive on a purchase invoice, which captures the serial off each box. This\nis for opening stock and for a unit being recorded by hand. Every serial listed records an\nopening stock movement and its inventory journal, exactly as opening stock does."
parameters: []
responses:
422:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'Turn on serial tracking for this item first.'
errors:
item_id:
- 'Turn on serial tracking for this item first.'
properties:
message:
type: string
example: 'Turn on serial tracking for this item first.'
errors:
type: object
properties:
item_id:
type: array
example:
- 'Turn on serial tracking for this item first.'
items:
type: string
tags:
- Inventory
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
warehouse_id:
type: integer
description: 'Must be at least 1.'
example: 16
nullable: true
serial_numbers:
type: array
description: 'One per unit, up to 100 at a time.'
example:
- '356938035643809'
- '356938035643810'
items:
type: string
warranty_until:
type: date
description: 'Cover end date applied to every unit in this call.'
example: '2027-09-13'
nullable: true
received_on:
type: string
description: 'Must be a valid date.'
example: '2026-01-15'
nullable: true
required:
- serial_numbers
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
-
in: path
name: item_id
description: 'The ID of the item.'
example: 1
required: true
schema:
type: integer
'/api/v1/businesses/{business}/item-serials':
get:
summary: 'Search every unit in the catalogue.'
operationId: searchEveryUnitInTheCatalogue
description: "This is the lookup the counter actually uses: a customer walks in with a handset and the\nshop needs to know whether it sold it, to whom, when, and whether cover still runs.\nSerial history and item names remain available after the item is soft deleted."
parameters:
-
in: query
name: q
description: 'Part or all of a serial number.'
example: '356938'
required: false
schema:
type: string
description: 'Part or all of a serial number.'
example: '356938'
-
in: query
name: item_id
description: 'Narrow to one item.'
example: 12
required: false
schema:
type: integer
description: 'Narrow to one item.'
example: 12
-
in: query
name: status
description: 'One exact status: in_stock, sold, returned, or void.'
example: sold
required: false
schema:
type: string
description: 'One exact status: in_stock, sold, returned, or void.'
example: sold
responses:
401:
description: ''
content:
application/json:
schema:
type: object
example:
message: Unauthenticated.
properties:
message:
type: string
example: Unauthenticated.
tags:
- Inventory
requestBody:
required: false
content:
application/json:
schema:
type: object
properties:
q:
type: string
description: 'Must not be greater than 64 characters.'
example: b
nullable: true
item_id:
type: integer
description: ''
example: 16
nullable: true
status:
type: string
description: ''
example: null
nullable: true
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
'/api/v1/businesses/{business}/item-serials/{serial}':
get:
summary: 'One unit by its exact serial number.'
operationId: oneUnitByItsExactSerialNumber
description: "Exact match only, ignoring case and surrounding space — a scanner that silently picks the\nnearest handset is worse than one that says it does not know this unit. A miss answers\n`404` carrying the code back, so the app can offer to record it.\nSerial history and item names remain available after the item is soft deleted."
parameters: []
responses:
401:
description: ''
content:
application/json:
schema:
type: object
example:
message: Unauthenticated.
properties:
message:
type: string
example: Unauthenticated.
404:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'No unit is recorded with this serial number.'
serial_number: '356938035643809'
properties:
message:
type: string
example: 'No unit is recorded with this serial number.'
serial_number:
type: string
example: '356938035643809'
tags:
- Inventory
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
-
in: path
name: serial
description: 'The serial number as scanned.'
example: '356938035643809'
required: true
schema:
type: string
'/api/v1/businesses/{business}/items/{item_id}/serials/{itemSerial_id}':
patch:
summary: 'Correct one unit.'
operationId: correctOneUnit
description: "The serial number and its warranty date can be corrected — a mistyped IMEI is a real and\ncommon problem. Where the unit *is* cannot: that follows the documents it moved on, and a\nstatus typed by hand would put the shelf and the books out of step."
parameters: []
responses: {}
tags:
- Inventory
requestBody:
required: false
content:
application/json:
schema:
type: object
properties:
serial_number:
type: string
description: 'Must not be greater than 64 characters.'
example: b
warranty_until:
type: string
description: 'Must be a valid date.'
example: '2026-01-15'
nullable: true
received_on:
type: string
description: 'Must be a valid date.'
example: '2026-01-15'
nullable: true
delete:
summary: 'Remove a unit that was never sold.'
operationId: removeAUnitThatWasNeverSold
description: "A unit that has been billed is part of the audit trail and stays, however it was later\nreturned. A unit on the shelf can be removed — a serial typed twice, or a box that turned\nout to be empty — and doing so takes it out of stock with an adjustment, not silently."
parameters: []
responses: {}
tags:
- Inventory
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
-
in: path
name: item_id
description: 'The ID of the item.'
example: 1
required: true
schema:
type: integer
-
in: path
name: itemSerial_id
description: 'The ID of the itemSerial.'
example: 1
required: true
schema:
type: integer
'/api/v1/businesses/{business}/items/by-barcode/{code}':
get:
summary: 'One item by its exact barcode.'
operationId: oneItemByItsExactBarcode
description: "The catalogue search is a guess machine: it matches part of a name, part of an SKU, and\nhands back a list. That is right for a person typing \"parle\" and wrong for a scanner. An\napp that silently bills the first of three near-matches costs the shop a wrong bill and a\nstock count that stops tallying, and nobody notices for a month — so this answers with one\nitem or with nothing, and never guesses.\n\nMatching ignores surrounding space and reads a GS1 code the way GS1 does, so the 12-digit\nUPC-A a camera reports and the 13-digit EAN-13 printed on the same pack find the same\nitem. A miss answers 404 carrying the code back, so the app can offer to create an item\nwith it rather than make the shopkeeper retype thirteen digits off a crumpled packet.\n\nLots and units ride along exactly as they do in the POS feed, because a batched or\nserialised item cannot be billed without naming which one leaves, and the counter has to\nwork without signal."
parameters:
-
in: query
name: contact
description: 'Customer id. Adds `party_price`, the rate that party pays under its rate card.'
example: 42
required: false
schema:
type: integer
description: 'Customer id. Adds `party_price`, the rate that party pays under its rate card.'
example: 42
responses:
401:
description: ''
content:
application/json:
schema:
type: object
example:
message: Unauthenticated.
properties:
message:
type: string
example: Unauthenticated.
404:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'No item is recorded with this barcode.'
barcode: '8901234567890'
properties:
message:
type: string
example: 'No item is recorded with this barcode.'
barcode:
type: string
example: '8901234567890'
tags:
- Inventory
requestBody:
required: false
content:
application/json:
schema:
type: object
properties:
contact:
type: integer
description: 'Must be at least 1.'
example: 16
nullable: true
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
-
in: path
name: code
description: 'The barcode as scanned.'
example: '8901234567890'
required: true
schema:
type: string
'/api/v1/businesses/{business}/items/by-qr':
get:
summary: 'One item by the QR sticker the shop printed for it.'
operationId: oneItemByTheQRStickerTheShopPrintedForIt
description: "The companion to scanning a barcode, for the half of the shelf a barcode can never cover.\nA barcode belongs to whoever made the pack, so loose rice, re-packed dal and the shop's\nown mixture have no code to scan and never will; a QR is the shop's own label for its own\ngoods, so Dukanam mints it and the counter can scan it like anything else.\n\nSend the payload exactly as the scanner read it. A Dukanam sticker encodes a URL, so that\nis usually what arrives, but the bare token is accepted too — a client should never have\nto take a URL apart to scan a label. A payload that is not one of ours is a miss, not an\nerror, and is never guessed at: like the barcode endpoint, this answers with one item or\nwith nothing.\n\nThe code travels as a query parameter, not in the path, and that is the whole reason this\nendpoint is shaped differently from `items/by-barcode/{code}`. A barcode is usually digits\nand only rarely holds a slash; a QR payload is a URL and holds slashes every time, and an\nencoded slash in a path segment is refused or quietly collapsed by a fair number of\nproxies. Percent-encode the value as you would any query parameter.\n\nA token is minted per item and never reissued, because once a sticker is on a shelf it is\nout of our hands and a rotated token would strand every label already stuck down. Tokens\nare unique platform-wide but resolved within the workspace in the URL, so one shop's\nsticker cannot read an item out of another shop.\n\nLots and units ride along exactly as they do for a barcode scan, because a batched or\nserialised item cannot be billed without naming which one leaves."
parameters:
-
in: query
name: code
description: 'The QR payload as scanned — the whole URL, or just the token.'
example: 'https://dukanam.com/q/7Fq2bXm9KdLp3RtVw8ZaCe'
required: true
schema:
type: string
description: 'The QR payload as scanned — the whole URL, or just the token.'
example: 'https://dukanam.com/q/7Fq2bXm9KdLp3RtVw8ZaCe'
-
in: query
name: contact
description: 'Customer id. Adds `party_price`, the rate that party pays under its rate card.'
example: 42
required: false
schema:
type: integer
description: 'Customer id. Adds `party_price`, the rate that party pays under its rate card.'
example: 42
responses:
401:
description: ''
content:
application/json:
schema:
type: object
example:
message: Unauthenticated.
properties:
message:
type: string
example: Unauthenticated.
404:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'No item is recorded with this QR code.'
properties:
message:
type: string
example: 'No item is recorded with this QR code.'
tags:
- Inventory
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
code:
type: string
description: 'Must not be greater than 2048 characters.'
example: b
contact:
type: integer
description: 'Must be at least 1.'
example: 22
nullable: true
required:
- code
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
'/api/v1/businesses/{business}/items/barcode-labels':
get:
summary: 'Barcode labels for one item or for many.'
operationId: barcodeLabelsForOneItemOrForMany
description: "Two shapes of the same job. `GET items/{item}/barcode-labels` prints one item's sticker —\nwhat an app offers from an item screen after assigning a barcode. `GET items/barcode-labels?items[]=`\nprints a run of them, which is what a shop actually does: a delivery arrives and forty\nlines need shelf labels before opening. Both are this one endpoint, so a client that\nlearns the options once can print either.\n\n`format=json`, the default, returns the code as an inline SVG per item together with the\nchosen stationery's measurements, so an app can lay the sheet out itself or send one\nlabel to a Bluetooth label printer. `format=pdf` returns the whole sheet already laid out\non the chosen stationery, for handing to the platform print service.\n\n`code_kind` chooses what the sticker carries, and the two are not interchangeable. A\nbarcode reprints a number somebody else issued for a pack they made, so an item that\nholds no barcode cannot have one invented for it and is skipped. A QR carries a token\nDukanam mints for the item, so it labels the loose rice, the re-packed dal and everything\nelse no manufacturer ever numbered — a QR run skips nothing. Scan them back with\n`items/by-barcode/{code}` and `items/by-qr?code=` respectively.\n\nCopies are per item, because a delivery is never uniform: `copies[12]=6&copies[19]=2`\nprints six of one line and two of another. A single `copies=10` prints ten of everything.\nItems not named in the map print once.\n\nAn item that cannot be labelled is never silently dropped from the run. It comes back in\n`meta.skipped` with a reason, so a shopkeeper counting stickers against a delivery note is\ntold which two are missing and why, rather than finding out at the shelf."
parameters:
-
in: query
name: items
description: 'Item ids to label. Required on the collection route, ignored when the URL already names an item. Maximum 200.'
example:
- 12
- 19
required: false
schema:
type: array
description: 'Item ids to label. Required on the collection route, ignored when the URL already names an item. Maximum 200.'
example:
- 12
- 19
items:
type: integer
-
in: query
name: copies
description: 'Copies of every label, 1-100. Send `copies[- ]` instead to set a count per item.'
example: 10
required: false
schema:
type: integer
description: 'Copies of every label, 1-100. Send `copies[
- ]` instead to set a count per item.'
example: 10
-
in: query
name: layout
description: 'Stationery to lay the sheet out on: a4-65, a4-40, a4-24, roll-50x25, roll-38x25 or custom (one sticker per page). Defaults to a4-65.'
example: a4-65
required: false
schema:
type: string
description: 'Stationery to lay the sheet out on: a4-65, a4-40, a4-24, roll-50x25, roll-38x25 or custom (one sticker per page). Defaults to a4-65.'
example: a4-65
-
in: query
name: width_mm
description: 'Required for layout=custom. Sticker and page width in millimetres, 25-200. Ignored for presets.'
example: 50.0
required: false
schema:
type: number
description: 'Required for layout=custom. Sticker and page width in millimetres, 25-200. Ignored for presets.'
example: 50.0
-
in: query
name: height_mm
description: 'Required for layout=custom. Sticker and page height in millimetres, 20-200. Must fit selected text plus a QR of at least 14mm or bars of at least 5mm; otherwise returns 422. Ignored for presets.'
example: 30.0
required: false
schema:
type: number
description: 'Required for layout=custom. Sticker and page height in millimetres, 20-200. Must fit selected text plus a QR of at least 14mm or bars of at least 5mm; otherwise returns 422. Ignored for presets.'
example: 30.0
-
in: query
name: code_kind
description: '`barcode` to reprint the code the item already carries, or `qr` to print a Dukanam QR sticker. Defaults to barcode. A barcode run skips items holding no code; a QR run skips nothing, because the token is minted for the item.'
example: qr
required: false
schema:
type: string
description: '`barcode` to reprint the code the item already carries, or `qr` to print a Dukanam QR sticker. Defaults to barcode. A barcode run skips items holding no code; a QR run skips nothing, because the token is minted for the item.'
example: qr
-
in: query
name: show_name
description: 'Print the item name. Defaults to true.'
example: true
required: false
schema:
type: boolean
description: 'Print the item name. Defaults to true.'
example: true
-
in: query
name: show_price
description: 'Print the selling price. Defaults to true.'
example: true
required: false
schema:
type: boolean
description: 'Print the selling price. Defaults to true.'
example: true
-
in: query
name: show_mrp
description: 'Print the MRP when the item carries one. Defaults to false.'
example: false
required: false
schema:
type: boolean
description: 'Print the MRP when the item carries one. Defaults to false.'
example: false
-
in: query
name: show_sku
description: 'Print the SKU. Defaults to false.'
example: false
required: false
schema:
type: boolean
description: 'Print the SKU. Defaults to false.'
example: false
-
in: query
name: show_business
description: 'Print the shop name. Defaults to false.'
example: false
required: false
schema:
type: boolean
description: 'Print the shop name. Defaults to false.'
example: false
-
in: query
name: format
description: '`json` for per-item SVG, or `pdf` for the laid-out sheet. Defaults to json.'
example: json
required: false
schema:
type: string
description: '`json` for per-item SVG, or `pdf` for the laid-out sheet. Defaults to json.'
example: json
responses:
401:
description: ''
content:
application/json:
schema:
type: object
example:
message: Unauthenticated.
properties:
message:
type: string
example: Unauthenticated.
422:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'That would print 720 labels. Print at most 500 at a time.'
errors:
copies:
- 'That would print 720 labels. Print at most 500 at a time.'
properties:
message:
type: string
example: 'That would print 720 labels. Print at most 500 at a time.'
errors:
type: object
properties:
copies:
type: array
example:
- 'That would print 720 labels. Print at most 500 at a time.'
items:
type: string
tags:
- Inventory
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
items:
type: array
description: 'Must be at least 1.'
example:
- 16
items:
type: integer
copies:
type: array
description: 'Must be at least 1. Must not be greater than 100.'
example:
- 22
items:
type: integer
layout:
type: string
description: ''
example: a4-65
enum:
- a4-65
- a4-40
- a4-24
- roll-50x25
- roll-38x25
- custom
nullable: true
width_mm:
type: number
description: 'Must be between 25 and 200.'
example: 25
height_mm:
type: number
description: 'Must be between 20 and 200.'
example: 21
code_kind:
type: string
description: ''
example: barcode
enum:
- barcode
- qr
nullable: true
show_business:
type: boolean
description: ''
example: false
nullable: true
show_name:
type: boolean
description: ''
example: false
nullable: true
show_sku:
type: boolean
description: ''
example: false
nullable: true
show_price:
type: boolean
description: ''
example: false
nullable: true
show_mrp:
type: boolean
description: ''
example: false
nullable: true
format:
type: string
description: ''
example: json
enum:
- json
- pdf
nullable: true
search:
type: string
description: 'Must not be greater than 128 characters.'
example: m
nullable: true
required:
- width_mm
- height_mm
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
'/api/v1/businesses/{business}/items/{item_id}/barcode-labels':
get:
summary: 'Barcode labels for one item or for many.'
operationId: barcodeLabelsForOneItemOrForMany
description: "Two shapes of the same job. `GET items/{item}/barcode-labels` prints one item's sticker —\nwhat an app offers from an item screen after assigning a barcode. `GET items/barcode-labels?items[]=`\nprints a run of them, which is what a shop actually does: a delivery arrives and forty\nlines need shelf labels before opening. Both are this one endpoint, so a client that\nlearns the options once can print either.\n\n`format=json`, the default, returns the code as an inline SVG per item together with the\nchosen stationery's measurements, so an app can lay the sheet out itself or send one\nlabel to a Bluetooth label printer. `format=pdf` returns the whole sheet already laid out\non the chosen stationery, for handing to the platform print service.\n\n`code_kind` chooses what the sticker carries, and the two are not interchangeable. A\nbarcode reprints a number somebody else issued for a pack they made, so an item that\nholds no barcode cannot have one invented for it and is skipped. A QR carries a token\nDukanam mints for the item, so it labels the loose rice, the re-packed dal and everything\nelse no manufacturer ever numbered — a QR run skips nothing. Scan them back with\n`items/by-barcode/{code}` and `items/by-qr?code=` respectively.\n\nCopies are per item, because a delivery is never uniform: `copies[12]=6&copies[19]=2`\nprints six of one line and two of another. A single `copies=10` prints ten of everything.\nItems not named in the map print once.\n\nAn item that cannot be labelled is never silently dropped from the run. It comes back in\n`meta.skipped` with a reason, so a shopkeeper counting stickers against a delivery note is\ntold which two are missing and why, rather than finding out at the shelf."
parameters:
-
in: query
name: items
description: 'Item ids to label. Required on the collection route, ignored when the URL already names an item. Maximum 200.'
example:
- 12
- 19
required: false
schema:
type: array
description: 'Item ids to label. Required on the collection route, ignored when the URL already names an item. Maximum 200.'
example:
- 12
- 19
items:
type: integer
-
in: query
name: copies
description: 'Copies of every label, 1-100. Send `copies[
- ]` instead to set a count per item.'
example: 10
required: false
schema:
type: integer
description: 'Copies of every label, 1-100. Send `copies[
- ]` instead to set a count per item.'
example: 10
-
in: query
name: layout
description: 'Stationery to lay the sheet out on: a4-65, a4-40, a4-24, roll-50x25, roll-38x25 or custom (one sticker per page). Defaults to a4-65.'
example: a4-65
required: false
schema:
type: string
description: 'Stationery to lay the sheet out on: a4-65, a4-40, a4-24, roll-50x25, roll-38x25 or custom (one sticker per page). Defaults to a4-65.'
example: a4-65
-
in: query
name: width_mm
description: 'Required for layout=custom. Sticker and page width in millimetres, 25-200. Ignored for presets.'
example: 50.0
required: false
schema:
type: number
description: 'Required for layout=custom. Sticker and page width in millimetres, 25-200. Ignored for presets.'
example: 50.0
-
in: query
name: height_mm
description: 'Required for layout=custom. Sticker and page height in millimetres, 20-200. Must fit selected text plus a QR of at least 14mm or bars of at least 5mm; otherwise returns 422. Ignored for presets.'
example: 30.0
required: false
schema:
type: number
description: 'Required for layout=custom. Sticker and page height in millimetres, 20-200. Must fit selected text plus a QR of at least 14mm or bars of at least 5mm; otherwise returns 422. Ignored for presets.'
example: 30.0
-
in: query
name: code_kind
description: '`barcode` to reprint the code the item already carries, or `qr` to print a Dukanam QR sticker. Defaults to barcode. A barcode run skips items holding no code; a QR run skips nothing, because the token is minted for the item.'
example: qr
required: false
schema:
type: string
description: '`barcode` to reprint the code the item already carries, or `qr` to print a Dukanam QR sticker. Defaults to barcode. A barcode run skips items holding no code; a QR run skips nothing, because the token is minted for the item.'
example: qr
-
in: query
name: show_name
description: 'Print the item name. Defaults to true.'
example: true
required: false
schema:
type: boolean
description: 'Print the item name. Defaults to true.'
example: true
-
in: query
name: show_price
description: 'Print the selling price. Defaults to true.'
example: true
required: false
schema:
type: boolean
description: 'Print the selling price. Defaults to true.'
example: true
-
in: query
name: show_mrp
description: 'Print the MRP when the item carries one. Defaults to false.'
example: false
required: false
schema:
type: boolean
description: 'Print the MRP when the item carries one. Defaults to false.'
example: false
-
in: query
name: show_sku
description: 'Print the SKU. Defaults to false.'
example: false
required: false
schema:
type: boolean
description: 'Print the SKU. Defaults to false.'
example: false
-
in: query
name: show_business
description: 'Print the shop name. Defaults to false.'
example: false
required: false
schema:
type: boolean
description: 'Print the shop name. Defaults to false.'
example: false
-
in: query
name: format
description: '`json` for per-item SVG, or `pdf` for the laid-out sheet. Defaults to json.'
example: json
required: false
schema:
type: string
description: '`json` for per-item SVG, or `pdf` for the laid-out sheet. Defaults to json.'
example: json
responses:
401:
description: ''
content:
application/json:
schema:
type: object
example:
message: Unauthenticated.
properties:
message:
type: string
example: Unauthenticated.
422:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'That would print 720 labels. Print at most 500 at a time.'
errors:
copies:
- 'That would print 720 labels. Print at most 500 at a time.'
properties:
message:
type: string
example: 'That would print 720 labels. Print at most 500 at a time.'
errors:
type: object
properties:
copies:
type: array
example:
- 'That would print 720 labels. Print at most 500 at a time.'
items:
type: string
tags:
- Inventory
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
items:
type: array
description: 'Must be at least 1.'
example:
- 16
items:
type: integer
copies:
type: array
description: 'Must be at least 1. Must not be greater than 100.'
example:
- 22
items:
type: integer
layout:
type: string
description: ''
example: a4-65
enum:
- a4-65
- a4-40
- a4-24
- roll-50x25
- roll-38x25
- custom
nullable: true
width_mm:
type: number
description: 'Must be between 25 and 200.'
example: 25
height_mm:
type: number
description: 'Must be between 20 and 200.'
example: 21
code_kind:
type: string
description: ''
example: barcode
enum:
- barcode
- qr
nullable: true
show_business:
type: boolean
description: ''
example: false
nullable: true
show_name:
type: boolean
description: ''
example: false
nullable: true
show_sku:
type: boolean
description: ''
example: false
nullable: true
show_price:
type: boolean
description: ''
example: false
nullable: true
show_mrp:
type: boolean
description: ''
example: false
nullable: true
format:
type: string
description: ''
example: json
enum:
- json
- pdf
nullable: true
search:
type: string
description: 'Must not be greater than 128 characters.'
example: m
nullable: true
required:
- width_mm
- height_mm
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
-
in: path
name: item_id
description: 'The ID of the item.'
example: 1
required: true
schema:
type: integer
'/api/v1/businesses/{business}/items/{item_id}/active':
patch:
summary: 'Take an item out of circulation, or put it back.'
operationId: takeAnItemOutOfCirculationOrPutItBack
description: "Its own endpoint rather than a field on update, which reconciles the submitted quantity\nagainst the shelf and can record a stock adjustment and a journal with it. Retiring a line is\na one-word decision and must not be able to move stock. An inactive item keeps its stock and\nevery document it already sits on; it is simply no longer offered at the counter or when\nraising a new document."
parameters: []
responses: {}
tags:
- Inventory
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
is_active:
type: boolean
description: 'Whether the shop still offers this item.'
example: false
required:
- is_active
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
-
in: path
name: item_id
description: 'The ID of the item.'
example: 1
required: true
schema:
type: integer
'/api/v1/businesses/{business}/items/organisation':
patch:
summary: 'Assign classifications to multiple items atomically.'
operationId: assignClassificationsToMultipleItemsAtomically
description: "Inventory permission required. Omitted fields stay unchanged; null clears that field.\nChanging category clears the old subcategory unless a matching new subcategory is supplied."
parameters: []
responses:
200:
description: ''
content:
application/json:
schema:
type: object
example:
data:
updated: 2
properties:
data:
type: object
properties:
updated:
type: integer
example: 2
tags:
- Inventory
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
item_ids:
type: array
description: 'Up to 100 distinct item IDs owned by this workspace.'
example:
- 1
- 2
items:
type: integer
category_id:
type: integer
description: 'Category ID, or null to clear it.'
example: 1
subcategory_id:
type: integer
description: 'Subcategory belonging to the selected category.'
example: 2
brand_id:
type: integer
description: 'Brand ID, or null to clear it.'
example: 3
manufacturer_id:
type: integer
description: 'Manufacturer ID, or null to clear it.'
example: 4
required:
- item_ids
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
'/api/v1/businesses/{business}/items':
get:
summary: 'List items.'
operationId: listItems
description: 'Item resources include `sale_tax` (enabled, tax_rate_basis_points, cess_rate_basis_points, price_includes_tax). Use this for new sale previews. Raw catalogue tax metadata is retained for history and future registration; it never authorizes GST collection. Unregistered businesses have sale_tax.enabled=false, zero sale rates and price_includes_tax=false.'
parameters:
-
in: query
name: category_id
description: 'Filter by a tenant-owned category.'
example: 1
required: false
schema:
type: integer
description: 'Filter by a tenant-owned category.'
example: 1
-
in: query
name: subcategory_id
description: 'Filter by a tenant-owned subcategory.'
example: 2
required: false
schema:
type: integer
description: 'Filter by a tenant-owned subcategory.'
example: 2
-
in: query
name: brand_id
description: 'Filter by a tenant-owned brand.'
example: 3
required: false
schema:
type: integer
description: 'Filter by a tenant-owned brand.'
example: 3
-
in: query
name: manufacturer_id
description: 'Filter by a tenant-owned manufacturer.'
example: 4
required: false
schema:
type: integer
description: 'Filter by a tenant-owned manufacturer.'
example: 4
-
in: query
name: gst_rate_id
description: 'Filter by a tenant-owned GST percentage master.'
example: 1
required: false
schema:
type: integer
description: 'Filter by a tenant-owned GST percentage master.'
example: 1
-
in: query
name: attention
description: 'low_stock, expiring (next 30 days), expired, uncategorised or warranty.'
example: warranty
required: false
schema:
type: string
description: 'low_stock, expiring (next 30 days), expired, uncategorised or warranty.'
example: warranty
-
in: query
name: contact
description: 'Customer id. Adds `party_price` to every item, showing what that party pays under its rate card.'
example: 42
required: false
schema:
type: integer
description: 'Customer id. Adds `party_price` to every item, showing what that party pays under its rate card.'
example: 42
-
in: query
name: supplier
description: 'Supplier contact id, or `preferred`. Adds `supplier_price` to every item that supplier has an agreed or last-billed rate for, to open a purchase line at.'
example: '17'
required: false
schema:
type: string
description: 'Supplier contact id, or `preferred`. Adds `supplier_price` to every item that supplier has an agreed or last-billed rate for, to open a purchase line at.'
example: '17'
-
in: query
name: status
description: 'Which items to list by state: `all` (the default) lists both, with inactive items last, `active` lists only items the shop still offers, and `inactive` lists only inactive ones. Use `active` when building a picker.'
example: active
required: false
schema:
type: string
description: 'Which items to list by state: `all` (the default) lists both, with inactive items last, `active` lists only items the shop still offers, and `inactive` lists only inactive ones. Use `active` when building a picker.'
example: active
responses:
401:
description: ''
content:
application/json:
schema:
type: object
example:
message: Unauthenticated.
properties:
message:
type: string
example: Unauthenticated.
tags:
- Inventory
requestBody:
required: false
content:
application/json:
schema:
type: object
properties:
search:
type: string
description: 'Must not be greater than 128 characters.'
example: b
nullable: true
low_stock:
type: boolean
description: ''
example: false
nullable: true
status:
type: string
description: ''
example: all
enum:
- all
- active
- inactive
nullable: true
contact:
type: integer
description: 'Must be at least 1.'
example: 22
nullable: true
supplier:
type: string
description: ''
example: null
per_page:
type: integer
description: 'Must be at least 1. Must not be greater than 100.'
example: 7
nullable: true
post:
summary: 'Create an item and record opening inventory.'
operationId: createAnItemAndRecordOpeningInventory
description: "Tracked opening stock creates a balanced Inventory / Owner equity journal at purchase cost.\nStock and reorder quantities accept up to three decimal places.\nOptional category_id/subcategory_id/brand_id/manufacturer_id must belong to this tenant\nand have the matching kind. Alternatively use their *_name fields to create or reuse names.\nWarranty duration requires warranty_unit and warranty_provider and is capped at 10 years.\nNew/changed brand, manufacturer or both policies require a support name and at least one\nphone, email or HTTP(S) website. Omitted fields are retained on update; null clears optional fields.\nShop/no-warranty policies clear external contact fields. Issued warranties retain contact snapshots.\nGST master selection is authoritative for taxable items. Numeric tax_rate inputs create/reuse a\nbusiness percentage master for compatibility. Omitted rate inputs preserve the item rate on update.\nrequires_expiry needs enabled batch tracking; expiry is stored per physical batch."
parameters: []
responses:
422:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'Your current plan includes up to 5 items. Upgrade to Smart Books to add more.'
errors:
plan:
- 'Your current plan includes up to 5 items. Upgrade to Smart Books to add more.'
properties:
message:
type: string
example: 'Your current plan includes up to 5 items. Upgrade to Smart Books to add more.'
errors:
type: object
properties:
plan:
type: array
example:
- 'Your current plan includes up to 5 items. Upgrade to Smart Books to add more.'
items:
type: string
tags:
- Inventory
requestBody:
required: true
content:
multipart/form-data:
schema:
type: object
properties:
category_id:
type: integer
description: 'Must match an existing stored value.'
example: 16
nullable: true
category_name:
type: string
description: 'Optional category to create or reuse.'
example: Electrical
nullable: true
subcategory_id:
type: integer
description: 'Must match an existing stored value.'
example: 16
nullable: true
subcategory_name:
type: string
description: 'Optional subcategory under the selected category.'
example: 'LED lighting'
nullable: true
brand_id:
type: integer
description: 'Must match an existing stored value.'
example: 16
nullable: true
brand_name:
type: string
description: 'Optional brand to create or reuse.'
example: 'Sample Brand'
nullable: true
manufacturer_id:
type: integer
description: 'Must match an existing stored value.'
example: 16
nullable: true
manufacturer_name:
type: string
description: 'Optional manufacturer to create or reuse.'
example: 'Sample Manufacturer'
nullable: true
gst_rate_id:
type: integer
description: 'Optional business GST percentage master. Product tax metadata does not authorize collecting GST; unregistered/composition sales apply zero. Numeric inputs only create percentage masters for Regular GST businesses. Overrides tax_rate for taxable items; cross-business IDs return 422.'
example: 1
nullable: true
warranty_duration:
type: integer
description: 'Optional whole-number duration.'
example: 6
nullable: true
warranty_unit:
type: string
description: 'days, months or years; required with warranty_duration.'
example: months
nullable: true
warranty_provider:
type: string
description: 'brand, manufacturer, shop or both; required with warranty_duration.'
example: shop
nullable: true
warranty_terms:
type: string
description: 'Coverage conditions, at most 4000 characters.'
example: 'Replacement for manufacturing defects.'
nullable: true
warranty_provider_name:
type: string
description: 'Support name, required for new/changed external warranties.'
example: 'Sample Brand Support'
nullable: true
warranty_provider_phone:
type: string
description: 'Support phone; at least one phone/email/website is required for external cover.'
example: '+91 1800 123 4567'
nullable: true
warranty_provider_email:
type: string
description: 'Support email, at most 254 characters.'
example: support@example.test
nullable: true
warranty_provider_website:
type: string
description: 'HTTP(S) support URL, at most 2048 characters.'
example: 'https://example.test/support'
nullable: true
warranty_provider_address:
type: string
description: 'Optional service address, at most 1000 characters.'
example: 'Example Service Centre, Hyderabad'
nullable: true
warranty_provider_notes:
type: string
description: 'Optional claim instructions, at most 2000 characters.'
example: 'Keep your invoice and product serial number ready.'
nullable: true
requires_expiry:
type: boolean
description: 'Require expiry on received stock lots.'
example: false
name:
type: string
description: 'Must not be greater than 128 characters.'
example: k
sku:
type: string
description: 'Must not be greater than 64 characters.'
example: h
nullable: true
barcode:
type: string
description: 'Must not be greater than 64 characters.'
example: w
nullable: true
item_type:
type: string
description: ''
example: goods
enum:
- goods
- service
hsn_sac:
type: string
description: 'Must not be greater than 8 characters.'
example: aykcmyuw
nullable: true
unit:
type: string
description: 'Must not be greater than 24 characters.'
example: pwlvqwrsitcpscql
uqc:
type: string
description: 'Must not be greater than 16 characters.'
example: dzsnrwtujwvlxjkl
gst_taxability:
type: string
description: ''
example: taxable
enum:
- taxable
- nil_rated
- exempt
- non_gst
sale_price:
type: number
description: 'Must be at least 0. Must not be greater than 999999999.'
example: 8
purchase_price:
type: number
description: 'Must be at least 0. Must not be greater than 999999999.'
example: 10
nullable: true
mrp:
type: number
description: 'Must be at least 0. Must not be greater than 999999999.'
example: 3
nullable: true
tax_rate:
type: number
description: 'Must be at least 0. Must not be greater than 100.'
example: 14
nullable: true
cess_rate:
type: number
description: 'Optional product cess percentage, from 0 to 100; omitted updates preserve the current item value. Unregistered/composition sales apply zero cess.'
example: 0.0
nullable: true
stock_quantity:
type: number
description: 'Must be at least 0. Must not be greater than 999999999.'
example: 4
warehouse_id:
type: integer
description: 'Must be at least 1.'
example: 35
nullable: true
reorder_level:
type: number
description: 'Must be at least 0. Must not be greater than 999999999.'
example: 9
nullable: true
is_active:
type: boolean
description: ''
example: false
track_inventory:
type: boolean
description: ''
example: false
track_batches:
type: boolean
description: ''
example: false
min_shelf_life_days:
type: integer
description: 'Must be at least 0. Must not be greater than 730.'
example: 6
nullable: true
track_serials:
type: boolean
description: ''
example: false
opening_batch_number:
type: string
description: 'Must not be greater than 64 characters.'
example: 'n'
nullable: true
opening_batch_expiry_date:
type: string
description: 'Must be a valid date.'
example: '2026-01-15'
nullable: true
opening_serial_numbers:
type: array
description: 'Must not be greater than 64 characters.'
example:
- 'n'
items:
type: string
opening_warranty_until:
type: string
description: 'Must be a valid date.'
example: '2026-01-15'
nullable: true
price_includes_tax:
type: boolean
description: ''
example: false
photos:
type: array
description: 'Must be an image. Must not be greater than 5120 kilobytes.'
items:
type: string
format: binary
remove_photos:
type: array
description: ''
example:
- 16
items:
type: integer
required:
- name
- item_type
- unit
- uqc
- gst_taxability
- sale_price
- stock_quantity
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
'/api/v1/businesses/{business}/items/{id}':
get:
summary: ''
operationId: getApiV1BusinessesBusinessItemsId
description: ''
parameters:
-
in: query
name: contact
description: 'Customer id. Adds `party_price` to the item, showing what that party pays under its rate card.'
example: 42
required: false
schema:
type: integer
description: 'Customer id. Adds `party_price` to the item, showing what that party pays under its rate card.'
example: 42
-
in: query
name: supplier
description: 'Supplier contact id, or `preferred`. Adds `supplier_price`, the rate to open a purchase line at.'
example: '17'
required: false
schema:
type: string
description: 'Supplier contact id, or `preferred`. Adds `supplier_price`, the rate to open a purchase line at.'
example: '17'
responses:
401:
description: ''
content:
application/json:
schema:
type: object
example:
message: Unauthenticated.
properties:
message:
type: string
example: Unauthenticated.
tags:
- Inventory
requestBody:
required: false
content:
application/json:
schema:
type: object
properties:
contact:
type: integer
description: 'Must be at least 1.'
example: 16
nullable: true
supplier:
type: string
description: ''
example: null
put:
summary: 'Update an item and account for stock corrections.'
operationId: updateAnItemAndAccountForStockCorrections
description: "Quantity increases record inventory-adjustment income; decreases record an operating expense.\nStock and reorder quantities accept up to three decimal places."
parameters: []
responses: {}
tags:
- Inventory
requestBody:
required: true
content:
multipart/form-data:
schema:
type: object
properties:
category_id:
type: integer
description: 'Must match an existing stored value.'
example: 16
nullable: true
category_name:
type: string
description: 'Must not be greater than 100 characters.'
example: 'n'
nullable: true
subcategory_id:
type: integer
description: 'Must match an existing stored value.'
example: 16
nullable: true
subcategory_name:
type: string
description: 'Must not be greater than 100 characters.'
example: 'n'
nullable: true
brand_id:
type: integer
description: 'Must match an existing stored value.'
example: 16
nullable: true
brand_name:
type: string
description: 'Must not be greater than 100 characters.'
example: 'n'
nullable: true
manufacturer_id:
type: integer
description: 'Must match an existing stored value.'
example: 16
nullable: true
manufacturer_name:
type: string
description: 'Must not be greater than 100 characters.'
example: 'n'
nullable: true
gst_rate_id:
type: integer
description: 'Must match an existing stored value.'
example: 16
nullable: true
warranty_duration:
type: integer
description: 'Must be at least 1. Must not be greater than 3650.'
example: 22
nullable: true
warranty_unit:
type: string
description: 'This field is required when
warranty_duration is present.'
example: days
enum:
- days
- months
- years
nullable: true
warranty_provider:
type: string
description: 'This field is required when warranty_duration is present.'
example: brand
enum:
- brand
- manufacturer
- shop
- both
nullable: true
warranty_terms:
type: string
description: 'Must not be greater than 4000 characters.'
example: g
nullable: true
warranty_provider_name:
type: string
description: 'Must not be greater than 160 characters.'
example: z
nullable: true
warranty_provider_phone:
type: string
description: 'Must match the regex /^[+0-9][0-9\s().\-xX#]{4,49}$/. Must not be greater than 50 characters.'
example: m
nullable: true
warranty_provider_email:
type: string
description: 'Must be a valid email address. Must not be greater than 254 characters.'
example: gulgowski.asia@example.com
nullable: true
warranty_provider_website:
type: string
description: 'Must be a valid URL. Must not be greater than 2048 characters.'
example: j
nullable: true
warranty_provider_address:
type: string
description: 'Must not be greater than 1000 characters.'
example: 'n'
nullable: true
warranty_provider_notes:
type: string
description: 'Must not be greater than 2000 characters.'
example: i
nullable: true
requires_expiry:
type: boolean
description: ''
example: false
name:
type: string
description: 'Must not be greater than 128 characters.'
example: k
sku:
type: string
description: 'Must not be greater than 64 characters.'
example: h
nullable: true
barcode:
type: string
description: 'Must not be greater than 64 characters.'
example: w
nullable: true
item_type:
type: string
description: ''
example: goods
enum:
- goods
- service
hsn_sac:
type: string
description: 'Must not be greater than 8 characters.'
example: aykcmyuw
nullable: true
unit:
type: string
description: 'Must not be greater than 24 characters.'
example: pwlvqwrsitcpscql
uqc:
type: string
description: 'Must not be greater than 16 characters.'
example: dzsnrwtujwvlxjkl
gst_taxability:
type: string
description: ''
example: taxable
enum:
- taxable
- nil_rated
- exempt
- non_gst
sale_price:
type: number
description: 'Must be at least 0. Must not be greater than 999999999.'
example: 8
purchase_price:
type: number
description: 'Must be at least 0. Must not be greater than 999999999.'
example: 10
nullable: true
mrp:
type: number
description: 'Must be at least 0. Must not be greater than 999999999.'
example: 3
nullable: true
tax_rate:
type: number
description: 'Must be at least 0. Must not be greater than 100.'
example: 14
nullable: true
cess_rate:
type: number
description: 'Must be at least 0. Must not be greater than 100.'
example: 7
nullable: true
stock_quantity:
type: number
description: 'Must be at least 0. Must not be greater than 999999999.'
example: 4
warehouse_id:
type: integer
description: 'Must be at least 1.'
example: 35
nullable: true
reorder_level:
type: number
description: 'Must be at least 0. Must not be greater than 999999999.'
example: 9
nullable: true
is_active:
type: boolean
description: ''
example: false
track_inventory:
type: boolean
description: ''
example: false
track_batches:
type: boolean
description: ''
example: false
min_shelf_life_days:
type: integer
description: 'Must be at least 0. Must not be greater than 730.'
example: 6
nullable: true
track_serials:
type: boolean
description: ''
example: false
opening_batch_number:
type: string
description: 'Must not be greater than 64 characters.'
example: 'n'
nullable: true
opening_batch_expiry_date:
type: string
description: 'Must be a valid date.'
example: '2026-01-15'
nullable: true
opening_serial_numbers:
type: array
description: 'Must not be greater than 64 characters.'
example:
- 'n'
items:
type: string
opening_warranty_until:
type: string
description: 'Must be a valid date.'
example: '2026-01-15'
nullable: true
price_includes_tax:
type: boolean
description: ''
example: false
photos:
type: array
description: 'Must be an image. Must not be greater than 5120 kilobytes.'
items:
type: string
format: binary
remove_photos:
type: array
description: ''
example:
- 16
items:
type: integer
required:
- name
- item_type
- unit
- uqc
- gst_taxability
- sale_price
- stock_quantity
delete:
summary: 'Soft delete an item.'
operationId: softDeleteAnItem
description: "Items used on documents can be deleted. The item is removed from the catalogue and new\nitem selections; existing document lines, stock history, quantities, and accounting\nentries are retained. Existing documents can keep their deleted items when edited,\nand returns still use the original item. Requires inventory permission.\nSubsequent item reads, updates, or deletions return 404, as do foreign-tenant items."
parameters: []
responses:
204:
description: ''
content:
application/json:
schema:
type: object
example: {}
properties: {}
403:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'Your role does not allow this action.'
properties:
message:
type: string
example: 'Your role does not allow this action.'
404:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'Not Found'
properties:
message:
type: string
example: 'Not Found'
tags:
- Inventory
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
-
in: path
name: id
description: 'The ID of the item.'
example: 1
required: true
schema:
type: integer
'/api/v1/businesses/{business}/gst-rates':
get:
summary: 'List business GST percentage masters for item lookups.'
operationId: listBusinessGSTPercentageMastersForItemLookups
description: "Returns an empty list for unregistered and composition businesses; historical masters remain stored.\nThese are configured percentages, not a tax-advice or statutory-rate catalogue."
parameters: []
responses:
200:
description: ''
content:
application/json:
schema:
type: object
example:
data:
-
id: 1
percentage: 18
basis_points: 1800
label: 18%
properties:
data:
type: array
example:
- id: 1
percentage: 18
basis_points: 1800
label: 18%
items:
type: object
properties:
id:
type: integer
example: 1
percentage:
type: integer
example: 18
basis_points:
type: integer
example: 1800
label:
type: string
example: 18%
tags:
- Inventory
post:
summary: 'Create or reuse a business GST percentage.'
operationId: createOrReuseABusinessGSTPercentage
description: 'Regular GST registration is required. Unregistered and composition businesses return 422.'
parameters: []
responses:
200:
description: ''
content:
application/json:
schema:
type: object
example:
data:
id: 1
percentage: 18
basis_points: 1800
label: 18%
properties:
data:
type: object
properties:
id:
type: integer
example: 1
percentage:
type: integer
example: 18
basis_points:
type: integer
example: 1800
label:
type: string
example: 18%
201:
description: ''
content:
application/json:
schema:
type: object
example:
data:
id: 1
percentage: 18
basis_points: 1800
label: 18%
properties:
data:
type: object
properties:
id:
type: integer
example: 1
percentage:
type: integer
example: 18
basis_points:
type: integer
example: 1800
label:
type: string
example: 18%
422:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'Only a business with Regular GST registration can add or edit GST percentages.'
errors:
percentage:
- 'Only a business with Regular GST registration can add or edit GST percentages.'
properties:
message:
type: string
example: 'Only a business with Regular GST registration can add or edit GST percentages.'
errors:
type: object
properties:
percentage:
type: array
example:
- 'Only a business with Regular GST registration can add or edit GST percentages.'
items:
type: string
tags:
- Inventory
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
percentage:
type: number
description: 'Percentage from 0 to 100, at most two decimal places.'
example: 18.0
required:
- percentage
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
'/api/v1/businesses/{business}/gst-rates/{gstRate_id}':
patch:
summary: 'Change an unused GST percentage.'
operationId: changeAnUnusedGSTPercentage
description: "Requires Regular GST registration; unregistered and composition businesses return 422.\nA rate referenced by any item (including deleted items) cannot change. Create a new master instead."
parameters: []
responses:
409:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'This rate is used by an item. Add a new percentage instead.'
properties:
message:
type: string
example: 'This rate is used by an item. Add a new percentage instead.'
422:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'Only a business with Regular GST registration can add or edit GST percentages.'
errors:
percentage:
- 'Only a business with Regular GST registration can add or edit GST percentages.'
properties:
message:
type: string
example: 'Only a business with Regular GST registration can add or edit GST percentages.'
errors:
type: object
properties:
percentage:
type: array
example:
- 'Only a business with Regular GST registration can add or edit GST percentages.'
items:
type: string
tags:
- Inventory
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
percentage:
type: number
description: 'Percentage from 0 to 100, at most two decimal places.'
example: 12.0
required:
- percentage
delete:
summary: 'Delete an unused GST percentage. Used rates return 409.'
operationId: deleteAnUnusedGSTPercentageUsedRatesReturn409
description: 'Requires Regular GST registration; unregistered and composition businesses return 422.'
parameters: []
responses:
204:
description: ''
content:
application/json:
schema:
type: object
example: {}
properties: {}
409:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'This rate is still used by an item.'
properties:
message:
type: string
example: 'This rate is still used by an item.'
422:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'Only a business with Regular GST registration can add or edit GST percentages.'
errors:
percentage:
- 'Only a business with Regular GST registration can add or edit GST percentages.'
properties:
message:
type: string
example: 'Only a business with Regular GST registration can add or edit GST percentages.'
errors:
type: object
properties:
percentage:
type: array
example:
- 'Only a business with Regular GST registration can add or edit GST percentages.'
items:
type: string
tags:
- Inventory
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
-
in: path
name: gstRate_id
description: 'The ID of the gstRate.'
example: 1
required: true
schema:
type: integer
'/api/v1/businesses/{business}/item-classifications':
get:
summary: 'List categories, subcategories, brands and manufacturers.'
operationId: listCategoriesSubcategoriesBrandsAndManufacturers
description: ''
parameters:
-
in: query
name: kind
description: 'Optional classification kind.'
example: category
required: false
schema:
type: string
description: 'Optional classification kind.'
example: category
-
in: query
name: parent_id
description: 'Optional parent category for subcategory lookups; must belong to this business.'
example: 1
required: false
schema:
type: integer
description: 'Optional parent category for subcategory lookups; must belong to this business.'
example: 1
responses:
401:
description: ''
content:
application/json:
schema:
type: object
example:
message: Unauthenticated.
properties:
message:
type: string
example: Unauthenticated.
tags:
- Inventory
requestBody:
required: false
content:
application/json:
schema:
type: object
properties:
kind:
type: string
description: ''
example: null
nullable: true
parent_id:
type: integer
description: ''
example: 16
nullable: true
post:
summary: 'Create or reuse a classification.'
operationId: createOrReuseAClassification
description: 'Names ignore case and repeated spaces. Subcategories require a tenant-owned category parent.'
parameters: []
responses: {}
tags:
- Inventory
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
kind:
type: string
description: 'category, subcategory, brand or manufacturer.'
example: category
name:
type: string
description: 'Display name, at most 100 characters.'
example: Lighting
parent_id:
type: integer
description: 'Parent category for a subcategory.'
example: 1
nullable: true
required:
- kind
- name
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
'/api/v1/businesses/{business}/item-classifications/{itemClassification_id}':
patch:
summary: 'Rename a classification without changing its hierarchy.'
operationId: renameAClassificationWithoutChangingItsHierarchy
description: ''
parameters: []
responses: {}
tags:
- Inventory
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
name:
type: string
description: 'New display name, at most 100 characters.'
example: 'Electrical lighting'
required:
- name
delete:
summary: 'Delete an unused classification. Used classifications and categories with children return 409.'
operationId: deleteAnUnusedClassificationUsedClassificationsAndCategoriesWithChildrenReturn409
description: ''
parameters: []
responses:
204:
description: ''
content:
application/json:
schema:
type: object
example: {}
properties: {}
tags:
- Inventory
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
-
in: path
name: itemClassification_id
description: 'The ID of the itemClassification.'
example: 1
required: true
schema:
type: integer
'/api/v1/businesses/{business}/item-warranties':
get:
summary: 'List warranty records for this workspace.'
operationId: listWarrantyRecordsForThisWorkspace
description: 'Contact details are frozen when coverage is issued, including after invoice corrections.'
parameters:
-
in: query
name: search
description: 'Search item, buyer, invoice or serial number.'
example: LED
required: false
schema:
type: string
description: 'Search item, buyer, invoice or serial number.'
example: LED
-
in: query
name: item_id
description: 'Filter one item.'
example: 1
required: false
schema:
type: integer
description: 'Filter one item.'
example: 1
-
in: query
name: contact_id
description: 'Filter one customer.'
example: 1
required: false
schema:
type: integer
description: 'Filter one customer.'
example: 1
-
in: query
name: per_page
description: 'Page size, 1-100.'
example: 25
required: false
schema:
type: integer
description: 'Page size, 1-100.'
example: 25
responses:
401:
description: ''
content:
application/json:
schema:
type: object
example:
message: Unauthenticated.
properties:
message:
type: string
example: Unauthenticated.
tags:
- Inventory
requestBody:
required: false
content:
application/json:
schema:
type: object
properties:
search:
type: string
description: 'Must not be greater than 128 characters.'
example: b
nullable: true
item_id:
type: integer
description: 'Must be at least 1.'
example: 22
nullable: true
contact_id:
type: integer
description: 'Must be at least 1.'
example: 67
nullable: true
per_page:
type: integer
description: 'Must be at least 1. Must not be greater than 100.'
example: 16
nullable: true
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
'/api/v1/businesses/{business}/item-warranties/{itemWarranty_id}':
get:
summary: 'Read a warranty, including coverage, its private buyer link and issued support contacts.'
operationId: readAWarrantyIncludingCoverageItsPrivateBuyerLinkAndIssuedSupportContacts
description: ''
parameters: []
responses:
401:
description: ''
content:
application/json:
schema:
type: object
example:
message: Unauthenticated.
properties:
message:
type: string
example: Unauthenticated.
tags:
- Inventory
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
-
in: path
name: itemWarranty_id
description: 'The ID of the itemWarranty.'
example: 1
required: true
schema:
type: integer
'/api/v1/businesses/{business}/item-imports/template':
get:
summary: 'Download the product import template.'
operationId: downloadTheProductImportTemplate
description: "The workbook carries the `Products` sheet and header row the upload expects. Row 1 must\nsurvive unedited — a renamed or reordered header is reported as a file-level error.\nOptional product columns include category, subcategory, brand, manufacturer, warranty\nduration/unit/provider/terms and batch/serial/expiry flags. The original 16-column template\nremains supported. Stock batches and Serial units sheets link by SKU; quantities/counts\nmust equal Opening Stock. Each optional stock sheet allows up to 2,000 rows. Required\nexpiry, normalised serial uniqueness, tracked-goods constraints and plan features apply.\nCreate category, subcategory, brand and manufacturer masters first. This business-specific\nworkbook includes a Masters reference sheet and per-item dropdowns for existing records.\nSubcategory uses a plain saved-master list, without dependent INDIRECT formulas.\nThe Masters sheet shows each subcategory's parent; upload validates that it matches the category.\nSpreadsheet apps that do not preserve dropdowns can copy the saved names from Masters.\nRegular GST businesses also receive\nGST treatment choices and existing GST percentage masters; other businesses have tax columns hidden.\nDownload again after changing masters. Organisation columns may be left blank and assigned later."
parameters: []
responses:
200:
description: 'The product import template workbook.'
content:
application/octet-stream:
schema:
type: string
format: binary
tags:
- Inventory
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
'/api/v1/businesses/{business}/item-imports':
post:
summary: 'Upload a product file and stage it for review.'
operationId: uploadAProductFileAndStageItForReview
description: "Creates no items. The file is stored, checksummed, and validated row by row.\nImports never create masters. Nonblank classification names must match existing records\nin this business (case and repeated spaces are ignored); a subcategory must match its category.\nTaxable GST percentages supplied by a Regular GST business must match existing GST masters.\nBlank organisation/rate cells remain unassigned. Unknown records produce row validation errors.\nOptional warranty units are days/months/years and providers brand/manufacturer/shop/both.\nExternal policies need Warranty provider name and at least one Warranty provider phone/email/website.\nOptional Warranty provider address and Warranty provider notes appear on issued slips.\nThe response reports how many rows passed, which rows failed and why, and a sample of the normalized\nrows that would be created. Call the confirm endpoint once `error_count` is zero.\n\nRe-uploading a file this workspace has already uploaded does not stage it a second time:\nthe existing record is returned with `duplicate` set to `true` and HTTP `200`. Unconfirmed\nfiles are revalidated against current masters; imported files remain unchanged."
parameters: []
responses:
422:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'The file must be a file of type: xlsx.'
errors:
file:
- 'The file must be a file of type: xlsx.'
properties:
message:
type: string
example: 'The file must be a file of type: xlsx.'
errors:
type: object
properties:
file:
type: array
example:
- 'The file must be a file of type: xlsx.'
items:
type: string
tags:
- Inventory
requestBody:
required: true
content:
multipart/form-data:
schema:
type: object
properties:
file:
type: string
format: binary
description: 'The product workbook, `.xlsx`, up to 5 MB and 2,000 rows.'
required:
- file
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
'/api/v1/businesses/{business}/item-imports/{itemImport_id}':
get:
summary: 'Read a staged import.'
operationId: readAStagedImport
description: 'Returns the current status, the row counts, every validation error, and the preview sample.'
parameters: []
responses:
401:
description: ''
content:
application/json:
schema:
type: object
example:
message: Unauthenticated.
properties:
message:
type: string
example: Unauthenticated.
tags:
- Inventory
delete:
summary: 'Discard a staged import.'
operationId: discardAStagedImport
description: "Deletes the stored file and its staged rows. An import that has already created items\ncannot be discarded — its checksum is what stops the same file being imported twice."
parameters: []
responses:
409:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'An imported file cannot be discarded.'
properties:
message:
type: string
example: 'An imported file cannot be discarded.'
tags:
- Inventory
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
-
in: path
name: itemImport_id
description: 'The ID of the itemImport.'
example: 1
required: true
schema:
type: integer
'/api/v1/businesses/{business}/item-imports/{itemImport_id}/confirm':
post:
summary: 'Create every validated row.'
operationId: createEveryValidatedRow
description: "All rows are created inside one transaction, so the catalogue is either wholly imported or\nuntouched. Rows carrying opening stock also record an opening stock movement and its\ninventory journal, exactly as the setup wizard does.\n\nThe workbook is checked again before writing. Conflicts introduced since preview (including\nSKUs or barcodes reserved by deleted items) return `422` under `file` and create nothing.\nMalformed numeric and boolean values are rejected instead of being converted to zero or false.\n\nExisting classification and GST masters are revalidated at confirmation; renamed/deleted\nmasters return 422 under file. No masters are created during preview or confirmation.\nUnregistered/composition imports retain product tax metadata without creating new masters;\nthat metadata never authorizes collecting GST on new sales."
parameters: []
responses:
409:
description: ''
content:
application/json:
schema:
oneOf:
-
description: ''
type: object
example:
message: 'This file has already been imported.'
properties:
message:
type: string
example: 'This file has already been imported.'
-
description: ''
type: object
example:
message: 'This file has not passed validation.'
properties:
message:
type: string
example: 'This file has not passed validation.'
422:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'Your current plan includes up to 5 items. Upgrade to Smart Books to add more.'
errors:
plan:
- 'Your current plan includes up to 5 items. Upgrade to Smart Books to add more.'
properties:
message:
type: string
example: 'Your current plan includes up to 5 items. Upgrade to Smart Books to add more.'
errors:
type: object
properties:
plan:
type: array
example:
- 'Your current plan includes up to 5 items. Upgrade to Smart Books to add more.'
items:
type: string
tags:
- Inventory
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
-
in: path
name: itemImport_id
description: 'The ID of the itemImport.'
example: 1
required: true
schema:
type: integer
'/api/v1/businesses/{business}/invoices/{invoice_id}/warranty.pdf':
get:
summary: 'Download the warranty slip PDF for an invoice.'
operationId: downloadTheWarrantySlipPDFForAnInvoice
description: 'Requires sales permission. Includes current warranty records, their terms and buyer tracking links.'
parameters: []
responses:
200:
description: ''
content:
text/plain:
schema:
type: string
example: 'binary application/pdf'
tags:
- Inventory
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
-
in: path
name: invoice_id
description: 'The ID of the invoice.'
example: 1
required: true
schema:
type: integer
'/api/v1/businesses/{business}/invoices/{invoice_id}/warranty/send':
post:
summary: "Send or resend the invoice and warranty slip to the buyer's primary email."
operationId: sendOrResendTheInvoiceAndWarrantySlipToTheBuyersPrimaryEmail
description: "Requires sales permission, configured mail and a valid customer email. Queued or sending\ndeliveries are reused. Explicit resends may duplicate a message already accepted by mail."
parameters: []
responses:
202:
description: ''
content:
application/json:
schema:
type: object
example:
data:
status: queued
sent_at: null
properties:
data:
type: object
properties:
status:
type: string
example: queued
sent_at:
type: string
example: null
nullable: true
tags:
- Inventory
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
-
in: path
name: invoice_id
description: 'The ID of the invoice.'
example: 1
required: true
schema:
type: integer
'/api/v1/businesses/{business}/invoices':
get:
summary: 'List saved sales invoices.'
operationId: listSavedSalesInvoices
description: 'Resources include `show_gst_details`, based on the registration frozen at issue. When false, hide GSTINs, HSN/tax columns, place of supply, reverse charge, GST amounts and tax summaries in invoice and document UI. New unregistered documents force GST/cess to zero and tax-inclusive/reverse-charge flags false. Previously issued registered documents keep their original tax treatment.'
parameters:
-
in: query
name: status
description: 'Filter by status. saved aliases saved.'
example: saved
required: false
schema:
type: string
description: 'Filter by status. saved aliases saved.'
example: saved
-
in: query
name: outstanding
description: 'Only non-void invoices with a positive balance after payments and returns. Filtered before pagination.'
example: true
required: false
schema:
type: boolean
description: 'Only non-void invoices with a positive balance after payments and returns. Filtered before pagination.'
example: true
responses:
401:
description: ''
content:
application/json:
schema:
type: object
example:
message: Unauthenticated.
properties:
message:
type: string
example: Unauthenticated.
tags:
- 'Sales invoices'
requestBody:
required: false
content:
application/json:
schema:
type: object
properties:
status:
type: string
description: ''
example: saved
enum:
- saved
- posted
- partially_paid
- paid
- void
- partially_returned
- returned
nullable: true
outstanding:
type: boolean
description: ''
example: false
nullable: true
contact_id:
type: integer
description: ''
example: 16
nullable: true
from:
type: string
description: 'Must be a valid date.'
example: '2026-01-15'
nullable: true
to:
type: string
description: 'Must be a valid date. Must be a date after or equal to from.'
example: '2026-01-15'
nullable: true
search:
type: string
description: 'Must not be greater than 128 characters.'
example: 'n'
nullable: true
per_page:
type: integer
description: 'Must be at least 1. Must not be greater than 100.'
example: 7
nullable: true
post:
summary: 'Save one or many sales invoices.'
operationId: saveOneOrManySalesInvoices
description: "Send a single invoice payload, or wrap up to 25 of them in an `invoices` array to save a batch in one atomic request. A batch either saves completely or not at all, and returns its invoices in request order under `data` with `meta.count`.\n\nMonthly invoice allowances count all invoices created in the application-calendar month, including trial\nand voided invoices. A downgrade does not reset usage. At the limit, new invoices return HTTP 422\nagainst `plan` with an upgrade/reset message. Existing idempotency keys can still be retried at the limit.\nA batch that exceeds the remaining allowance is rolled back completely. Existing invoices remain accessible.\n\nA customer carrying a credit limit is refused with HTTP 422 against `contact_id` when the invoice would take what it owes past that limit. A batch is weighed cumulatively: each entry sees the exposure the earlier entries created.\n\nGST heads follow the place of supply: CGST and SGST (UTGST in Chandigarh, Dadra and Nagar Haveli and Daman and Diu, Lakshadweep, Andaman and Nicobar Islands and Ladakh) when it is the business's own state, otherwise IGST. A customer with `gst_treatment` `sez` is always charged IGST, and an overseas customer defaults to place of supply `96`."
parameters: []
responses:
422:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'The selected record was not found for this business.'
errors:
invoices.1.contact_id:
- 'The selected record was not found for this business.'
properties:
message:
type: string
example: 'The selected record was not found for this business.'
errors:
type: object
properties:
invoices.1.contact_id:
type: array
example:
- 'The selected record was not found for this business.'
items:
type: string
tags:
- 'Sales invoices'
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
line_layout:
type: array
description: 'Optional display rows. Empty or null uses the line array order. On update, omission retains the layout when the line count is unchanged.'
example:
-
type: header
title: 'Kitchen essentials'
-
type: item
line_index: 0
-
type: subtotal
items:
type: object
properties:
type:
type: string
description: 'item, header, or subtotal.'
example: item
line_index:
type: integer
description: 'Zero-based index in lines, required for item rows. Every line must appear exactly once.'
example: 0
nullable: true
title:
type: string
description: 'Heading required for header rows, maximum 100 characters.'
example: 'Kitchen essentials'
nullable: true
required:
- type
show_section_totals:
type: boolean
description: 'Show calculated header totals on document output; defaults true.'
example: true
workers:
type: array
description: 'Optional complete multi-worker assignment set with commission_kind, fixed_amount_paise or rate_basis_points, and owner-entered attribution_basis_points totalling 10000. Requires Advanced Plan and owner/admin. Earnings commit atomically with the invoice.'
example: null
items:
type: object
properties:
worker_id:
type: integer
description: 'Must be at least 1.'
example: 66
commission_kind:
type: string
description: ''
example: percentage
enum:
- percentage
- fixed
rate_basis_points:
type: integer
description: 'Must be between 0 and 10000.'
example: 0
nullable: true
fixed_amount_paise:
type: integer
description: 'Must be between 0 and 99999999900.'
example: 0
nullable: true
attribution_basis_points:
type: integer
description: 'Must be between 1 and 10000.'
example: 1
required:
- worker_id
- commission_kind
- attribution_basis_points
worker_attribution_basis_points:
type: integer
description: 'Required with worker_id; owner-entered single-worker share must be 10000.'
example: null
nullable: true
worker_id:
type: integer
description: 'Optional referral worker. Advanced Plan and owner/administrator required; commission is earned atomically when this sale is saved.'
example: null
nullable: true
worker_rate_basis_points:
type: integer
description: 'Optional rate override from 0 to 10000; omitted uses the worker default. Earnings use discounted value before tax, with automatic return debit notes.'
example: null
nullable: true
contact_id:
type: integer
description: ''
example: 16
issue_date:
type: string
description: 'Must be a valid date.'
example: '2026-01-15'
due_date:
type: string
description: 'Must be a valid date. Must be a date after or equal to issue_date.'
example: '2026-01-15'
nullable: true
notes:
type: string
description: 'Must not be greater than 2000 characters.'
example: 'n'
nullable: true
place_of_supply_state_code:
type: string
description: 'Two-digit GST state code, or 96 for an export. 99 is refused. Defaults from the customer, then the business.'
example: '29'
nullable: true
reverse_charge:
type: boolean
description: ''
example: false
prices_include_tax:
type: boolean
description: ''
example: false
channel:
type: string
description: ''
example: backoffice
enum:
- backoffice
- pos
nullable: true
idempotency_key:
type: string
description: 'Must be a valid UUID.'
example: 6b72fe4a-5b40-307c-bc24-f79acf9a1bb9
nullable: true
lines:
type: array
description: 'Must have at least 1 items. Must not have more than 50 items.'
example:
- []
items:
type: object
properties:
details:
type: string
description: 'Optional additional description, maximum 2000 characters.'
example: 'Deliver in sealed packs.'
nullable: true
item_id:
type: integer
description: ''
example: 16
nullable: true
warehouse_id:
type: integer
description: 'Must be at least 1.'
example: 22
nullable: true
item_batch_id:
type: integer
description: ''
example: 16
nullable: true
serial_numbers:
type: array
description: 'Must not be greater than 64 characters.'
example:
- 'n'
items:
type: string
description:
type: string
description: 'Must not be greater than 255 characters.'
example: 'Animi quos velit et fugiat.'
hsn_sac:
type: string
description: "HSN (4, 6 or 8 digits) or SAC (6 digits) for this line. Dots and spaces are stripped. Defaults to the item's code on an item line."
example: '100630'
nullable: true
quantity:
type: number
description: 'Must not be greater than 999999.'
example: 18
unit_price:
type: number
description: 'This field is required when lines.*.item_id is not present. Must be at least 0. Must not be greater than 999999999.'
example: 22
nullable: true
tax_rate:
type: number
description: 'Must be at least 0. Must not be greater than 100.'
example: 24
nullable: true
cess_rate:
type: number
description: 'Must be at least 0. Must not be greater than 100.'
example: 18
nullable: true
discount:
type: number
description: 'Must be at least 0. Must not be greater than 999999999.'
example: 8
nullable: true
price_includes_tax:
type: boolean
description: ''
example: false
required:
- description
- quantity
required:
- contact_id
- issue_date
- lines
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
'/api/v1/businesses/{business}/invoices/payments':
post:
summary: 'Record one or many customer payments.'
operationId: recordOneOrManyCustomerPayments
description: 'Post a single payment to `invoices/{invoice}/payments`, or send up to 25 payments — each naming its own `invoice_id` — as a `payments` array to `invoices/payments` to settle several invoices in one atomic request.'
parameters: []
responses:
422:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'Enter an amount up to the current outstanding balance.'
errors:
payments.1.amount:
- 'Enter an amount up to the current outstanding balance.'
properties:
message:
type: string
example: 'Enter an amount up to the current outstanding balance.'
errors:
type: object
properties:
payments.1.amount:
type: array
example:
- 'Enter an amount up to the current outstanding balance.'
items:
type: string
tags:
- 'Sales invoices'
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
payments:
type: array
description: 'Must have at least 1 items. Must not have more than 25 items.'
example:
- []
items:
type: object
properties:
invoice_id:
type: integer
description: ''
example: 16
amount:
type: number
description: 'Must not be greater than 999999999.'
example: 22
method:
type: string
description: ''
example: cash
enum:
- cash
- bank
- upi
- card
- cheque
- other
payment_account_id:
type: integer
description: ''
example: 16
paid_on:
type: string
description: 'Must be a valid date.'
example: '2026-01-15'
nullable: true
reference:
type: string
description: 'Must not be greater than 64 characters.'
example: 'n'
nullable: true
notes:
type: string
description: 'Must not be greater than 1000 characters.'
example: g
nullable: true
required:
- invoice_id
- amount
- method
- payment_account_id
required:
- payments
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
'/api/v1/businesses/{business}/invoices/void':
post:
summary: 'Void one or many unpaid invoices that have no returns.'
operationId: voidOneOrManyUnpaidInvoicesThatHaveNoReturns
description: "Invoices with a payment or a sales return must be settled through the corresponding payment or return workflow and cannot be voided.\n\nVoid a single invoice at `invoices/{invoice}/void`, or send up to 25 entries — each naming its own `invoice_id` — as an `invoices` array to `invoices/void`. A batch may carry one shared request-level `reason` instead of repeating it per entry, and either voids completely or not at all."
parameters: []
responses:
422:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'Only unpaid, active invoices without returns can be voided.'
errors:
invoice:
- 'Only unpaid, active invoices without returns can be voided.'
properties:
message:
type: string
example: 'Only unpaid, active invoices without returns can be voided.'
errors:
type: object
properties:
invoice:
type: array
example:
- 'Only unpaid, active invoices without returns can be voided.'
items:
type: string
tags:
- 'Sales invoices'
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
invoices:
type: array
description: 'Must have at least 1 items. Must not have more than 25 items.'
example:
- []
items:
type: object
properties:
invoice_id:
type: integer
description: ''
example: 16
reason:
type: string
description: 'This field is required when reason is not present. Must be at least 3 characters. Must not be greater than 255 characters.'
example: 'n'
nullable: true
required:
- invoice_id
reason:
type: string
description: 'Must be at least 3 characters. Must not be greater than 255 characters.'
example: b
nullable: true
required:
- invoices
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
'/api/v1/businesses/{business}/invoices/{invoice_id}':
get:
summary: 'Read an invoice and its change history.'
operationId: readAnInvoiceAndItsChangeHistory
description: ''
parameters: []
responses:
200:
description: ''
content:
application/json:
schema:
type: object
example:
data:
customer_pdf_url: 'https://dukanam.com/invoices/1/shared?expires=1790000000&signature=example'
send:
share_url: 'https://dukanam.com/shared/invoices/1?expires=1790000000&signature=example'
message: "Hi Ravi Kumar, please find invoice INV-1001 for ₹2,500.00 from Sri Lakshmi Traders. Thank you.\n\nView invoice PDF: https://dukanam.com/shared/invoices/1?expires=1790000000&signature=example"
email_subject: 'Invoice INV-1001 from Sri Lakshmi Traders'
email_available: true
payment_reminder:
message: 'Hi Ravi Kumar, a payment of ₹2,500.00 is pending on invoice INV-1001. Pay to Sri Lakshmi Traders: Account number: 001234567890, IFSC: HDFC0000123. View invoice PDF: https://dukanam.com/shared/invoices/1?expires=1790000000&signature=example'
email_subject: 'Sri Lakshmi Traders | Payment reminder: INV-1001'
recipients:
-
key: primary
name: 'Ravi Kumar'
designation: 'Primary contact'
phone: '98765 43210'
whatsapp_phone: '919876543210'
email: ravi@example.test
-
key: person-12
name: Priya
designation: Accounts
phone: null
whatsapp_phone: null
email: priya@example.test
properties:
data:
type: object
properties:
customer_pdf_url:
type: string
example: 'https://dukanam.com/invoices/1/shared?expires=1790000000&signature=example'
description: 'Signed public invoice link showing the complete document with print and PDF sharing actions. The link expires after 90 days.'
send:
type: object
properties:
share_url:
type: string
example: 'https://dukanam.com/shared/invoices/1?expires=1790000000&signature=example'
description: 'Signed public invoice link to include in a WhatsApp or SMS message.'
message:
type: string
example: "Hi Ravi Kumar, please find invoice INV-1001 for ₹2,500.00 from Sri Lakshmi Traders. Thank you.\n\nView invoice PDF: https://dukanam.com/shared/invoices/1?expires=1790000000&signature=example"
description: "Suggested message, ending with the public link. Uses the business's invoice sharing template."
email_subject:
type: string
example: 'Invoice INV-1001 from Sri Lakshmi Traders'
description: 'Suggested email subject.'
email_available:
type: boolean
example: true
description: 'Whether this app can send email; when false, offer WhatsApp and SMS only.'
payment_reminder:
type: object
properties:
message:
type: string
example: 'Hi Ravi Kumar, a payment of ₹2,500.00 is pending on invoice INV-1001. Pay to Sri Lakshmi Traders: Account number: 001234567890, IFSC: HDFC0000123. View invoice PDF: https://dukanam.com/shared/invoices/1?expires=1790000000&signature=example'
email_subject:
type: string
example: 'Sri Lakshmi Traders | Payment reminder: INV-1001'
description: 'Suggested customer payment reminder with message and email_subject, including the outstanding amount, due date, visible receiving accounts and invoice link. Null for settled or void invoices. Clients let the shop review and send through the existing email endpoint or WhatsApp/SMS; scheduled push alerts still go to workspace staff.'
recipients:
type: array
example:
- key: primary
name: 'Ravi Kumar'
designation: 'Primary contact'
phone: '98765 43210'
whatsapp_phone: '919876543210'
email: ravi@example.test
- key: person-12
name: Priya
designation: Accounts
phone: null
whatsapp_phone: null
email: priya@example.test
description: 'People at the customer with a phone or email: the primary contact (`key` `primary`) then each additional contact (`key` `person-{id}`), each with `name`, `designation`, `phone`, `whatsapp_phone` (digits with country code, or null) and `email`.'
items:
type: object
properties:
key:
type: string
example: primary
name:
type: string
example: 'Ravi Kumar'
designation:
type: string
example: 'Primary contact'
phone:
type: string
example: '98765 43210'
whatsapp_phone:
type: string
example: '919876543210'
email:
type: string
example: ravi@example.test
description: 'What to send and who can receive it. Returned on this endpoint only.'
tags:
- 'Sales invoices'
put:
summary: 'Save edits to an invoice at any time.'
operationId: saveEditsToAnInvoiceAtAnyTime
description: "PUT and PATCH both replace the editable invoice fields and lines in full. Send the latest revision from GET and include each existing line's id to retain it; omit id for a new line. Missing existing lines are removed. A stale revision returns 422 on revision. The invoice keeps its identifier, number, payment records, and return records. All statuses, including paid, returned, and void, remain editable with no time limit; a void invoice remains void.\n\nThe customer cannot change when payments or returns exist. Returned lines cannot be removed, assigned another item, or reduced below their returned quantity or recorded amounts. Reducing the total below payments retains customer credit. Totals, stock and accounting reconcile atomically, and each save records complete before/after snapshots with the actor and timestamp. Accounting corrections retain original entries and dated reversals/replacements; report date ranges honor each entry's date. Original business tax registration and stored line metadata are preserved.\n\nInvoice edits keep the original invoice date unless the owner explicitly changes issue_date. Revision reversals use the previous transaction dates; replacements use the submitted invoice date, restating that period rather than moving the correction to today. The edit timestamp remains in audit history. There is no invoice-age limit on changing the date."
parameters: []
responses:
200:
description: ''
content:
application/json:
schema:
type: object
example:
data:
id: 1
number: INV-001
status: posted
status_label: Saved
revision: 2
total_paise: 20000
history:
-
id: 2
action: invoice.updated
user:
id: 1
name: 'Shop owner'
old_values:
revision: 1
total_paise: 10000
lines:
-
id: 1
quantity: '1.000'
new_values:
revision: 2
total_paise: 20000
lines:
-
id: 1
quantity: '2.000'
reason: null
created_at: '2026-09-13T10:00:00.000000Z'
properties:
data:
type: object
properties:
id:
type: integer
example: 1
number:
type: string
example: INV-001
status:
type: string
example: posted
status_label:
type: string
example: Saved
description: 'Display label; the legacy saved status is labelled Saved.'
revision:
type: integer
example: 2
description: 'New revision after the edit is saved.'
total_paise:
type: integer
example: 20000
history:
type: array
example:
-
id: 2
action: invoice.updated
user:
id: 1
name: 'Shop owner'
old_values:
revision: 1
total_paise: 10000
lines:
- id: 1
quantity: '1.000'
new_values:
revision: 2
total_paise: 20000
lines:
- id: 1
quantity: '2.000'
reason: null
created_at: '2026-09-13T10:00:00.000000Z'
description: 'Invoice audit records with id, action, user (id and name), old_values, new_values, reason, and created_at. IP addresses, user agents, and internal audit metadata are omitted.'
items:
type: object
properties:
id:
type: integer
example: 2
action:
type: string
example: invoice.updated
user:
type: object
properties:
id:
type: integer
example: 1
name:
type: string
example: 'Shop owner'
old_values:
type: object
properties:
revision:
type: integer
example: 1
total_paise:
type: integer
example: 10000
lines:
type: array
example:
- id: 1
quantity: '1.000'
items:
type: object
properties:
id: { type: integer, example: 1 }
quantity: { type: string, example: '1.000' }
new_values:
type: object
properties:
revision:
type: integer
example: 2
total_paise:
type: integer
example: 20000
lines:
type: array
example:
- id: 1
quantity: '2.000'
items:
type: object
properties:
id: { type: integer, example: 1 }
quantity: { type: string, example: '2.000' }
reason:
type: string
example: null
nullable: true
created_at:
type: string
example: '2026-09-13T10:00:00.000000Z'
422:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'This invoice was changed by someone else. Reload it before saving your changes.'
errors:
revision:
- 'This invoice was changed by someone else. Reload it before saving your changes.'
properties:
message:
type: string
example: 'This invoice was changed by someone else. Reload it before saving your changes.'
errors:
type: object
properties:
revision:
type: array
example:
- 'This invoice was changed by someone else. Reload it before saving your changes.'
items:
type: string
tags:
- 'Sales invoices'
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
line_layout:
type: array
description: 'Optional display rows. Empty or null uses the line array order. On update, omission retains the layout when the line count is unchanged.'
example:
-
type: header
title: 'Kitchen essentials'
-
type: item
line_index: 0
-
type: subtotal
items:
type: object
properties:
type:
type: string
description: 'item, header, or subtotal.'
example: item
line_index:
type: integer
description: 'Zero-based index in lines, required for item rows. Every line must appear exactly once.'
example: 0
nullable: true
title:
type: string
description: 'Heading required for header rows, maximum 100 characters.'
example: 'Kitchen essentials'
nullable: true
required:
- type
show_section_totals:
type: boolean
description: 'Show calculated header totals on document output; defaults true.'
example: true
workers:
type: array
description: 'Must not have more than 10 items.'
example: null
items:
type: object
properties:
worker_id:
type: integer
description: 'Must be at least 1.'
example: 27
commission_kind:
type: string
description: ''
example: percentage
enum:
- percentage
- fixed
rate_basis_points:
type: integer
description: 'Must be between 0 and 10000.'
example: 0
nullable: true
fixed_amount_paise:
type: integer
description: 'Must be between 0 and 99999999900.'
example: 0
nullable: true
attribution_basis_points:
type: integer
description: 'Must be between 1 and 10000.'
example: 2
required:
- worker_id
- commission_kind
- attribution_basis_points
worker_attribution_basis_points:
type: integer
description: 'This field is required when worker_id is present.'
example: 10000
enum:
- 10000
nullable: true
worker_id:
type: integer
description: 'This field is required when worker_rate_basis_points is present. Must be at least 1.'
example: 16
nullable: true
worker_rate_basis_points:
type: integer
description: 'Must be between 0 and 10000.'
example: 1
nullable: true
contact_id:
type: integer
description: ''
example: 16
issue_date:
type: string
description: 'Must be a valid date.'
example: '2026-01-15'
due_date:
type: string
description: 'Must be a valid date. Must be a date after or equal to issue_date.'
example: '2026-01-15'
nullable: true
notes:
type: string
description: 'Must not be greater than 2000 characters.'
example: 'n'
nullable: true
place_of_supply_state_code:
type: string
description: ''
example: 1
enum:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 26
- 27
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 96
- 97
nullable: true
reverse_charge:
type: boolean
description: ''
example: false
prices_include_tax:
type: boolean
description: ''
example: false
lines:
type: array
description: 'Must have at least 1 items. Must not have more than 50 items.'
example:
- []
items:
type: object
properties:
details:
type: string
description: 'Optional additional description, maximum 2000 characters.'
example: 'Deliver in sealed packs.'
nullable: true
item_id:
type: integer
description: ''
example: 16
nullable: true
warehouse_id:
type: integer
description: 'Must be at least 1.'
example: 22
nullable: true
item_batch_id:
type: integer
description: ''
example: 16
nullable: true
serial_numbers:
type: array
description: 'Must not be greater than 64 characters.'
example:
- 'n'
items:
type: string
description:
type: string
description: 'Must not be greater than 255 characters.'
example: 'Animi quos velit et fugiat.'
hsn_sac:
type: string
description: "HSN or SAC for this line; omit to keep the line's current code."
example: '100630'
nullable: true
quantity:
type: number
description: 'Must not be greater than 999999.'
example: 18
unit_price:
type: number
description: 'This field is required when lines.*.item_id is not present. Must be at least 0. Must not be greater than 999999999.'
example: 22
nullable: true
tax_rate:
type: number
description: 'Must be at least 0. Must not be greater than 100.'
example: 24
nullable: true
cess_rate:
type: number
description: 'Must be at least 0. Must not be greater than 100.'
example: 18
nullable: true
discount:
type: number
description: 'Must be at least 0. Must not be greater than 999999999.'
example: 8
nullable: true
price_includes_tax:
type: boolean
description: ''
example: false
id:
type: integer
description: 'Existing line identifier belonging to this invoice; omit for a new line. Values must be distinct.'
example: 1
nullable: true
required:
- description
- quantity
revision:
type: integer
description: 'Current invoice revision from GET; prevents overwriting another edit.'
example: 1
required:
- contact_id
- issue_date
- lines
- revision
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
-
in: path
name: invoice_id
description: 'The ID of the invoice.'
example: 1
required: true
schema:
type: integer
'/api/v1/businesses/{business}/invoices/{invoice_id}/pdf':
get:
summary: 'Download a sales invoice PDF.'
operationId: downloadASalesInvoicePDF
description: 'Returns the same tenant-scoped invoice document used by the web sharing flow.'
parameters: []
responses:
200:
description: 'The generated invoice PDF.'
content:
application/pdf:
schema:
type: string
format: binary
tags:
- 'Sales invoices'
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
-
in: path
name: invoice_id
description: 'The ID of the invoice.'
example: 1
required: true
schema:
type: integer
'/api/v1/businesses/{business}/invoices/{invoice_id}/receipt':
get:
summary: 'Get a sales invoice as a thermal printer receipt.'
operationId: getASalesInvoiceAsAThermalPrinterReceipt
description: "Returns the receipt as ESC/POS printer commands for a 58mm or 80mm thermal printer, so the app can print over Bluetooth, USB or the network with no print dialog. Decode `content_base64` and write the bytes to the printer unchanged. The same bytes are served to the web counter. The supermarket layout includes bill/date/cashier, aligned description/quantity/rate/amount columns, per-line discounts, item/quantity counts, payment methods, savings and a GST-rate summary using stored invoice values. Long descriptions wrap and large amounts retain all digits.\n\nPass `cash_register_id` to use that counter's saved printer settings. The cash drawer opens only when the counter allows it and the sale has a cash payment. A UPI QR prints only while a balance is due, for that balance. Text prints in the printer's built-in ASCII font; the rupee sign prints as `Rs.` and other characters are transliterated.\n\nAvailable to members who can make sales or use the POS."
parameters:
-
in: query
name: cash_register_id
description: 'Counter the receipt prints at. Its saved printer settings (paper, cut, cash drawer) apply. Omit to use the defaults: 80mm, cut, drawer opens for cash sales. Must match an existing stored value.'
example: 1
required: false
schema:
type: integer
description: 'Counter the receipt prints at. Its saved printer settings (paper, cut, cash drawer) apply. Omit to use the defaults: 80mm, cut, drawer opens for cash sales. Must match an existing stored value.'
example: 1
nullable: true
-
in: query
name: paper
description: "Overrides the counter's paper width: `58mm` (32 characters a line) or `80mm` (48 characters a line)."
example: 58mm
required: false
schema:
type: string
description: "Overrides the counter's paper width: `58mm` (32 characters a line) or `80mm` (48 characters a line)."
example: 58mm
enum:
- 58mm
- 80mm
nullable: true
-
in: query
name: cut
description: 'Overrides whether the paper is cut at the end: `1` or `0`.'
example: false
required: false
schema:
type: boolean
description: 'Overrides whether the paper is cut at the end: `1` or `0`.'
example: false
nullable: true
-
in: query
name: open_drawer
description: 'Overrides whether the cash drawer opens: `1` always opens it, `0` never does. When omitted, the drawer opens only if the counter allows it and the sale has a cash payment.'
example: false
required: false
schema:
type: boolean
description: 'Overrides whether the cash drawer opens: `1` always opens it, `0` never does. When omitted, the drawer opens only if the counter allows it and the sale has a cash payment.'
example: false
nullable: true
responses:
200:
description: ''
content:
application/json:
schema:
type: object
example:
data:
format: escpos
invoice_id: 1
invoice_number: INV-0001
cash_register_id: 1
paper: 80mm
characters_per_line: 48
cut: true
opens_drawer: true
auto_print: false
content_base64: G0AbcAAZ+htHYQE...
byte_length: 812
text: "Sri Lakshmi Traders\nTAX INVOICE\nBill: INV-0001 28 Sep 2026"
properties:
data:
type: object
properties:
format:
type: string
example: escpos
description: 'Always `escpos`.'
invoice_id:
type: integer
example: 1
invoice_number:
type: string
example: INV-0001
cash_register_id:
type: integer
example: 1
paper:
type: string
example: 80mm
description: 'Paper width used: `58mm` or `80mm`.'
characters_per_line:
type: integer
example: 48
description: '32 on 58mm, 48 on 80mm.'
cut:
type: boolean
example: true
description: 'Whether the bytes end with a paper cut.'
opens_drawer:
type: boolean
example: true
description: 'Whether the bytes open the cash drawer.'
auto_print:
type: boolean
example: false
description: "The counter's setting to print as soon as a sale is saved. False without a counter."
content_base64:
type: string
example: G0AbcAAZ+htHYQE...
description: 'ESC/POS bytes, base64-encoded. Send to the printer as-is.'
byte_length:
type: integer
example: 812
description: 'Length of the decoded bytes.'
text:
type: string
example: "Sri Lakshmi Traders\nTAX INVOICE\nBill: INV-0001 28 Sep 2026"
description: 'Plain-text copy of what prints, including customer-visible bank/payment instructions, for an on-screen preview.'
422:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'Choose 58mm or 80mm paper.'
errors:
paper:
- 'Choose 58mm or 80mm paper.'
properties:
message:
type: string
example: 'Choose 58mm or 80mm paper.'
errors:
type: object
properties:
paper:
type: array
example:
- 'Choose 58mm or 80mm paper.'
items:
type: string
tags:
- 'Sales invoices'
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
-
in: path
name: invoice_id
description: 'The ID of the invoice.'
example: 1
required: true
schema:
type: integer
'/api/v1/businesses/{business}/invoices/{invoice_id}/email':
post:
summary: 'Email an invoice to people at the customer.'
operationId: emailAnInvoiceToPeopleAtTheCustomer
description: "Sends one email to each chosen person with the invoice PDF attached and a button to the signed public invoice page. Choose people by the `key` values in `send.recipients` from GET `invoices/{invoice}`; each must have an email address. To send on WhatsApp or SMS instead, open the chat on the device with `send.message` and a recipient's `whatsapp_phone` or `phone`. Every email send is recorded in the audit log."
parameters: []
responses:
202:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'Emailed to Ravi Kumar and Priya.'
data:
sent_to:
-
key: primary
name: 'Ravi Kumar'
email: ravi@example.test
-
key: person-12
name: Priya
email: priya@example.test
properties:
message:
type: string
example: 'Emailed to Ravi Kumar and Priya.'
data:
type: object
properties:
sent_to:
type: array
example:
- key: primary
name: 'Ravi Kumar'
email: ravi@example.test
- key: person-12
name: Priya
email: priya@example.test
items:
type: object
properties:
key:
type: string
example: primary
name:
type: string
example: 'Ravi Kumar'
email:
type: string
example: ravi@example.test
422:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'Priya has no email address.'
errors:
recipients.1:
- 'Priya has no email address.'
properties:
message:
type: string
example: 'Priya has no email address.'
errors:
type: object
properties:
recipients.1:
type: array
example:
- 'Priya has no email address.'
items:
type: string
tags:
- 'Sales invoices'
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
recipients:
type: array
description: 'A recipient key: `primary` for the party''s primary contact, or `person-{id}` for an additional contact. Must match the regex /^(primary|person-\d+)$/.'
example:
- primary
- person-12
items:
type: string
recipient_emails:
type: array
description: 'Must be a valid email address. Must not be greater than 254 characters.'
example:
primary: accounts@example.com
items:
type: string
nullable: true
subject:
type: string
description: 'Email subject line. Must not be greater than 150 characters.'
example: 'Sri Lakshmi Traders | Purchase order: PO-0007'
message:
type: string
description: 'Message body. Blank lines separate paragraphs. The PDF is attached and a button links to the signed public page. Must not be greater than 2000 characters.'
example: 'Hi Ravi, please find purchase order PO-0007 for ₹12,500.00. Please confirm the order and the delivery date.'
from:
type: string
description: 'Ledger statements only: start date, inclusive. Must be a valid date.'
example: '2026-04-01'
nullable: true
to:
type: string
description: 'Ledger statements only: end date, inclusive. Must be on or after `from`. Must be a valid date. Must be a date after or equal to from.'
example: '2027-03-31'
nullable: true
required:
- recipients
- subject
- message
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
-
in: path
name: invoice_id
description: 'The ID of the invoice.'
example: 1
required: true
schema:
type: integer
'/api/v1/businesses/{business}/invoices/{invoice_id}/payments':
post:
summary: 'Record one or many customer payments.'
operationId: recordOneOrManyCustomerPayments
description: 'Post a single payment to `invoices/{invoice}/payments`, or send up to 25 payments — each naming its own `invoice_id` — as a `payments` array to `invoices/payments` to settle several invoices in one atomic request.'
parameters: []
responses:
422:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'Enter an amount up to the current outstanding balance.'
errors:
payments.1.amount:
- 'Enter an amount up to the current outstanding balance.'
properties:
message:
type: string
example: 'Enter an amount up to the current outstanding balance.'
errors:
type: object
properties:
payments.1.amount:
type: array
example:
- 'Enter an amount up to the current outstanding balance.'
items:
type: string
tags:
- 'Sales invoices'
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
payments:
type: array
description: 'Must have at least 1 items. Must not have more than 25 items.'
example:
- []
items:
type: object
properties:
invoice_id:
type: integer
description: ''
example: 16
amount:
type: number
description: 'Must not be greater than 999999999.'
example: 22
method:
type: string
description: ''
example: cash
enum:
- cash
- bank
- upi
- card
- cheque
- other
payment_account_id:
type: integer
description: ''
example: 16
paid_on:
type: string
description: 'Must be a valid date.'
example: '2026-01-15'
nullable: true
reference:
type: string
description: 'Must not be greater than 64 characters.'
example: 'n'
nullable: true
notes:
type: string
description: 'Must not be greater than 1000 characters.'
example: g
nullable: true
required:
- invoice_id
- amount
- method
- payment_account_id
required:
- payments
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
-
in: path
name: invoice_id
description: 'The ID of the invoice.'
example: 1
required: true
schema:
type: integer
'/api/v1/businesses/{business}/invoices/{invoice_id}/void':
post:
summary: 'Void one or many unpaid invoices that have no returns.'
operationId: voidOneOrManyUnpaidInvoicesThatHaveNoReturns
description: "Invoices with a payment or a sales return must be settled through the corresponding payment or return workflow and cannot be voided.\n\nVoid a single invoice at `invoices/{invoice}/void`, or send up to 25 entries — each naming its own `invoice_id` — as an `invoices` array to `invoices/void`. A batch may carry one shared request-level `reason` instead of repeating it per entry, and either voids completely or not at all."
parameters: []
responses:
422:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'Only unpaid, active invoices without returns can be voided.'
errors:
invoice:
- 'Only unpaid, active invoices without returns can be voided.'
properties:
message:
type: string
example: 'Only unpaid, active invoices without returns can be voided.'
errors:
type: object
properties:
invoice:
type: array
example:
- 'Only unpaid, active invoices without returns can be voided.'
items:
type: string
tags:
- 'Sales invoices'
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
invoices:
type: array
description: 'Must have at least 1 items. Must not have more than 25 items.'
example:
- []
items:
type: object
properties:
invoice_id:
type: integer
description: ''
example: 16
reason:
type: string
description: 'This field is required when reason is not present. Must be at least 3 characters. Must not be greater than 255 characters.'
example: 'n'
nullable: true
required:
- invoice_id
reason:
type: string
description: 'Must be at least 3 characters. Must not be greater than 255 characters.'
example: b
nullable: true
required:
- invoices
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
-
in: path
name: invoice_id
description: 'The ID of the invoice.'
example: 1
required: true
schema:
type: integer
'/api/v1/businesses/{business}/documents/{document}/recurring-settings':
get:
summary: 'Get recurring schedule settings and generation history.'
operationId: getRecurringScheduleSettingsAndGenerationHistory
description: "Requires sales permission and recurring-invoice entitlement. Returns the schedule,\ncustomer recipients (including contacts missing an email) and the latest 25 occurrences.\nOwner notification preference defaults on; a missing owner email is not configured."
parameters: []
responses:
401:
description: ''
content:
application/json:
schema:
type: object
example:
message: Unauthenticated.
properties:
message:
type: string
example: Unauthenticated.
tags:
- 'Business documents'
put:
summary: 'Update a recurring schedule and automatic email preferences.'
operationId: updateARecurringScheduleAndAutomaticEmailPreferences
description: "Changes affect future invoices only. Start/next date must follow the last generated date.\nMonthly dates stay anchored to their original day, clamped to shorter months. Creation\nuses the business timezone. Customer auto-email requires selected valid emails; missing\naddresses can be saved with recipient_emails. Existing emails cannot be overwritten.\nOwner notifications default on but missing email never blocks invoice creation. Pause\nwith schedule_status=paused, resume with active; set a future next date to skip backlog.\nRequires sales permission and recurring-invoice entitlement; foreign schedules return 404."
parameters: []
responses: {}
tags:
- 'Business documents'
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
frequency:
type: string
description: 'weekly, monthly, quarterly or yearly.'
example: monthly
next_issue_date:
type: date
description: 'Next invoice date in business timezone.'
example: '2026-10-31'
end_date:
type: date
description: 'Nullable inclusive last date.'
example: '2027-10-31'
repeat_every:
type: integer
description: 'Repeat every 1–52 selected periods.'
example: 1
due_after_days:
type: integer
description: 'Payment due 0–365 days after creation date.'
example: 7
auto_email:
type: boolean
description: 'Automatically email generated invoices to selected customer contacts.'
example: true
email_recipients:
type: array
description: 'Selected primary or person-ID keys belonging to this customer.'
example:
- primary
items:
type: string
recipient_emails:
type: object
description: 'Missing emails to save for selected recipients.'
example:
primary: accounts@example.com
properties: {}
notify_owner:
type: boolean
description: 'Notify owner separately; defaults true.'
example: true
owner_email:
type: string
description: 'Nullable notification override, not a change to account/login email.'
example: owner@example.com
schedule_status:
type: string
description: 'active or paused.'
example: active
required:
- frequency
- next_issue_date
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
-
in: path
name: document
description: 'The document.'
example: architecto
required: true
schema:
type: string
'/api/v1/businesses/{business}/documents/{document}/recurring-runs/{run}/retry':
post:
summary: 'Retry failed emails for an existing recurring invoice.'
operationId: retryFailedEmailsForAnExistingRecurringInvoice
description: "Never creates another invoice or resends recipients already marked sent. Missing-address\nskips require updating the contact and sending that invoice manually. Queue retries are\nautomatic (three attempts); this endpoint restarts exhausted pending/failed delivery.\nRequires sales permission and recurring-invoice entitlement; foreign runs return 404."
parameters: []
responses:
202:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'Unsent emails queued for retry.'
properties:
message:
type: string
example: 'Unsent emails queued for retry.'
tags:
- 'Business documents'
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
-
in: path
name: document
description: 'The document.'
example: architecto
required: true
schema:
type: string
-
in: path
name: run
description: ''
example: architecto
required: true
schema:
type: string
'/api/v1/businesses/{business}/documents':
get:
summary: 'List business documents.'
operationId: listBusinessDocuments
description: "Saving a bill updates stock and account balances automatically. There is no extra confirmation step.\nDisplay status_label in screens; raw status values are retained for existing integrations."
parameters: []
responses:
401:
description: ''
content:
application/json:
schema:
type: object
example:
message: Unauthenticated.
properties:
message:
type: string
example: Unauthenticated.
tags:
- 'Business documents'
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
type:
type: string
description: ''
example: quote
enum:
- quote
- purchase_order
- purchase_invoice
- recurring_invoice
- sales_return
- purchase_return
- delivery_challan
status:
type: string
description: 'Must not be greater than 32 characters.'
example: b
nullable: true
contact_id:
type: integer
description: ''
example: 16
nullable: true
per_page:
type: integer
description: 'Must be at least 1. Must not be greater than 100.'
example: 22
nullable: true
required:
- type
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
'/api/v1/businesses/{business}/documents/{document_type}':
post:
summary: 'Create a business document.'
operationId: createABusinessDocument
description: "Document resources include `show_gst_details`, based on the registration frozen at issue. When false, hide GST identifiers, HSN/tax columns, supply and reverse-charge details, GST amounts and tax summaries. Previously issued registered documents keep their original tax treatment.\n\nCreate a business document or recurring invoice schedule.\n\nTax is split by place of supply as on sales invoices: CGST and SGST (or UTGST) within the business's state, IGST across states and for SEZ parties. Responses carry `place_of_supply_state_code`, `supply_type`, `state_tax_label`, the `cgst_total_paise`, `sgst_total_paise`, `igst_total_paise` and `cess_total_paise` totals, and per-line `hsn_sac` and tax heads.\nSales and purchase returns retain the source line's item even after it has been soft deleted,\nincluding its inventory, batch, and serial handling. Deleted items cannot be selected on new documents."
parameters: []
responses: {}
tags:
- 'Business documents'
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
line_layout:
type: array
description: 'Optional display rows. Empty or null uses the line array order. On update, omission retains the layout when the line count is unchanged.'
example:
-
type: header
title: 'Kitchen essentials'
-
type: item
line_index: 0
-
type: subtotal
items:
type: object
properties:
type:
type: string
description: 'item, header, or subtotal.'
example: item
line_index:
type: integer
description: 'Zero-based index in lines, required for item rows. Every line must appear exactly once.'
example: 0
nullable: true
title:
type: string
description: 'Heading required for header rows, maximum 100 characters.'
example: 'Kitchen essentials'
nullable: true
required:
- type
show_section_totals:
type: boolean
description: 'Show calculated header totals on document output; defaults true.'
example: true
contact_id:
type: integer
description: ''
example: 16
source_invoice_id:
type: integer
description: ''
example: 16
nullable: true
source_document_id:
type: integer
description: ''
example: 16
nullable: true
issue_date:
type: string
description: 'Must be a valid date.'
example: '2026-01-15'
due_date:
type: string
description: 'Must be a valid date. Must be a date after or equal to issue_date.'
example: '2026-01-15'
nullable: true
frequency:
type: string
description: ''
example: weekly
enum:
- weekly
- monthly
- quarterly
- yearly
nullable: true
next_issue_date:
type: string
description: 'Must be a valid date.'
example: '2026-01-15'
nullable: true
end_date:
type: string
description: 'Must be a valid date. Must be a date after or equal to next_issue_date.'
example: '2026-01-15'
nullable: true
notes:
type: string
description: 'Must not be greater than 2000 characters.'
example: 'n'
nullable: true
external_reference:
type: string
description: 'Must not be greater than 64 characters.'
example: g
nullable: true
challan_reason:
type: string
description: ''
example: job_work
enum:
- job_work
- branch_transfer
- approval
- line_sale
- other
nullable: true
destination_warehouse_id:
type: integer
description: 'Must be at least 1.'
example: 66
nullable: true
expected_return_on:
type: string
description: 'Must be a valid date. Must be a date after or equal to issue_date.'
example: '2026-01-15'
nullable: true
place_of_supply_state_code:
type: string
description: 'Two-digit GST state code, or 96 for an export. 99 is refused.'
example: '29'
nullable: true
reverse_charge:
type: boolean
description: ''
example: false
prices_include_tax:
type: boolean
description: ''
example: false
lines:
type: array
description: 'Must have at least 1 items. Must not have more than 100 items.'
example:
- []
items:
type: object
properties:
details:
type: string
description: 'Optional additional description, maximum 2000 characters.'
example: 'Deliver in sealed packs.'
nullable: true
item_id:
type: integer
description: ''
example: 16
nullable: true
warehouse_id:
type: integer
description: 'Must be at least 1.'
example: 22
nullable: true
item_batch_id:
type: integer
description: ''
example: 16
nullable: true
batch_number:
type: string
description: 'Must not be greater than 64 characters.'
example: 'n'
nullable: true
batch_expiry_date:
type: string
description: 'Must be a valid date.'
example: '2026-01-15'
nullable: true
batch_manufactured_on:
type: string
description: 'Must be a valid date.'
example: '2026-01-15'
nullable: true
batch_mrp:
type: number
description: 'Must be at least 0. Must not be greater than 999999999.'
example: 7
nullable: true
serial_numbers:
type: array
description: 'Must not be greater than 64 characters.'
example:
- z
items:
type: string
warranty_until:
type: string
description: 'Must be a valid date.'
example: '2026-01-15'
nullable: true
source_invoice_line_id:
type: integer
description: ''
example: 16
nullable: true
source_document_line_id:
type: integer
description: ''
example: 16
nullable: true
description:
type: string
description: 'Must not be greater than 255 characters.'
example: 'Et animi quos velit et fugiat.'
hsn_sac:
type: string
description: "HSN (4, 6 or 8 digits) or SAC (6 digits) for this line; defaults to the item's code."
example: "998719\n\nRecurring schedules support the options below, with the same validation as the\nrecurring-settings endpoint. Owner notification defaults on; without an owner email,\nhistory remains available and invoice creation continues. Customer auto-email defaults off."
nullable: true
quantity:
type: number
description: 'Must not be greater than 999999.'
example: 18
unit_price:
type: number
description: 'Must be at least 0. Must not be greater than 999999999.'
example: 22
tax_rate:
type: number
description: 'Must be at least 0. Must not be greater than 100.'
example: 24
nullable: true
cess_rate:
type: number
description: 'Must be at least 0. Must not be greater than 100.'
example: 18
nullable: true
discount:
type: number
description: 'Must be at least 0. Must not be greater than 999999999.'
example: 8
nullable: true
price_includes_tax:
type: boolean
description: ''
example: false
required:
- description
- quantity
- unit_price
repeat_every:
type: integer
description: 'Recurring only: every 1–52 selected periods.'
example: 1
due_after_days:
type: integer
description: 'Recurring only: 0–365 days after issue.'
example: 7
auto_email:
type: boolean
description: 'Recurring only: automatically email generated invoices.'
example: false
email_recipients:
type: array
description: 'Recurring only: primary or person-ID keys on this customer.'
example:
- primary
items:
type: string
recipient_emails:
type: object
description: 'Recurring only: missing emails to save for selected contacts.'
example:
primary: accounts@example.com
properties: {}
notify_owner:
type: boolean
description: 'Recurring only: separate owner notification, default true.'
example: true
owner_email:
type: string
description: 'Recurring only: nullable notification address; defaults to owner account email.'
example: owner@example.com
schedule_status:
type: string
description: 'Recurring only: active or paused.'
example: active
required:
- contact_id
- issue_date
- lines
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
-
in: path
name: document_type
description: ''
example: quote|purchase_order|purchase_invoice|recurring_invoice|sales_return|purchase_return|delivery_challan
required: true
schema:
type: string
'/api/v1/businesses/{business}/documents/{document_id}':
get:
summary: ''
operationId: getApiV1BusinessesBusinessDocumentsDocument_id
description: ''
parameters: []
responses:
401:
description: ''
content:
application/json:
schema:
type: object
example:
message: Unauthenticated.
properties:
message:
type: string
example: Unauthenticated.
tags:
- 'Business documents'
patch:
summary: 'Update a quotation, purchase order, purchase invoice, or delivery challan.'
operationId: updateAQuotationPurchaseOrderPurchaseInvoiceOrDeliveryChallan
description: "Replaces the party, dates, note, and every line of a quotation, purchase order, or delivery challan that\nhas not been converted yet. A quotation that the customer already answered returns to `open` and its\nrecorded decision is cleared, so the same approval link asks for a fresh decision on the updated figures.\nA delivery challan that had already taken goods off the shelf returns them before the replacement lines\ntake out their own, so stock is never held out twice for one challan.\nPurchase invoices replace stock, supplier ledger, and accounting entries atomically, retaining their number.\nInvoice edits keep the original invoice date unless the owner explicitly changes issue_date. Purchase revision reversals use the previous transaction dates and replacements use the submitted invoice date, restating that period. The audit timestamp records when the edit occurred. There is no invoice-age limit on changing the date; existing stock, return, and payment safeguards still apply.\nReturns, batch or serial tracked goods, and later stock activity prevent purchase invoice edits (422).\nPaid invoices retain payments: the supplier cannot change and the total cannot fall below applied payments.\nThe complete contact_id, issue_date, and lines payload is required; omitted optional fields are cleared.\nItems already on this document can be retained after soft deletion. Other deleted items\nand foreign-tenant items return 404; deletion never bypasses the existing edit restrictions."
parameters: []
responses: {}
tags:
- 'Business documents'
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
line_layout:
type: array
description: 'Optional display rows. Empty or null uses the line array order. On update, omission retains the layout when the line count is unchanged.'
example:
-
type: header
title: 'Kitchen essentials'
-
type: item
line_index: 0
-
type: subtotal
items:
type: object
properties:
type:
type: string
description: 'item, header, or subtotal.'
example: item
line_index:
type: integer
description: 'Zero-based index in lines, required for item rows. Every line must appear exactly once.'
example: 0
nullable: true
title:
type: string
description: 'Heading required for header rows, maximum 100 characters.'
example: 'Kitchen essentials'
nullable: true
required:
- type
show_section_totals:
type: boolean
description: 'Show calculated header totals on document output; defaults true.'
example: true
contact_id:
type: integer
description: ''
example: 16
issue_date:
type: string
description: 'Must be a valid date.'
example: '2026-01-15'
due_date:
type: string
description: 'Must be a valid date. Must be a date after or equal to issue_date.'
example: '2026-01-15'
nullable: true
notes:
type: string
description: 'Must not be greater than 2000 characters.'
example: 'n'
nullable: true
external_reference:
type: string
description: 'Must not be greater than 64 characters.'
example: g
nullable: true
challan_reason:
type: string
description: ''
example: job_work
enum:
- job_work
- branch_transfer
- approval
- line_sale
- other
nullable: true
destination_warehouse_id:
type: integer
description: 'Must be at least 1.'
example: 66
nullable: true
expected_return_on:
type: string
description: 'Must be a valid date. Must be a date after or equal to issue_date.'
example: '2026-01-15'
nullable: true
place_of_supply_state_code:
type: string
description: ''
example: 1
enum:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 26
- 27
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 96
- 97
nullable: true
reverse_charge:
type: boolean
description: ''
example: false
prices_include_tax:
type: boolean
description: ''
example: false
lines:
type: array
description: 'Must have at least 1 items. Must not have more than 100 items.'
example:
- []
items:
type: object
properties:
details:
type: string
description: 'Optional additional description, maximum 2000 characters.'
example: 'Deliver in sealed packs.'
nullable: true
item_id:
type: integer
description: ''
example: 16
nullable: true
warehouse_id:
type: integer
description: 'Must be at least 1.'
example: 22
nullable: true
item_batch_id:
type: integer
description: ''
example: 16
nullable: true
serial_numbers:
type: array
description: 'Must not be greater than 64 characters.'
example:
- 'n'
items:
type: string
description:
type: string
description: 'Must not be greater than 255 characters.'
example: 'Animi quos velit et fugiat.'
hsn_sac:
type: string
description: 'Must match the regex /^[0-9]{4}(?:[0-9]{2}){0,2}$/.'
example: '5593:14):23)'
nullable: true
quantity:
type: number
description: 'Must not be greater than 999999.'
example: 18
unit_price:
type: number
description: 'Must be at least 0. Must not be greater than 999999999.'
example: 22
tax_rate:
type: number
description: 'Must be at least 0. Must not be greater than 100.'
example: 24
nullable: true
cess_rate:
type: number
description: 'Must be at least 0. Must not be greater than 100.'
example: 18
nullable: true
discount:
type: number
description: 'Must be at least 0. Must not be greater than 999999999.'
example: 8
nullable: true
price_includes_tax:
type: boolean
description: ''
example: false
required:
- description
- quantity
- unit_price
required:
- contact_id
- issue_date
- lines
delete:
summary: 'Delete a purchase invoice.'
operationId: deleteAPurchaseInvoice
description: "Requires purchases permission. Reverses stock, supplier ledger and accounting atomically,\nretaining the document with status `void` for audit. Outstanding becomes zero.\nPayments, applied advances, purchase returns, batch or serial tracked goods, later stock\nactivity, and already voided invoices prevent deletion (422). Other document types return 404.\nAny failed deletion leaves the invoice and all balances unchanged."
parameters: []
responses:
200:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'Purchase invoice deleted. Audit record retained.'
properties:
message:
type: string
example: 'Purchase invoice deleted. Audit record retained.'
422:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'This invoice has payments or applied advances and cannot be deleted.'
errors:
status:
- 'This invoice has payments or applied advances and cannot be deleted.'
properties:
message:
type: string
example: 'This invoice has payments or applied advances and cannot be deleted.'
errors:
type: object
properties:
status:
type: array
example:
- 'This invoice has payments or applied advances and cannot be deleted.'
items:
type: string
tags:
- 'Business documents'
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
-
in: path
name: document_id
description: 'The ID of the document.'
example: 1
required: true
schema:
type: integer
'/api/v1/businesses/{business}/documents/{document_id}/convert':
post:
summary: 'Convert a document into the bill it becomes.'
operationId: convertADocumentIntoTheBillItBecomes
description: "A quotation and a delivery challan both become a tax invoice; a purchase order becomes a purchase\ninvoice. A delivery challan may be converted exactly once: a second attempt answers `422`, leaving the\nfirst invoice as the only bill for those goods. GST on a converted challan is computed at the conversion\ndate, because the price is frequently not settled when the goods leave the shop."
parameters: []
responses: {}
tags:
- 'Business documents'
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
-
in: path
name: document_id
description: 'The ID of the document.'
example: 1
required: true
schema:
type: integer
'/api/v1/businesses/{business}/documents/{document_id}/pdf':
get:
summary: 'Download a document PDF.'
operationId: downloadADocumentPDF
description: 'The same A4 paper the party receives, for quotations, purchase orders, delivery challans, sales returns and purchase returns. Other document types answer `404`.'
parameters: []
responses:
200:
description: 'The generated document PDF.'
content:
application/pdf:
schema:
type: string
format: binary
tags:
- 'Business documents'
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
-
in: path
name: document_id
description: 'The ID of the document.'
example: 1
required: true
schema:
type: integer
'/api/v1/businesses/{business}/documents/{document_id}/email':
post:
summary: 'Email a document to people at the party.'
operationId: emailADocumentToPeopleAtTheParty
description: "Sends one email to each chosen person with the document PDF attached and a button to its signed public page. Works for quotations, purchase orders, purchase invoices, recurring invoice schedules, delivery challans, sales returns and purchase returns that are not voided. Delivery is queued through platform Cloudflare email with the shop’s branding. Choose people by the `key` values in `send.recipients` from GET `documents/{document}`; each must have an email address. To send on WhatsApp or SMS instead, open the chat on the device with `send.message` and a recipient's `whatsapp_phone` or `phone`. Every email send is recorded in the audit log."
parameters: []
responses:
202:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'Emailed to Ravi Kumar.'
data:
sent_to:
-
key: primary
name: 'Ravi Kumar'
email: ravi@example.test
properties:
message:
type: string
example: 'Emailed to Ravi Kumar.'
data:
type: object
properties:
sent_to:
type: array
example:
- key: primary
name: 'Ravi Kumar'
email: ravi@example.test
items:
type: object
properties:
key:
type: string
example: primary
name:
type: string
example: 'Ravi Kumar'
email:
type: string
example: ravi@example.test
422:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'Choose people listed on this party.'
errors:
recipients.0:
- 'Choose people listed on this party.'
properties:
message:
type: string
example: 'Choose people listed on this party.'
errors:
type: object
properties:
recipients.0:
type: array
example:
- 'Choose people listed on this party.'
items:
type: string
tags:
- 'Business documents'
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
recipients:
type: array
description: 'A recipient key: `primary` for the party''s primary contact, or `person-{id}` for an additional contact. Must match the regex /^(primary|person-\d+)$/.'
example:
- primary
- person-12
items:
type: string
recipient_emails:
type: array
description: 'Must be a valid email address. Must not be greater than 254 characters.'
example:
primary: accounts@example.com
items:
type: string
nullable: true
subject:
type: string
description: 'Email subject line. Must not be greater than 150 characters.'
example: 'Sri Lakshmi Traders | Purchase order: PO-0007'
message:
type: string
description: 'Message body. Blank lines separate paragraphs. The PDF is attached and a button links to the signed public page. Must not be greater than 2000 characters.'
example: 'Hi Ravi, please find purchase order PO-0007 for ₹12,500.00. Please confirm the order and the delivery date.'
from:
type: string
description: 'Ledger statements only: start date, inclusive. Must be a valid date.'
example: '2026-04-01'
nullable: true
to:
type: string
description: 'Ledger statements only: end date, inclusive. Must be on or after `from`. Must be a valid date. Must be a date after or equal to from.'
example: '2027-03-31'
nullable: true
required:
- recipients
- subject
- message
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
-
in: path
name: document_id
description: 'The ID of the document.'
example: 1
required: true
schema:
type: integer
'/api/v1/businesses/{business_id}/documents/{document_id}/attachments':
post:
summary: 'Attach a supplier invoice.'
operationId: attachASupplierInvoice
description: "Upload one PDF, JPEG or PNG using multipart/form-data. Maximum 10 MB per file and\n10 attachments per purchase invoice. Requires purchases permission. Files are private\nto this business. Voided invoices are read-only (422); other document types return 404.\nAttachments do not change stock, balances or the invoice's accounting entries."
parameters: []
responses:
201:
description: ''
content:
application/json:
schema:
type: object
example:
data:
id: 1
name: supplier-invoice.pdf
mime_type: application/pdf
size_bytes: 2048
created_at: '2026-09-28T05:00:00.000000Z'
download_url: 'https://dukanam.com/api/v1/businesses/1/documents/1/attachments/1'
properties:
data:
type: object
properties:
id:
type: integer
example: 1
name:
type: string
example: supplier-invoice.pdf
mime_type:
type: string
example: application/pdf
size_bytes:
type: integer
example: 2048
created_at:
type: string
example: '2026-09-28T05:00:00.000000Z'
download_url:
type: string
example: 'https://dukanam.com/api/v1/businesses/1/documents/1/attachments/1'
422:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'A purchase invoice can have up to 10 attachments.'
errors:
file:
- 'A purchase invoice can have up to 10 attachments.'
properties:
message:
type: string
example: 'A purchase invoice can have up to 10 attachments.'
errors:
type: object
properties:
file:
type: array
example:
- 'A purchase invoice can have up to 10 attachments.'
items:
type: string
tags:
- 'Business documents'
requestBody:
required: true
content:
multipart/form-data:
schema:
type: object
properties:
file:
type: string
format: binary
description: 'Supplier invoice PDF, JPEG or PNG, up to 10240 KB.'
required:
- file
parameters:
-
in: path
name: business_id
description: 'The ID of the business.'
example: 1
required: true
schema:
type: integer
-
in: path
name: document_id
description: 'The ID of the document.'
example: 1
required: true
schema:
type: integer
'/api/v1/businesses/{business_id}/documents/{document_id}/attachments/{attachment_id}':
get:
summary: 'Download an attached supplier invoice.'
operationId: downloadAnAttachedSupplierInvoice
description: "Requires authentication and purchases permission in the owning business, including when\nfollowing download_url. Available on voided invoices for audit. Missing files, foreign-tenant\ninvoices and attachments belonging to a different invoice return 404. No public storage URL is exposed."
parameters:
-
in: query
name: preview
description: 'Use 1 to display the PDF or image inline instead of downloading it. Authentication and tenant checks still apply.'
example: true
required: false
schema:
type: boolean
description: 'Use 1 to display the PDF or image inline instead of downloading it. Authentication and tenant checks still apply.'
example: true
responses:
200:
description: 'The original supplier invoice file, inline when preview=1 or downloaded otherwise.'
content:
application/octet-stream:
schema:
type: string
format: binary
tags:
- 'Business documents'
requestBody:
required: false
content:
application/json:
schema:
type: object
properties:
preview:
type: boolean
description: ''
example: false
delete:
summary: 'Remove an attached supplier invoice.'
operationId: removeAnAttachedSupplierInvoice
description: "Requires purchases permission. Removes the attachment without changing the invoice or\nits balances. Voided invoices are read-only (422). Tenant and invoice mismatches return 404."
parameters: []
responses:
204:
description: ''
content:
application/json:
schema:
type: object
example: {}
properties: {}
tags:
- 'Business documents'
parameters:
-
in: path
name: business_id
description: 'The ID of the business.'
example: 1
required: true
schema:
type: integer
-
in: path
name: document_id
description: 'The ID of the document.'
example: 1
required: true
schema:
type: integer
-
in: path
name: attachment_id
description: 'The ID of the attachment.'
example: 1
required: true
schema:
type: integer
'/api/v1/businesses/{business}/payments':
get:
summary: 'List payments.'
operationId: listPayments
description: "Payments received from customers or made to suppliers, newest first. Advances — money taken\nor paid before there was a bill — appear here too, with `is_advance` set and the part not\nyet put towards a bill in `unapplied_paise`."
parameters:
-
in: query
name: direction
description: '`received` for customer payments, `made` for supplier payments.'
example: received
required: true
schema:
type: string
description: '`received` for customer payments, `made` for supplier payments.'
example: received
-
in: query
name: from
description: 'Earliest payment date.'
example: '2026-09-01'
required: false
schema:
type: string
description: 'Earliest payment date.'
example: '2026-09-01'
-
in: query
name: to
description: 'Latest payment date.'
example: '2026-09-30'
required: false
schema:
type: string
description: 'Latest payment date.'
example: '2026-09-30'
-
in: query
name: contact_id
description: "Only this customer's or supplier's payments."
example: 42
required: false
schema:
type: integer
description: "Only this customer's or supplier's payments."
example: 42
-
in: query
name: is_advance
description: '`1` for advances only, `0` to leave advances out.'
example: true
required: false
schema:
type: boolean
description: '`1` for advances only, `0` to leave advances out.'
example: true
-
in: query
name: unapplied
description: '`1` for advances that still have money left to apply.'
example: true
required: false
schema:
type: boolean
description: '`1` for advances that still have money left to apply.'
example: true
-
in: query
name: per_page
description: 'Page size, up to 100.'
example: 20
required: false
schema:
type: integer
description: 'Page size, up to 100.'
example: 20
responses:
401:
description: ''
content:
application/json:
schema:
type: object
example:
message: Unauthenticated.
properties:
message:
type: string
example: Unauthenticated.
tags:
- Payments
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
direction:
type: string
description: ''
example: received
enum:
- received
- made
from:
type: string
description: 'Must be a valid date.'
example: '2026-01-15'
nullable: true
to:
type: string
description: 'Must be a valid date. Must be a date after or equal to from.'
example: '2026-01-15'
nullable: true
contact_id:
type: integer
description: ''
example: 16
nullable: true
is_advance:
type: boolean
description: ''
example: false
nullable: true
unapplied:
type: boolean
description: ''
example: false
nullable: true
per_page:
type: integer
description: 'Must be at least 1. Must not be greater than 100.'
example: 22
nullable: true
required:
- direction
post:
summary: 'Record a payment.'
operationId: recordAPayment
description: "Settles a bill, or — with `is_advance` — records money taken from a customer or paid to a\nsupplier before any bill exists. An advance moves the account balance, the cash register,\nthe party's khata and the books the day it is recorded, so the party carries a credit\nbalance until a bill takes it up through `POST /payments/{payment}/apply`."
parameters: []
responses:
201:
description: ''
content:
application/json:
schema:
type: object
example:
data:
id: 9
direction: received
is_advance: true
amount_paise: 500000
applied_paise: 0
unapplied_paise: 500000
paid_on: '2026-09-25'
method: upi
invoice_id: null
business_document_id: null
properties:
data:
type: object
properties:
id:
type: integer
example: 9
direction:
type: string
example: received
is_advance:
type: boolean
example: true
description: 'Whether this is an advance.'
amount_paise:
type: integer
example: 500000
applied_paise:
type: integer
example: 0
unapplied_paise:
type: integer
example: 500000
description: 'How much of an advance is still available to apply.'
paid_on:
type: string
example: '2026-09-25'
method:
type: string
example: upi
invoice_id:
type: string
example: null
nullable: true
business_document_id:
type: string
example: null
nullable: true
422:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'Choose the customer who paid this advance.'
errors:
contact_id:
- 'Choose the customer who paid this advance.'
properties:
message:
type: string
example: 'Choose the customer who paid this advance.'
errors:
type: object
properties:
contact_id:
type: array
example:
- 'Choose the customer who paid this advance.'
items:
type: string
tags:
- Payments
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
is_advance:
type: boolean
description: 'Record an advance against the party rather than a payment against a bill.'
example: false
nullable: true
contact_id:
type: integer
description: 'The customer (received) or supplier (made) who paid or was paid the advance. Required when `is_advance` is true; ignored otherwise.'
example: 42
nullable: true
invoice_id:
type: integer
description: 'The sales invoice being settled. Required for a `received` payment that is not an advance.'
example: 16
nullable: true
business_document_id:
type: integer
description: 'The purchase invoice being settled. Required for a `made` payment that is not an advance.'
example: 16
nullable: true
amount:
type: number
description: 'Must not be greater than 999999999.'
example: 22
paid_on:
type: string
description: 'Must be a valid date.'
example: '2026-01-15'
method:
type: string
description: ''
example: cash
enum:
- cash
- bank
- upi
- card
- cheque
- other
payment_account_id:
type: integer
description: ''
example: 16
nullable: true
reference:
type: string
description: 'Must not be greater than 64 characters.'
example: 'n'
nullable: true
notes:
type: string
description: 'Must not be greater than 1000 characters.'
example: g
nullable: true
direction:
type: string
description: '`received` for a customer payment, `made` for a supplier payment.'
example: received
required:
- amount
- paid_on
- method
- direction
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
'/api/v1/businesses/{business}/payments/{payment_id}':
get:
summary: 'Get a payment and its email/share options.'
operationId: getAPaymentAndItsEmailshareOptions
description: "Requires sales permission for received payments and purchases permission for made\npayments. Includes advances. Foreign business payments return 404."
parameters: []
responses:
401:
description: ''
content:
application/json:
schema:
type: object
example:
message: Unauthenticated.
properties:
message:
type: string
example: Unauthenticated.
tags:
- Payments
patch:
summary: 'Edit an allocated payment.'
operationId: editAnAllocatedPayment
description: "Correct a payment allocated directly to a sales or purchase invoice, including a POS\nreceipt. Requires sales permission for received payments or purchases for made payments.\nThe party, direction, advance designation and invoice allocation cannot change. Advances\nand voided invoices return 422. Amount is capped at the current payment plus the bill's\noutstanding balance (an existing overpayment may be retained or reduced).\nUpdates bill balances/status, khata, account journals and cash movements atomically;\noriginal financial entries are reversed and replacements saved, with payment.updated\naudit values. Reconciled payments cannot change amount, date, method or account. Payments\nin closed cash sessions cannot change amount, method or account; reference and notes can\nstill be corrected. Moving to cash requires an open register. Foreign records return 404.\nOmitted fields retain their values; null reference/notes clear them."
parameters: []
responses:
200:
description: ''
content:
application/json:
schema:
type: object
example:
data:
id: 9
direction: received
is_advance: false
amount_paise: 100000
paid_on: '2026-09-30'
method: upi
payment_account_id: 3
reference: UTR-123
notes: 'Corrected receipt'
invoice_id: 16
properties:
data:
type: object
properties:
id:
type: integer
example: 9
direction:
type: string
example: received
is_advance:
type: boolean
example: false
amount_paise:
type: integer
example: 100000
paid_on:
type: string
example: '2026-09-30'
method:
type: string
example: upi
payment_account_id:
type: integer
example: 3
reference:
type: string
example: UTR-123
notes:
type: string
example: 'Corrected receipt'
invoice_id:
type: integer
example: 16
422:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'This payment is reconciled. Its amount, date, method and account cannot be changed.'
errors:
payment:
- 'This payment is reconciled. Its amount, date, method and account cannot be changed.'
properties:
message:
type: string
example: 'This payment is reconciled. Its amount, date, method and account cannot be changed.'
errors:
type: object
properties:
payment:
type: array
example:
- 'This payment is reconciled. Its amount, date, method and account cannot be changed.'
items:
type: string
tags:
- Payments
requestBody:
required: false
content:
application/json:
schema:
type: object
properties:
is_advance:
type: boolean
description: 'prohibited The original designation is fixed.'
example: false
contact_id:
type: integer
description: 'prohibited The original party is fixed.'
example: null
invoice_id:
type: integer
description: 'prohibited The original allocation is fixed.'
example: null
business_document_id:
type: integer
description: 'prohibited The original allocation is fixed.'
example: null
amount:
type: number
description: 'Payment amount in rupees, greater than zero.'
example: 1000.0
paid_on:
type: string
description: 'Payment date.'
example: '2026-09-30'
method:
type: string
description: 'cash, bank, upi, card, cheque or other.'
example: upi
payment_account_id:
type: integer
description: 'Compatible account belonging to this business. A different account must be active.'
example: 3
nullable: true
reference:
type: string
description: 'Optional transaction reference, up to 64 characters.'
example: UTR-123
nullable: true
notes:
type: string
description: 'Optional note, up to 1000 characters.'
example: 'Corrected receipt'
nullable: true
direction:
type: string
description: 'prohibited The original direction is fixed.'
example: null
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
-
in: path
name: payment_id
description: 'The ID of the payment.'
example: 1
required: true
schema:
type: integer
'/api/v1/businesses/{business}/payments/{payment_id}/email':
post:
summary: 'Email a payment receipt or advice.'
operationId: emailAPaymentReceiptOrAdvice
description: "Queues a separate branded Dukanam email for each selected contact through the platform's\nCloudflare SMTP transport, with the shop's branding and receipt PDF attached.\nRequires sales permission for received payments or purchases for made payments.\nIncludes advances. Foreign tenant payments return 404, invalid or foreign recipient keys\nand disabled platform email return 422. No payment or ledger values are changed."
parameters: []
responses:
202:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'Emailed to Ravi.'
data:
sent_to:
-
key: primary
name: Ravi
email: ravi@example.com
properties:
message:
type: string
example: 'Emailed to Ravi.'
data:
type: object
properties:
sent_to:
type: array
example:
- key: primary
name: Ravi
email: ravi@example.com
items:
type: object
properties:
key:
type: string
example: primary
name:
type: string
example: Ravi
email:
type: string
example: ravi@example.com
tags:
- Payments
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
recipients:
type: array
description: 'A recipient key: `primary` for the party''s primary contact, or `person-{id}` for an additional contact. Must match the regex /^(primary|person-\d+)$/.'
example:
- primary
- person-12
items:
type: string
recipient_emails:
type: array
description: 'Must be a valid email address. Must not be greater than 254 characters.'
example:
primary: accounts@example.com
items:
type: string
nullable: true
subject:
type: string
description: 'Email subject line. Must not be greater than 150 characters.'
example: 'Sri Lakshmi Traders | Purchase order: PO-0007'
message:
type: string
description: 'Message body. Blank lines separate paragraphs. The PDF is attached and a button links to the signed public page. Must not be greater than 2000 characters.'
example: 'Hi Ravi, please find purchase order PO-0007 for ₹12,500.00. Please confirm the order and the delivery date.'
from:
type: string
description: 'Ledger statements only: start date, inclusive. Must be a valid date.'
example: '2026-04-01'
nullable: true
to:
type: string
description: 'Ledger statements only: end date, inclusive. Must be on or after `from`. Must be a valid date. Must be a date after or equal to from.'
example: '2027-03-31'
nullable: true
required:
- recipients
- subject
- message
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
-
in: path
name: payment_id
description: 'The ID of the payment.'
example: 1
required: true
schema:
type: integer
'/api/v1/businesses/{business}/payments/{payment_id}/apply':
post:
summary: 'Apply an advance to a bill.'
operationId: applyAnAdvanceToABill
description: "Puts part or all of an advance towards one unpaid bill of the same party: a sales invoice\nfor an advance received, a purchase invoice for an advance made. The bill's `paid_paise`\nrises and the advance's `unapplied_paise` falls; the account, cash register, khata and\nbooks do not move again, because they already did when the advance was recorded. Leave\n`amount` out to apply as much as both the advance and the bill balance allow."
parameters: []
responses:
200:
description: ''
content:
application/json:
schema:
type: object
example:
data:
id: 9
direction: received
is_advance: true
amount_paise: 500000
applied_paise: 150000
unapplied_paise: 350000
applications:
-
id: 1
payment_id: 9
amount_paise: 150000
applied_on: '2026-09-25'
invoice_id: 16
invoice_number: INV-016
business_document_id: null
properties:
data:
type: object
properties:
id:
type: integer
example: 9
direction:
type: string
example: received
is_advance:
type: boolean
example: true
amount_paise:
type: integer
example: 500000
applied_paise:
type: integer
example: 150000
unapplied_paise:
type: integer
example: 350000
applications:
type: array
example:
- id: 1
payment_id: 9
amount_paise: 150000
applied_on: '2026-09-25'
invoice_id: 16
invoice_number: INV-016
business_document_id: null
description: 'Every bill this advance has been put towards, with amount_paise, applied_on, and the invoice or document it settled.'
items:
type: object
properties:
id:
type: integer
example: 1
payment_id:
type: integer
example: 9
amount_paise:
type: integer
example: 150000
applied_on:
type: string
example: '2026-09-25'
invoice_id:
type: integer
example: 16
invoice_number:
type: string
example: INV-016
business_document_id:
type: string
example: null
nullable: true
422:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'Enter an amount up to ₹3,500.00, the smaller of the unused advance and the bill balance.'
errors:
amount:
- 'Enter an amount up to ₹3,500.00, the smaller of the unused advance and the bill balance.'
properties:
message:
type: string
example: 'Enter an amount up to ₹3,500.00, the smaller of the unused advance and the bill balance.'
errors:
type: object
properties:
amount:
type: array
example:
- 'Enter an amount up to ₹3,500.00, the smaller of the unused advance and the bill balance.'
items:
type: string
tags:
- Payments
requestBody:
required: false
content:
application/json:
schema:
type: object
properties:
invoice_id:
type: integer
description: "The customer's sales invoice. Required for an advance received."
example: 16
nullable: true
business_document_id:
type: integer
description: "The supplier's purchase invoice. Required for an advance made."
example: 16
nullable: true
amount:
type: number
description: 'Rupees to apply, up to the smaller of the unused advance and the bill balance.'
example: 1500.0
nullable: true
applied_on:
type: string
description: 'Date the advance was set against the bill. Defaults to today.'
example: '2026-09-25'
nullable: true
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
-
in: path
name: payment_id
description: 'The ID of the payment.'
example: 1
required: true
schema:
type: integer
-
in: path
name: payment
description: 'The advance payment.'
example: 9
required: true
schema:
type: integer
'/api/v1/businesses/{business}/expenses':
get:
summary: 'List expenses.'
operationId: listExpenses
description: "Search and filter business expenses. The response includes totals for the current\nresult, the current month, and the retained voided audit trail."
parameters:
-
in: query
name: q
description: 'Case-insensitive search across category, payee, or description.'
example: internet
required: false
schema:
type: string
description: 'Case-insensitive search across category, payee, or description.'
example: internet
nullable: true
-
in: query
name: category
description: 'Filter by an exact category.'
example: Utilities
required: false
schema:
type: string
description: 'Filter by an exact category.'
example: Utilities
nullable: true
-
in: query
name: payment_account_id
description: 'Filter by the payment account used.'
example: 9
required: false
schema:
type: integer
description: 'Filter by the payment account used.'
example: 9
nullable: true
-
in: query
name: from
description: 'Include expenses on or after this date (`YYYY-MM-DD`).'
example: '2026-08-01'
required: false
schema:
type: string
description: 'Include expenses on or after this date (`YYYY-MM-DD`).'
example: '2026-08-01'
nullable: true
-
in: query
name: to
description: 'Include expenses on or before this date (`YYYY-MM-DD`).'
example: '2026-08-31'
required: false
schema:
type: string
description: 'Include expenses on or before this date (`YYYY-MM-DD`).'
example: '2026-08-31'
nullable: true
-
in: query
name: status
description: 'Filter by record status.'
example: active
required: false
schema:
type: string
description: 'Filter by record status.'
example: active
enum:
- active
- voided
nullable: true
-
in: query
name: per_page
description: 'Results per page, from 1 to 100.'
example: 20
required: false
schema:
type: integer
description: 'Results per page, from 1 to 100.'
example: 20
nullable: true
responses:
200:
description: ''
content:
application/json:
schema:
type: object
example:
data:
-
id: 41
category: Utilities
payee: 'City Internet Services'
amount_paise: 249900
occurred_on: '2026-08-12'
payment_method: bank
payment_account_id: 9
payment_account_name: 'HDFC Current Account'
note: 'August internet bill for the main shop'
voided_at: null
void_reason: null
created_at: '2026-08-12T10:30:00.000000Z'
links:
first: 'https://example.com/api/v1/businesses/1/expenses?page=1'
last: 'https://example.com/api/v1/businesses/1/expenses?page=1'
prev: null
next: null
meta:
current_page: 1
from: 1
last_page: 1
path: 'https://example.com/api/v1/businesses/1/expenses'
per_page: 20
to: 1
total: 1
summary:
filtered_total_paise: 249900
month_total_paise: 384900
month_count: 3
voided_count: 1
properties:
data:
type: array
example:
- id: 41
category: Utilities
payee: 'City Internet Services'
amount_paise: 249900
occurred_on: '2026-08-12'
payment_method: bank
payment_account_id: 9
payment_account_name: 'HDFC Current Account'
note: 'August internet bill for the main shop'
voided_at: null
void_reason: null
created_at: '2026-08-12T10:30:00.000000Z'
items:
type: object
properties:
id:
type: integer
example: 41
category:
type: string
example: Utilities
payee:
type: string
example: 'City Internet Services'
amount_paise:
type: integer
example: 249900
occurred_on:
type: string
example: '2026-08-12'
payment_method:
type: string
example: bank
payment_account_id:
type: integer
example: 9
payment_account_name:
type: string
example: 'HDFC Current Account'
note:
type: string
example: 'August internet bill for the main shop'
voided_at:
type: string
example: null
nullable: true
void_reason:
type: string
example: null
nullable: true
created_at:
type: string
example: '2026-08-12T10:30:00.000000Z'
links:
type: object
properties:
first:
type: string
example: 'https://example.com/api/v1/businesses/1/expenses?page=1'
last:
type: string
example: 'https://example.com/api/v1/businesses/1/expenses?page=1'
prev:
type: string
example: null
nullable: true
next:
type: string
example: null
nullable: true
meta:
type: object
properties:
current_page:
type: integer
example: 1
from:
type: integer
example: 1
last_page:
type: integer
example: 1
path:
type: string
example: 'https://example.com/api/v1/businesses/1/expenses'
per_page:
type: integer
example: 20
to:
type: integer
example: 1
total:
type: integer
example: 1
summary:
type: object
properties:
filtered_total_paise:
type: integer
example: 249900
description: 'Total of non-voided expenses matching the current filters.'
month_total_paise:
type: integer
example: 384900
description: 'Total of all non-voided expenses in the current calendar month.'
month_count:
type: integer
example: 3
description: 'Count of all non-voided expenses in the current calendar month.'
voided_count:
type: integer
example: 1
description: 'Count of all retained voided expenses.'
tags:
- Expenses
post:
summary: 'Save an expense.'
operationId: saveAnExpense
description: 'The expense, general-ledger journal, cash-drawer movement, and audit record either all commit or all roll back.'
parameters: []
responses: {}
tags:
- Expenses
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
category:
type: string
description: 'Must not be greater than 64 characters.'
example: b
payee:
type: string
description: 'Must not be greater than 128 characters.'
example: 'n'
nullable: true
amount:
type: number
description: 'Must not be greater than 999999999.'
example: 7
occurred_on:
type: string
description: 'Must be a valid date.'
example: '2026-01-15'
payment_method:
type: string
description: ''
example: cash
enum:
- cash
- bank
- upi
- card
- other
payment_account_id:
type: integer
description: ''
example: 16
nullable: true
note:
type: string
description: 'Must not be greater than 1000 characters.'
example: 'n'
idempotency_key:
type: string
description: 'Stable UUID used to make creation retries safe.'
example: 6d61f406-f07d-482d-a284-3e06edfd7f55
nullable: true
required:
- category
- amount
- occurred_on
- payment_method
- note
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
'/api/v1/businesses/{business}/expenses/{expense_id}':
get:
summary: ''
operationId: getApiV1BusinessesBusinessExpensesExpense_id
description: ''
parameters: []
responses:
401:
description: ''
content:
application/json:
schema:
type: object
example:
message: Unauthenticated.
properties:
message:
type: string
example: Unauthenticated.
tags:
- Expenses
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
-
in: path
name: expense_id
description: 'The ID of the expense.'
example: 1
required: true
schema:
type: integer
'/api/v1/businesses/{business}/expenses/{expense_id}/void':
post:
summary: 'Void an expense atomically.'
operationId: voidAnExpenseAtomically
description: 'The void marker, journal reversal, cash-drawer reversal, and audit record either all commit or all roll back.'
parameters: []
responses: {}
tags:
- Expenses
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
reason:
type: string
description: 'Must be at least 3 characters. Must not be greater than 255 characters.'
example: b
required:
- reason
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
-
in: path
name: expense_id
description: 'The ID of the expense.'
example: 1
required: true
schema:
type: integer
'/api/v1/businesses/{business}/pos/items':
get:
summary: "A barcode is matched whole, and read as GS1 reads it, so a wedge scanner emitting the\n12-digit UPC-A of a pack stored by its 13-digit EAN-13 still finds it. A name is matched\non any part of it. For a camera scan use `items/by-barcode/{code}`, which answers with one\nitem or a 404 rather than a list."
operationId: aBarcodeIsMatchedWholeAndReadAsGS1ReadsItSoAWedgeScannerEmittingThe12DigitUPCAOfAPackStoredByIts13DigitEAN13StillFindsItANameIsMatchedOnAnyPartOfItForACameraScanUseitemsbyBarcodecodeWhichAnswersWithOneItemOrA404RatherThanAList
description: ''
parameters:
-
in: query
name: q
description: 'Barcode, SKU, or item name to search for.'
example: atta
required: false
schema:
type: string
description: 'Barcode, SKU, or item name to search for.'
example: atta
-
in: query
name: contact
description: 'Customer id. Adds `party_price` to every item, so the counter shows what this party pays before the sale is rung up.'
example: 42
required: false
schema:
type: integer
description: 'Customer id. Adds `party_price` to every item, so the counter shows what this party pays before the sale is rung up.'
example: 42
responses:
401:
description: ''
content:
application/json:
schema:
type: object
example:
message: Unauthenticated.
properties:
message:
type: string
example: Unauthenticated.
tags:
- POS
requestBody:
required: false
content:
application/json:
schema:
type: object
properties:
q:
type: string
description: 'Must not be greater than 128 characters.'
example: b
nullable: true
contact:
type: integer
description: 'Must be at least 1.'
example: 22
nullable: true
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
'/api/v1/businesses/{business}/pos/upi':
get:
summary: ''
operationId: getApiV1BusinessesBusinessPosUpi
description: ''
parameters: []
responses:
401:
description: ''
content:
application/json:
schema:
type: object
example:
message: Unauthenticated.
properties:
message:
type: string
example: Unauthenticated.
tags:
- POS
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
amount:
type: number
description: 'Must not be greater than 10000000.'
example: 1
payment_account_id:
type: integer
description: ''
example: 16
nullable: true
required:
- amount
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
'/api/v1/businesses/{business}/pos/carts':
get:
summary: 'List held POS carts for the business.'
operationId: listHeldPOSCartsForTheBusiness
description: ''
parameters: []
responses:
401:
description: ''
content:
application/json:
schema:
type: object
example:
message: Unauthenticated.
properties:
message:
type: string
example: Unauthenticated.
tags:
- POS
post:
summary: 'Hold a POS cart for later checkout.'
operationId: holdAPOSCartForLaterCheckout
description: ''
parameters: []
responses: {}
tags:
- POS
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
name:
type: string
description: 'Must not be greater than 96 characters.'
example: b
contact_id:
type: integer
description: ''
example: 16
nullable: true
lines:
type: array
description: 'Must have at least 1 items. Must not have more than 100 items.'
example:
- []
items:
type: object
properties:
item_id:
type: integer
description: ''
example: 16
item_batch_id:
type: integer
description: ''
example: 16
nullable: true
serial_numbers:
type: array
description: 'Must not be greater than 64 characters.'
example:
- 'n'
items:
type: string
quantity:
type: number
description: ''
example: 4326.41688
required:
- item_id
- quantity
required:
- name
- lines
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
'/api/v1/businesses/{business}/pos/checkout':
post:
summary: ''
operationId: postApiV1BusinessesBusinessPosCheckout
description: ''
parameters: []
responses: {}
tags:
- POS
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
contact_id:
type: integer
description: ''
example: 16
idempotency_key:
type: string
description: 'Must be a valid UUID.'
example: a4855dc5-0acb-33c3-b921-f4291f719ca0
warehouse_id:
type: integer
description: "The warehouse this sale's stock leaves; the default warehouse when left out. Must be at least 1."
example: 66
nullable: true
cart_id:
type: integer
description: ''
example: 16
nullable: true
lines:
type: array
description: 'Must have at least 1 items. Must not have more than 100 items.'
example:
- []
items:
type: object
properties:
item_id:
type: integer
description: ''
example: 16
item_batch_id:
type: integer
description: 'A batch-tracked item says which lot it is sold from; anything else must not.'
example: 16
nullable: true
serial_numbers:
type: array
description: 'Must not be greater than 64 characters.'
example:
- 'n'
items:
type: string
quantity:
type: number
description: ''
example: 4326.41688
discount:
type: number
description: 'Must be at least 0.'
example: 77
nullable: true
required:
- item_id
- quantity
payments:
type: array
description: 'Must not have more than 4 items.'
example: null
items:
type: object
nullable: true
properties:
method:
type: string
description: ''
example: cash
enum:
- cash
- upi
- card
- bank
- cheque
payment_account_id:
type: integer
description: ''
example: 16
nullable: true
amount:
type: number
description: ''
example: 4326.41688
required:
- method
- amount
required:
- contact_id
- idempotency_key
- lines
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
'/api/v1/businesses/{business}/cash-register':
get:
summary: ''
operationId: getApiV1BusinessesBusinessCashRegister
description: ''
parameters: []
responses:
401:
description: ''
content:
application/json:
schema:
type: object
example:
message: Unauthenticated.
properties:
message:
type: string
example: Unauthenticated.
tags:
- 'Cash register'
requestBody:
required: false
content:
application/json:
schema:
type: object
properties:
cash_register_id:
type: integer
description: ''
example: 16
nullable: true
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
'/api/v1/businesses/{business}/cash-register/open':
post:
summary: ''
operationId: postApiV1BusinessesBusinessCashRegisterOpen
description: ''
parameters: []
responses: {}
tags:
- 'Cash register'
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
cash_register_id:
type: integer
description: ''
example: 16
nullable: true
opening_float:
type: number
description: 'Must be at least 0. Must not be greater than 999999999.'
example: 22
required:
- opening_float
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
'/api/v1/businesses/{business}/cash-register/movements':
post:
summary: ''
operationId: postApiV1BusinessesBusinessCashRegisterMovements
description: ''
parameters: []
responses: {}
tags:
- 'Cash register'
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
type:
type: string
description: ''
example: cash_in
enum:
- cash_in
- cash_out
cash_register_id:
type: integer
description: ''
example: 16
nullable: true
amount:
type: number
description: 'Must not be greater than 999999999.'
example: 22
notes:
type: string
description: 'Must not be greater than 255 characters.'
example: g
required:
- type
- amount
- notes
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
'/api/v1/businesses/{business}/cash-register/close':
post:
summary: ''
operationId: postApiV1BusinessesBusinessCashRegisterClose
description: ''
parameters: []
responses: {}
tags:
- 'Cash register'
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
counted_cash:
type: number
description: 'Must be at least 0. Must not be greater than 999999999.'
example: 1
cash_register_id:
type: integer
description: ''
example: 16
nullable: true
closing_notes:
type: string
description: 'Must not be greater than 1000 characters.'
example: 'n'
nullable: true
required:
- counted_cash
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
'/api/v1/businesses/{business}/cash-register/printer':
patch:
summary: "Save a counter's receipt printer settings."
operationId: saveACountersReceiptPrinterSettings
description: 'Every device billing at the counter reads these from `printer` on GET `cash-register` and from `cash_register.printer` on payment accounts, and GET `invoices/{invoice}/receipt` applies them. Send only the settings that change. The printer itself is paired on each device and is not stored.'
parameters: []
responses:
200:
description: ''
content:
application/json:
schema:
type: object
example:
data:
cash_register_id: 1
cash_register_name: 'Main counter'
paper: 58mm
auto_print: true
cut: false
open_drawer: true
properties:
data:
type: object
properties:
cash_register_id:
type: integer
example: 1
cash_register_name:
type: string
example: 'Main counter'
paper:
type: string
example: 58mm
auto_print:
type: boolean
example: true
cut:
type: boolean
example: false
open_drawer:
type: boolean
example: true
422:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'Choose 58mm or 80mm paper.'
errors:
paper:
- 'Choose 58mm or 80mm paper.'
properties:
message:
type: string
example: 'Choose 58mm or 80mm paper.'
errors:
type: object
properties:
paper:
type: array
example:
- 'Choose 58mm or 80mm paper.'
items:
type: string
tags:
- 'Cash register'
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
cash_register_id:
type: integer
description: 'Counter whose printer settings change. Must match an existing stored value.'
example: 1
paper:
type: string
description: 'Roll width: `58mm` (2-inch, 32 characters a line) or `80mm` (3-inch, 48 characters a line).'
example: 80mm
enum:
- 58mm
- 80mm
auto_print:
type: boolean
description: 'Print the receipt as soon as a sale is saved, without asking.'
example: true
cut:
type: boolean
description: 'Cut the paper after each receipt. Turn off for printers without a cutter.'
example: true
open_drawer:
type: boolean
description: 'Open the cash drawer connected to the printer after a sale with a cash payment.'
example: true
required:
- cash_register_id
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
'/api/v1/businesses/{business}/cash-register/printer/test':
get:
summary: 'Get a printer test slip.'
operationId: getAPrinterTestSlip
description: 'ESC/POS bytes for a short slip showing the counter, paper width, cutter and drawer settings, with a numbered line that fills exactly one row when the paper width is right. Use it to check a newly paired printer before the first sale. The drawer opens only when `open_drawer=1` is sent. The response has the same shape as GET `invoices/{invoice}/receipt`, with `invoice_id` and `invoice_number` null.'
parameters:
-
in: query
name: cash_register_id
description: 'Counter the receipt prints at. Its saved printer settings (paper, cut, cash drawer) apply. Omit to use the defaults: 80mm, cut, drawer opens for cash sales. Must match an existing stored value.'
example: 1
required: false
schema:
type: integer
description: 'Counter the receipt prints at. Its saved printer settings (paper, cut, cash drawer) apply. Omit to use the defaults: 80mm, cut, drawer opens for cash sales. Must match an existing stored value.'
example: 1
nullable: true
-
in: query
name: paper
description: "Overrides the counter's paper width: `58mm` (32 characters a line) or `80mm` (48 characters a line)."
example: 58mm
required: false
schema:
type: string
description: "Overrides the counter's paper width: `58mm` (32 characters a line) or `80mm` (48 characters a line)."
example: 58mm
enum:
- 58mm
- 80mm
nullable: true
-
in: query
name: cut
description: 'Overrides whether the paper is cut at the end: `1` or `0`.'
example: false
required: false
schema:
type: boolean
description: 'Overrides whether the paper is cut at the end: `1` or `0`.'
example: false
nullable: true
-
in: query
name: open_drawer
description: 'Overrides whether the cash drawer opens: `1` always opens it, `0` never does. When omitted, the drawer opens only if the counter allows it and the sale has a cash payment.'
example: false
required: false
schema:
type: boolean
description: 'Overrides whether the cash drawer opens: `1` always opens it, `0` never does. When omitted, the drawer opens only if the counter allows it and the sale has a cash payment.'
example: false
nullable: true
responses:
200:
description: ''
content:
application/json:
schema:
type: object
example:
data:
format: escpos
invoice_id: null
invoice_number: null
cash_register_id: 1
paper: 80mm
characters_per_line: 48
cut: true
opens_drawer: false
auto_print: false
content_base64: G0AbYQEbRQEd...
byte_length: 402
text: "API Shop\nPrinter test"
properties:
data:
type: object
properties:
format:
type: string
example: escpos
invoice_id:
type: string
example: null
nullable: true
invoice_number:
type: string
example: null
nullable: true
cash_register_id:
type: integer
example: 1
paper:
type: string
example: 80mm
characters_per_line:
type: integer
example: 48
cut:
type: boolean
example: true
opens_drawer:
type: boolean
example: false
auto_print:
type: boolean
example: false
content_base64:
type: string
example: G0AbYQEbRQEd...
byte_length:
type: integer
example: 402
text:
type: string
example: "API Shop\nPrinter test"
tags:
- 'Cash register'
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
'/api/v1/businesses/{business}/reports':
get:
summary: 'View a business report.'
operationId: viewABusinessReport
description: "Reports update automatically when bills, returns, expenses and other transactions are saved.\nNo extra confirmation step is required. The P&L description is \"Income and expenses for the selected period\".\nReport descriptions use plain billing language. General ledger labels its entry-number column Reference.\n\nUnregistered businesses have no Taxes category in catalog; tax-summary returns 403.\n\n`warehouse` narrows stock summary, stock valuation, and low stock to one warehouse (low stock then compares that warehouse's quantity with each item's reorder level). The `stock-by-warehouse` report lists every item's stock in every warehouse.\nInventory values use recorded carrying costs independently of editable catalogue prices;\nincomplete legacy movement history retains catalogue estimates. Warehouse allocations\nuse cumulative rounding so their values sum to the whole inventory value.\nCash flow includes actual expense settlements and worker payouts with dated payment\nreversals; unpaid invoice expenses and earned-but-unpaid commissions are not outflows.\n\nFinancial reports include original journal entries and their dated reversals, so invoice edits and cancellations retain history while producing the correct net balance. Each entry is included according to its own accounting date.\nInvoice edits restate the invoice period: reversals use the previous invoice date and replacements use the submitted invoice date. Saving an edit does not move the invoice to today's date. Owners may explicitly change issue_date; cancellation reversals retain their cancellation date."
parameters: []
responses:
401:
description: ''
content:
application/json:
schema:
type: object
example:
message: Unauthenticated.
properties:
message:
type: string
example: Unauthenticated.
tags:
- Reports
requestBody:
required: false
content:
application/json:
schema:
type: object
properties:
from:
type: string
description: 'Must be a valid date.'
example: '2026-01-15'
nullable: true
to:
type: string
description: 'Must be a valid date. Must be a date after or equal to from.'
example: '2026-01-15'
nullable: true
warehouse:
type: integer
description: 'Must be at least 1.'
example: 22
nullable: true
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
'/api/v1/businesses/{business}/reports/export':
get:
summary: 'Download a report as PDF or Excel.'
operationId: downloadAReportAsPDFOrExcel
description: "Supports every report in the report catalog, including profit-loss, balance-sheet,\ntrial-balance, general-ledger, cash-flow and tax-summary. Uses the same rows and totals\nas the web and JSON report, excluding the separate summary cards. Money in Excel is\nnumeric rupees, with signed amounts and two decimal places; dates are native Excel dates.\nProfit is green, loss is red, and zero is neutral in both formats.\nBoth formats include the current shop/legal name, address, contact details and GSTIN\nwhere registered, plus the saved invoice logo (when enabled), accent and header style.\nHeaders include the report dates and generation timestamp in the business timezone.\nMissing or unreadable logos are omitted without preventing download.\n\nRequires reports and exports permissions, reports.basic and data_export features.\nAdvanced reports also require reports.advanced. Unregistered businesses cannot export\ntax-summary. Tenant and warehouse isolation are enforced.\n\nDates are inclusive. Balance-sheet and trial-balance use to as their as-at date.\nCurrent-stock, payment-account and party-balance reports remain current snapshots,\nmatching the JSON report, and are labelled accordingly. Aging reports use the requested\ndocument period with aging as at today. Downloads are synchronous and online-only."
parameters:
-
in: query
name: report
description: 'A report slug from the catalog.'
example: profit-loss
required: true
schema:
type: string
description: 'A report slug from the catalog.'
example: profit-loss
-
in: query
name: format
description: 'pdf or xlsx.'
example: pdf
required: true
schema:
type: string
description: 'pdf or xlsx.'
example: pdf
enum:
- pdf
- xlsx
-
in: query
name: from
description: 'Start date, YYYY-MM-DD.'
example: '2026-04-01'
required: true
schema:
type: string
description: 'Start date, YYYY-MM-DD.'
example: '2026-04-01'
-
in: query
name: to
description: 'End date, YYYY-MM-DD, on or after from.'
example: '2026-10-03'
required: true
schema:
type: string
description: 'End date, YYYY-MM-DD, on or after from.'
example: '2026-10-03'
-
in: query
name: warehouse
description: 'Optional warehouse of this business; narrows stock-summary, stock-valuation and low-stock.'
example: 1
required: false
schema:
type: integer
description: 'Optional warehouse of this business; narrows stock-summary, stock-valuation and low-stock.'
example: 1
nullable: true
responses:
200:
description: 'The report file, with attachment filename dukanam-{report}-{from}-{to}.{pdf|xlsx}.'
content:
application/pdf:
schema:
type: string
format: binary
application/vnd.openxmlformats-officedocument.spreadsheetml.sheet:
schema:
type: string
format: binary
401:
description: ''
content:
application/json:
schema:
type: object
example:
message: Unauthenticated.
properties:
message:
type: string
example: Unauthenticated.
403:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'Your role does not allow this action.'
properties:
message:
type: string
example: 'Your role does not allow this action.'
404:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'Not Found'
properties:
message:
type: string
example: 'Not Found'
422:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'The selected format is invalid.'
errors:
format:
- 'The selected format is invalid.'
properties:
message:
type: string
example: 'The selected format is invalid.'
errors:
type: object
properties:
format:
type: array
example:
- 'The selected format is invalid.'
items:
type: string
tags:
- Reports
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
'/api/v1/businesses/{business}/reports/expiring':
get:
summary: 'Stock that is about to go out of date.'
operationId: stockThatIsAboutToGoOutOfDate
description: "Batches still holding stock whose expiry falls within `within_days`, soonest first.\nAlready-expired lots are included and flagged — the shop has to see and clear them — and\ncan never be billed. Requires a plan with advanced reports, like every other stock report."
parameters:
-
in: query
name: within_days
description: 'How far ahead to look, 1-365. Defaults to 30.'
example: 60
required: false
schema:
type: integer
description: 'How far ahead to look, 1-365. Defaults to 30.'
example: 60
responses:
401:
description: ''
content:
application/json:
schema:
type: object
example:
message: Unauthenticated.
properties:
message:
type: string
example: Unauthenticated.
tags:
- Reports
requestBody:
required: false
content:
application/json:
schema:
type: object
properties:
within_days:
type: integer
description: 'Must be at least 1. Must not be greater than 365.'
example: 1
nullable: true
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
'/api/v1/businesses/{business_id}/compliance':
get:
summary: 'Get the compliance profile, registrations, evidence metadata and assessed obligations.'
operationId: getTheComplianceProfileRegistrationsEvidenceMetadataAndAssessedObligations
description: ''
parameters: []
responses:
200:
description: ''
content:
application/json:
schema:
type: object
example:
data:
business_id: 16
state_code: '29'
profile:
constitution: proprietorship
masked_pan: 'ABC****34F'
district: 'Bengaluru Urban'
local_body: BBMP
premises_type: rented
supply_type: both
gst_registration_status: not_registered
previous_year_turnover_paise: 180000000
other_pan_turnover_paise: 25000000
employee_count: 4
contract_worker_count: 0
female_employee_count: 2
interstate_sales: false
ecommerce_sales: true
imports: false
exports: false
manufactures: false
food_business: true
uses_weighing_scale: true
packs_or_imports_goods: false
pharmacy: false
serves_alcohol: false
fire_risk: false
pollution_activity: false
reminder_days:
- 90
- 60
- 30
- 7
- 0
assessment_completed_at: '2026-08-20T12:00:00+05:30'
next_review_at: '2026-11-20T12:00:00+05:30'
categories:
-
id: 13
slug: food-service
name: 'Restaurant, café and food service'
nic_section: I
gst_registrations: []
obligations:
-
id: 81
rule_code: fssai-registration-licence
rule_version: 1
title: 'FSSAI registration or food licence'
applicability_reason: 'Every food business operator must hold the matching registration or licence. Matched using Guidance Shop facts.'
applicability_level: required
obligation_type: licence
authority_name: 'Food Safety and Standards Authority of India'
source_url: 'https://fssai.gov.in/business/registration'
application_url: 'https://foscos.fssai.gov.in/'
status: action_required
reference_number: null
issued_on: null
due_on: null
expires_on: null
notes: null
manual_override: false
last_assessed_at: '2026-08-20T12:00:00+05:30'
documents: []
guidance_notice: 'Guidance is generated from configurable rules and recorded business facts. Review items marked professional review and verify official sources before relying on them.'
options:
constitutions:
proprietorship: Proprietorship
partnership: 'Partnership firm'
llp: 'Limited Liability Partnership'
private_limited: 'Private limited company'
public_limited: 'Public limited company'
opc: 'One Person Company'
huf: 'Hindu Undivided Family'
trust_society: 'Trust / society'
cooperative: 'Co-operative society'
government: 'Government entity'
other: Other
supply_types:
goods: 'Goods only'
services: 'Services only'
both: 'Goods and services'
gst_statuses:
not_assessed: 'Not assessed'
not_registered: 'Not registered'
pending: 'Application pending'
active: 'Registered / active'
suspended: Suspended
cancelled: 'Cancelled / surrendered'
gst_registration_types:
normal: 'Normal taxpayer'
composition: 'Composition taxpayer'
casual: 'Casual taxable person'
non_resident: 'Non-resident taxable person'
isd: 'Input Service Distributor (ISD)'
tds: 'Tax Deductor (TDS)'
tcs: 'Tax Collector / e-commerce operator (TCS)'
sez_unit: 'SEZ unit'
sez_developer: 'SEZ developer'
oidar: 'OIDAR / online money gaming supplier'
uin: 'Unique Identity Number holder'
temporary: 'Temporary registration'
meta:
available_categories:
-
id: 13
slug: food-service
name: 'Restaurant, café and food service'
nic_section: I
description: 'Restaurants, cafés, caterers, cloud kitchens and mobile food service.'
properties:
data:
type: object
properties:
business_id:
type: integer
example: 16
state_code:
type: string
example: '29'
profile:
type: object
properties:
constitution:
type: string
example: proprietorship
masked_pan:
type: string
example: 'ABC****34F'
district:
type: string
example: 'Bengaluru Urban'
local_body:
type: string
example: BBMP
premises_type:
type: string
example: rented
supply_type:
type: string
example: both
gst_registration_status:
type: string
example: not_registered
previous_year_turnover_paise:
type: integer
example: 180000000
other_pan_turnover_paise:
type: integer
example: 25000000
employee_count:
type: integer
example: 4
contract_worker_count:
type: integer
example: 0
female_employee_count:
type: integer
example: 2
interstate_sales:
type: boolean
example: false
ecommerce_sales:
type: boolean
example: true
imports:
type: boolean
example: false
exports:
type: boolean
example: false
manufactures:
type: boolean
example: false
food_business:
type: boolean
example: true
uses_weighing_scale:
type: boolean
example: true
packs_or_imports_goods:
type: boolean
example: false
pharmacy:
type: boolean
example: false
serves_alcohol:
type: boolean
example: false
fire_risk:
type: boolean
example: false
pollution_activity:
type: boolean
example: false
reminder_days:
type: array
example:
- 90
- 60
- 30
- 7
- 0
items:
type: integer
assessment_completed_at:
type: string
example: '2026-08-20T12:00:00+05:30'
next_review_at:
type: string
example: '2026-11-20T12:00:00+05:30'
categories:
type: array
example:
- id: 13
slug: food-service
name: 'Restaurant, café and food service'
nic_section: I
items:
type: object
properties:
id:
type: integer
example: 13
slug:
type: string
example: food-service
name:
type: string
example: 'Restaurant, café and food service'
nic_section:
type: string
example: I
gst_registrations:
type: array
example: []
obligations:
type: array
example:
-
id: 81
rule_code: fssai-registration-licence
rule_version: 1
title: 'FSSAI registration or food licence'
applicability_reason: 'Every food business operator must hold the matching registration or licence. Matched using Guidance Shop facts.'
applicability_level: required
obligation_type: licence
authority_name: 'Food Safety and Standards Authority of India'
source_url: 'https://fssai.gov.in/business/registration'
application_url: 'https://foscos.fssai.gov.in/'
status: action_required
reference_number: null
issued_on: null
due_on: null
expires_on: null
notes: null
manual_override: false
last_assessed_at: '2026-08-20T12:00:00+05:30'
documents: []
items:
type: object
properties:
id:
type: integer
example: 81
rule_code:
type: string
example: fssai-registration-licence
rule_version:
type: integer
example: 1
title:
type: string
example: 'FSSAI registration or food licence'
applicability_reason:
type: string
example: 'Every food business operator must hold the matching registration or licence. Matched using Guidance Shop facts.'
applicability_level:
type: string
example: required
obligation_type:
type: string
example: licence
authority_name:
type: string
example: 'Food Safety and Standards Authority of India'
source_url:
type: string
example: 'https://fssai.gov.in/business/registration'
application_url:
type: string
example: 'https://foscos.fssai.gov.in/'
status:
type: string
example: action_required
reference_number:
type: string
example: null
nullable: true
issued_on:
type: string
example: null
nullable: true
due_on:
type: string
example: null
nullable: true
expires_on:
type: string
example: null
nullable: true
notes:
type: string
example: null
nullable: true
manual_override:
type: boolean
example: false
last_assessed_at:
type: string
example: '2026-08-20T12:00:00+05:30'
documents:
type: array
example: []
guidance_notice:
type: string
example: 'Guidance is generated from configurable rules and recorded business facts. Review items marked professional review and verify official sources before relying on them.'
options:
type: object
properties:
constitutions:
type: object
properties:
proprietorship:
type: string
example: Proprietorship
partnership:
type: string
example: 'Partnership firm'
llp:
type: string
example: 'Limited Liability Partnership'
private_limited:
type: string
example: 'Private limited company'
public_limited:
type: string
example: 'Public limited company'
opc:
type: string
example: 'One Person Company'
huf:
type: string
example: 'Hindu Undivided Family'
trust_society:
type: string
example: 'Trust / society'
cooperative:
type: string
example: 'Co-operative society'
government:
type: string
example: 'Government entity'
other:
type: string
example: Other
supply_types:
type: object
properties:
goods:
type: string
example: 'Goods only'
services:
type: string
example: 'Services only'
both:
type: string
example: 'Goods and services'
gst_statuses:
type: object
properties:
not_assessed:
type: string
example: 'Not assessed'
not_registered:
type: string
example: 'Not registered'
pending:
type: string
example: 'Application pending'
active:
type: string
example: 'Registered / active'
suspended:
type: string
example: Suspended
cancelled:
type: string
example: 'Cancelled / surrendered'
gst_registration_types:
type: object
properties:
normal:
type: string
example: 'Normal taxpayer'
composition:
type: string
example: 'Composition taxpayer'
casual:
type: string
example: 'Casual taxable person'
non_resident:
type: string
example: 'Non-resident taxable person'
isd:
type: string
example: 'Input Service Distributor (ISD)'
tds:
type: string
example: 'Tax Deductor (TDS)'
tcs:
type: string
example: 'Tax Collector / e-commerce operator (TCS)'
sez_unit:
type: string
example: 'SEZ unit'
sez_developer:
type: string
example: 'SEZ developer'
oidar:
type: string
example: 'OIDAR / online money gaming supplier'
uin:
type: string
example: 'Unique Identity Number holder'
temporary:
type: string
example: 'Temporary registration'
meta:
type: object
properties:
available_categories:
type: array
example:
- id: 13
slug: food-service
name: 'Restaurant, café and food service'
nic_section: I
description: 'Restaurants, cafés, caterers, cloud kitchens and mobile food service.'
items:
type: object
properties:
id:
type: integer
example: 13
slug:
type: string
example: food-service
name:
type: string
example: 'Restaurant, café and food service'
nic_section:
type: string
example: I
description:
type: string
example: 'Restaurants, cafés, caterers, cloud kitchens and mobile food service.'
tags:
- 'Business compliance guidance'
parameters:
-
in: path
name: business_id
description: 'The ID of the business.'
example: 1
required: true
schema:
type: integer
'/api/v1/businesses/{business_id}/compliance/profile':
put:
summary: 'Update business facts and immediately run the deterministic compliance assessment.'
operationId: updateBusinessFactsAndImmediatelyRunTheDeterministicComplianceAssessment
description: ''
parameters: []
responses:
200:
description: ''
content:
application/json:
schema:
type: object
example:
data:
business_id: 16
state_code: '29'
profile:
constitution: proprietorship
masked_pan: 'ABC****34F'
district: 'Bengaluru Urban'
local_body: BBMP
premises_type: rented
supply_type: both
gst_registration_status: not_registered
previous_year_turnover_paise: 180000000
other_pan_turnover_paise: 25000000
employee_count: 4
contract_worker_count: 0
female_employee_count: 2
interstate_sales: false
ecommerce_sales: true
imports: false
exports: false
manufactures: false
food_business: true
uses_weighing_scale: true
packs_or_imports_goods: false
pharmacy: false
serves_alcohol: false
fire_risk: false
pollution_activity: false
reminder_days:
- 90
- 60
- 30
- 7
- 0
assessment_completed_at: '2026-08-20T12:00:00+05:30'
next_review_at: '2026-11-20T12:00:00+05:30'
categories:
-
id: 13
slug: food-service
name: 'Restaurant, café and food service'
nic_section: I
gst_registrations: []
obligations:
-
id: 81
rule_code: fssai-registration-licence
rule_version: 1
title: 'FSSAI registration or food licence'
applicability_reason: 'Every food business operator must hold the matching registration or licence. Matched using Guidance Shop facts.'
applicability_level: required
obligation_type: licence
authority_name: 'Food Safety and Standards Authority of India'
source_url: 'https://fssai.gov.in/business/registration'
application_url: 'https://foscos.fssai.gov.in/'
status: action_required
reference_number: null
issued_on: null
due_on: null
expires_on: null
notes: null
manual_override: false
last_assessed_at: '2026-08-20T12:00:00+05:30'
documents: []
guidance_notice: 'Guidance is generated from configurable rules and recorded business facts. Review items marked professional review and verify official sources before relying on them.'
options:
constitutions:
proprietorship: Proprietorship
partnership: 'Partnership firm'
llp: 'Limited Liability Partnership'
private_limited: 'Private limited company'
public_limited: 'Public limited company'
opc: 'One Person Company'
huf: 'Hindu Undivided Family'
trust_society: 'Trust / society'
cooperative: 'Co-operative society'
government: 'Government entity'
other: Other
supply_types:
goods: 'Goods only'
services: 'Services only'
both: 'Goods and services'
gst_statuses:
not_assessed: 'Not assessed'
not_registered: 'Not registered'
pending: 'Application pending'
active: 'Registered / active'
suspended: Suspended
cancelled: 'Cancelled / surrendered'
gst_registration_types:
normal: 'Normal taxpayer'
composition: 'Composition taxpayer'
casual: 'Casual taxable person'
non_resident: 'Non-resident taxable person'
isd: 'Input Service Distributor (ISD)'
tds: 'Tax Deductor (TDS)'
tcs: 'Tax Collector / e-commerce operator (TCS)'
sez_unit: 'SEZ unit'
sez_developer: 'SEZ developer'
oidar: 'OIDAR / online money gaming supplier'
uin: 'Unique Identity Number holder'
temporary: 'Temporary registration'
meta:
available_categories:
-
id: 13
slug: food-service
name: 'Restaurant, café and food service'
nic_section: I
description: 'Restaurants, cafés, caterers, cloud kitchens and mobile food service.'
properties:
data:
type: object
properties:
business_id:
type: integer
example: 16
state_code:
type: string
example: '29'
profile:
type: object
properties:
constitution:
type: string
example: proprietorship
masked_pan:
type: string
example: 'ABC****34F'
district:
type: string
example: 'Bengaluru Urban'
local_body:
type: string
example: BBMP
premises_type:
type: string
example: rented
supply_type:
type: string
example: both
gst_registration_status:
type: string
example: not_registered
previous_year_turnover_paise:
type: integer
example: 180000000
other_pan_turnover_paise:
type: integer
example: 25000000
employee_count:
type: integer
example: 4
contract_worker_count:
type: integer
example: 0
female_employee_count:
type: integer
example: 2
interstate_sales:
type: boolean
example: false
ecommerce_sales:
type: boolean
example: true
imports:
type: boolean
example: false
exports:
type: boolean
example: false
manufactures:
type: boolean
example: false
food_business:
type: boolean
example: true
uses_weighing_scale:
type: boolean
example: true
packs_or_imports_goods:
type: boolean
example: false
pharmacy:
type: boolean
example: false
serves_alcohol:
type: boolean
example: false
fire_risk:
type: boolean
example: false
pollution_activity:
type: boolean
example: false
reminder_days:
type: array
example:
- 90
- 60
- 30
- 7
- 0
items:
type: integer
assessment_completed_at:
type: string
example: '2026-08-20T12:00:00+05:30'
next_review_at:
type: string
example: '2026-11-20T12:00:00+05:30'
categories:
type: array
example:
- id: 13
slug: food-service
name: 'Restaurant, café and food service'
nic_section: I
items:
type: object
properties:
id:
type: integer
example: 13
slug:
type: string
example: food-service
name:
type: string
example: 'Restaurant, café and food service'
nic_section:
type: string
example: I
gst_registrations:
type: array
example: []
obligations:
type: array
example:
-
id: 81
rule_code: fssai-registration-licence
rule_version: 1
title: 'FSSAI registration or food licence'
applicability_reason: 'Every food business operator must hold the matching registration or licence. Matched using Guidance Shop facts.'
applicability_level: required
obligation_type: licence
authority_name: 'Food Safety and Standards Authority of India'
source_url: 'https://fssai.gov.in/business/registration'
application_url: 'https://foscos.fssai.gov.in/'
status: action_required
reference_number: null
issued_on: null
due_on: null
expires_on: null
notes: null
manual_override: false
last_assessed_at: '2026-08-20T12:00:00+05:30'
documents: []
items:
type: object
properties:
id:
type: integer
example: 81
rule_code:
type: string
example: fssai-registration-licence
rule_version:
type: integer
example: 1
title:
type: string
example: 'FSSAI registration or food licence'
applicability_reason:
type: string
example: 'Every food business operator must hold the matching registration or licence. Matched using Guidance Shop facts.'
applicability_level:
type: string
example: required
obligation_type:
type: string
example: licence
authority_name:
type: string
example: 'Food Safety and Standards Authority of India'
source_url:
type: string
example: 'https://fssai.gov.in/business/registration'
application_url:
type: string
example: 'https://foscos.fssai.gov.in/'
status:
type: string
example: action_required
reference_number:
type: string
example: null
nullable: true
issued_on:
type: string
example: null
nullable: true
due_on:
type: string
example: null
nullable: true
expires_on:
type: string
example: null
nullable: true
notes:
type: string
example: null
nullable: true
manual_override:
type: boolean
example: false
last_assessed_at:
type: string
example: '2026-08-20T12:00:00+05:30'
documents:
type: array
example: []
guidance_notice:
type: string
example: 'Guidance is generated from configurable rules and recorded business facts. Review items marked professional review and verify official sources before relying on them.'
options:
type: object
properties:
constitutions:
type: object
properties:
proprietorship:
type: string
example: Proprietorship
partnership:
type: string
example: 'Partnership firm'
llp:
type: string
example: 'Limited Liability Partnership'
private_limited:
type: string
example: 'Private limited company'
public_limited:
type: string
example: 'Public limited company'
opc:
type: string
example: 'One Person Company'
huf:
type: string
example: 'Hindu Undivided Family'
trust_society:
type: string
example: 'Trust / society'
cooperative:
type: string
example: 'Co-operative society'
government:
type: string
example: 'Government entity'
other:
type: string
example: Other
supply_types:
type: object
properties:
goods:
type: string
example: 'Goods only'
services:
type: string
example: 'Services only'
both:
type: string
example: 'Goods and services'
gst_statuses:
type: object
properties:
not_assessed:
type: string
example: 'Not assessed'
not_registered:
type: string
example: 'Not registered'
pending:
type: string
example: 'Application pending'
active:
type: string
example: 'Registered / active'
suspended:
type: string
example: Suspended
cancelled:
type: string
example: 'Cancelled / surrendered'
gst_registration_types:
type: object
properties:
normal:
type: string
example: 'Normal taxpayer'
composition:
type: string
example: 'Composition taxpayer'
casual:
type: string
example: 'Casual taxable person'
non_resident:
type: string
example: 'Non-resident taxable person'
isd:
type: string
example: 'Input Service Distributor (ISD)'
tds:
type: string
example: 'Tax Deductor (TDS)'
tcs:
type: string
example: 'Tax Collector / e-commerce operator (TCS)'
sez_unit:
type: string
example: 'SEZ unit'
sez_developer:
type: string
example: 'SEZ developer'
oidar:
type: string
example: 'OIDAR / online money gaming supplier'
uin:
type: string
example: 'Unique Identity Number holder'
temporary:
type: string
example: 'Temporary registration'
meta:
type: object
properties:
available_categories:
type: array
example:
- id: 13
slug: food-service
name: 'Restaurant, café and food service'
nic_section: I
description: 'Restaurants, cafés, caterers, cloud kitchens and mobile food service.'
items:
type: object
properties:
id:
type: integer
example: 13
slug:
type: string
example: food-service
name:
type: string
example: 'Restaurant, café and food service'
nic_section:
type: string
example: I
description:
type: string
example: 'Restaurants, cafés, caterers, cloud kitchens and mobile food service.'
tags:
- 'Business compliance guidance'
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
state_code:
type: string
description: 'Two-character GST state/UT code.'
example: '29'
category_ids:
type: array
description: 'Must match an existing stored value.'
example:
- 13
items:
type: integer
constitution:
type: string
description: 'Legal constitution of the business.'
example: proprietorship
enum:
- proprietorship
- partnership
- llp
- private_limited
- public_limited
- opc
- huf
- trust_society
- cooperative
- government
- other
pan:
type: string
description: 'PAN. Required for a business marked not registered under GST. Omit to keep the saved PAN. Must match the regex /^[A-Z]{5}[0-9]{4}[A-Z]$/. Must be 10 characters.'
example: ABCDE1234F
nullable: true
district:
type: string
description: 'District containing the principal place of business. Must not be greater than 96 characters.'
example: 'Bengaluru Urban'
nullable: true
local_body:
type: string
description: 'Municipality, corporation or panchayat. Must not be greater than 128 characters.'
example: BBMP
nullable: true
premises_type:
type: string
description: 'How the principal premises is occupied.'
example: rented
enum:
- owned
- rented
- leased
- home
- mobile
- virtual
- other
supply_type:
type: string
description: 'Whether the business supplies goods, services or both.'
example: both
enum:
- goods
- services
- both
gst_registration_status:
type: string
description: 'Declared GST registration status.'
example: not_registered
enum:
- not_assessed
- not_registered
- pending
- active
- suspended
- cancelled
previous_year_turnover:
type: number
description: 'Previous financial-year PAN-wide aggregate turnover in rupees. Must be at least 0. Must not be greater than 999999999999.99.'
example: 1800000
other_pan_turnover:
type: number
description: 'Current-year PAN-wide turnover not recorded in this workspace, in rupees. Must be at least 0. Must not be greater than 999999999999.99.'
example: 250000
employee_count:
type: integer
description: 'Direct employee count. Must be at least 0. Must not be greater than 1000000.'
example: 4
contract_worker_count:
type: integer
description: 'Contract worker count. Must be at least 0. Must not be greater than 1000000.'
example: 0
female_employee_count:
type: integer
description: 'Women employees, not exceeding employee_count. Must be at least 0. Must not be greater than 1000000.'
example: 2
interstate_sales:
type: boolean
description: ''
example: false
ecommerce_sales:
type: boolean
description: ''
example: true
imports:
type: boolean
description: ''
example: false
exports:
type: boolean
description: ''
example: false
manufactures:
type: boolean
description: ''
example: false
food_business:
type: boolean
description: ''
example: true
uses_weighing_scale:
type: boolean
description: ''
example: true
packs_or_imports_goods:
type: boolean
description: ''
example: false
pharmacy:
type: boolean
description: ''
example: false
serves_alcohol:
type: boolean
description: ''
example: false
fire_risk:
type: boolean
description: ''
example: false
pollution_activity:
type: boolean
description: ''
example: false
reminder_days:
type: array
description: 'Must be at least 0. Must not be greater than 365.'
example:
- 90
- 60
- 30
- 7
- 0
items:
type: integer
required:
- state_code
- category_ids
- constitution
- premises_type
- supply_type
- gst_registration_status
- previous_year_turnover
- other_pan_turnover
- employee_count
- contract_worker_count
- female_employee_count
- interstate_sales
- ecommerce_sales
- imports
- exports
- manufactures
- food_business
- uses_weighing_scale
- packs_or_imports_goods
- pharmacy
- serves_alcohol
- fire_risk
- pollution_activity
parameters:
-
in: path
name: business_id
description: 'The ID of the business.'
example: 1
required: true
schema:
type: integer
'/api/v1/businesses/{business_id}/compliance/assess':
post:
summary: 'Re-run assessment against the latest published rule revisions.'
operationId: reRunAssessmentAgainstTheLatestPublishedRuleRevisions
description: ''
parameters: []
responses:
200:
description: ''
content:
application/json:
schema:
type: object
example:
data:
matched: 8
closed: 1
properties:
data:
type: object
properties:
matched:
type: integer
example: 8
closed:
type: integer
example: 1
tags:
- 'Business compliance guidance'
parameters:
-
in: path
name: business_id
description: 'The ID of the business.'
example: 1
required: true
schema:
type: integer
'/api/v1/businesses/{business_id}/compliance/gst-registrations':
post:
summary: ''
operationId: postApiV1BusinessesBusiness_idComplianceGstRegistrations
description: ''
parameters: []
responses:
201:
description: ''
content:
application/json:
schema:
type: object
example:
data:
id: 32
business_id: 16
registration_type: composition
registration_status: active
gstin: 29ABCDE1234F1Z5
uin: null
state_code: '29'
is_primary: true
valid_from: '2026-04-01T00:00:00.000000Z'
valid_until: null
created_at: '2026-08-20T06:30:00.000000Z'
updated_at: '2026-08-20T06:30:00.000000Z'
properties:
data:
type: object
properties:
id:
type: integer
example: 32
business_id:
type: integer
example: 16
registration_type:
type: string
example: composition
registration_status:
type: string
example: active
gstin:
type: string
example: 29ABCDE1234F1Z5
uin:
type: string
example: null
nullable: true
state_code:
type: string
example: '29'
is_primary:
type: boolean
example: true
valid_from:
type: string
example: '2026-04-01T00:00:00.000000Z'
valid_until:
type: string
example: null
nullable: true
created_at:
type: string
example: '2026-08-20T06:30:00.000000Z'
updated_at:
type: string
example: '2026-08-20T06:30:00.000000Z'
tags:
- 'Business compliance guidance'
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
registration_type:
type: string
description: 'GST taxpayer/registration profile.'
example: composition
enum:
- normal
- composition
- casual
- non_resident
- isd
- tds
- tcs
- sez_unit
- sez_developer
- oidar
- uin
- temporary
registration_status:
type: string
description: 'Current portal status.'
example: active
enum:
- pending
- active
- suspended
- cancelled
gstin:
type: string
description: '15-character GSTIN. Required except for a UIN record. This field is required unless registration_type is in uin. Must match the regex /^[0-9]{2}[A-Z]{5}[0-9]{4}[A-Z][1-9A-Z]Z[0-9A-Z]$/. Must be 15 characters.'
example: 29ABCDE1234F1Z5
nullable: true
uin:
type: string
description: 'Unique Identity Number for UIN records. This field is required when registration_type is uin. Must not be greater than 32 characters.'
example: null
nullable: true
state_code:
type: string
description: 'Two-character state/UT code; must match the GSTIN prefix.'
example: '29'
nullable: true
is_primary:
type: boolean
description: 'Whether this is the primary invoicing registration.'
example: true
valid_from:
type: string
description: 'Must be a valid date.'
example: '2026-04-01'
nullable: true
valid_until:
type: string
description: 'Must be a valid date. Must be a date after or equal to valid_from.'
example: null
nullable: true
required:
- registration_type
- registration_status
- is_primary
parameters:
-
in: path
name: business_id
description: 'The ID of the business.'
example: 1
required: true
schema:
type: integer
'/api/v1/businesses/{business_id}/compliance/gst-registrations/{gstRegistration_id}':
delete:
summary: ''
operationId: deleteApiV1BusinessesBusiness_idComplianceGstRegistrationsGstRegistration_id
description: ''
parameters: []
responses:
204:
description: ''
content:
application/json:
schema:
type: object
example: {}
properties: {}
tags:
- 'Business compliance guidance'
parameters:
-
in: path
name: business_id
description: 'The ID of the business.'
example: 1
required: true
schema:
type: integer
-
in: path
name: gstRegistration_id
description: 'The ID of the gstRegistration.'
example: 1
required: true
schema:
type: integer
'/api/v1/businesses/{business_id}/compliance/obligations/{complianceObligation_id}':
get:
summary: ''
operationId: getApiV1BusinessesBusiness_idComplianceObligationsComplianceObligation_id
description: ''
parameters: []
responses:
200:
description: ''
content:
application/json:
schema:
type: object
example:
data:
id: 81
rule_code: fssai-registration-licence
rule_version: 1
title: 'FSSAI registration or food licence'
applicability_reason: 'Every food business operator must hold the matching registration or licence. Matched using Guidance Shop facts.'
applicability_level: required
obligation_type: licence
authority_name: 'Food Safety and Standards Authority of India'
source_url: 'https://fssai.gov.in/business/registration'
application_url: 'https://foscos.fssai.gov.in/'
status: obtained
reference_number: FSSAI-10010022000123
issued_on: '2026-04-01'
due_on: null
expires_on: '2027-03-31'
notes: 'Certificate verified.'
manual_override: false
last_assessed_at: '2026-08-20T12:00:00+05:30'
documents:
-
id: 44
original_name: fssai-certificate.pdf
mime_type: application/pdf
size: 102400
document_number: FSSAI-10010022000123
issued_on: '2026-04-01'
expires_on: '2027-03-31'
is_current: true
download_url: 'https://billing.example.com/api/v1/businesses/16/compliance/documents/44/download'
properties:
data:
type: object
properties:
id:
type: integer
example: 81
rule_code:
type: string
example: fssai-registration-licence
rule_version:
type: integer
example: 1
title:
type: string
example: 'FSSAI registration or food licence'
applicability_reason:
type: string
example: 'Every food business operator must hold the matching registration or licence. Matched using Guidance Shop facts.'
applicability_level:
type: string
example: required
obligation_type:
type: string
example: licence
authority_name:
type: string
example: 'Food Safety and Standards Authority of India'
source_url:
type: string
example: 'https://fssai.gov.in/business/registration'
application_url:
type: string
example: 'https://foscos.fssai.gov.in/'
status:
type: string
example: obtained
reference_number:
type: string
example: FSSAI-10010022000123
issued_on:
type: string
example: '2026-04-01'
due_on:
type: string
example: null
nullable: true
expires_on:
type: string
example: '2027-03-31'
notes:
type: string
example: 'Certificate verified.'
manual_override:
type: boolean
example: false
last_assessed_at:
type: string
example: '2026-08-20T12:00:00+05:30'
documents:
type: array
example:
- id: 44
original_name: fssai-certificate.pdf
mime_type: application/pdf
size: 102400
document_number: FSSAI-10010022000123
issued_on: '2026-04-01'
expires_on: '2027-03-31'
is_current: true
download_url: 'https://billing.example.com/api/v1/businesses/16/compliance/documents/44/download'
items:
type: object
properties:
id:
type: integer
example: 44
original_name:
type: string
example: fssai-certificate.pdf
mime_type:
type: string
example: application/pdf
size:
type: integer
example: 102400
document_number:
type: string
example: FSSAI-10010022000123
issued_on:
type: string
example: '2026-04-01'
expires_on:
type: string
example: '2027-03-31'
is_current:
type: boolean
example: true
download_url:
type: string
example: 'https://billing.example.com/api/v1/businesses/16/compliance/documents/44/download'
tags:
- 'Business compliance guidance'
patch:
summary: ''
operationId: patchApiV1BusinessesBusiness_idComplianceObligationsComplianceObligation_id
description: ''
parameters: []
responses:
200:
description: ''
content:
application/json:
schema:
type: object
example:
data:
id: 81
rule_code: fssai-registration-licence
rule_version: 1
title: 'FSSAI registration or food licence'
applicability_reason: 'Every food business operator must hold the matching registration or licence. Matched using Guidance Shop facts.'
applicability_level: required
obligation_type: licence
authority_name: 'Food Safety and Standards Authority of India'
source_url: 'https://fssai.gov.in/business/registration'
application_url: 'https://foscos.fssai.gov.in/'
status: obtained
reference_number: FSSAI-10010022000123
issued_on: '2026-04-01'
due_on: null
expires_on: '2027-03-31'
notes: 'Certificate verified.'
manual_override: false
last_assessed_at: '2026-08-20T12:00:00+05:30'
documents:
-
id: 44
original_name: fssai-certificate.pdf
mime_type: application/pdf
size: 102400
document_number: FSSAI-10010022000123
issued_on: '2026-04-01'
expires_on: '2027-03-31'
is_current: true
download_url: 'https://billing.example.com/api/v1/businesses/16/compliance/documents/44/download'
properties:
data:
type: object
properties:
id:
type: integer
example: 81
rule_code:
type: string
example: fssai-registration-licence
rule_version:
type: integer
example: 1
title:
type: string
example: 'FSSAI registration or food licence'
applicability_reason:
type: string
example: 'Every food business operator must hold the matching registration or licence. Matched using Guidance Shop facts.'
applicability_level:
type: string
example: required
obligation_type:
type: string
example: licence
authority_name:
type: string
example: 'Food Safety and Standards Authority of India'
source_url:
type: string
example: 'https://fssai.gov.in/business/registration'
application_url:
type: string
example: 'https://foscos.fssai.gov.in/'
status:
type: string
example: obtained
reference_number:
type: string
example: FSSAI-10010022000123
issued_on:
type: string
example: '2026-04-01'
due_on:
type: string
example: null
nullable: true
expires_on:
type: string
example: '2027-03-31'
notes:
type: string
example: 'Certificate verified.'
manual_override:
type: boolean
example: false
last_assessed_at:
type: string
example: '2026-08-20T12:00:00+05:30'
documents:
type: array
example:
- id: 44
original_name: fssai-certificate.pdf
mime_type: application/pdf
size: 102400
document_number: FSSAI-10010022000123
issued_on: '2026-04-01'
expires_on: '2027-03-31'
is_current: true
download_url: 'https://billing.example.com/api/v1/businesses/16/compliance/documents/44/download'
items:
type: object
properties:
id:
type: integer
example: 44
original_name:
type: string
example: fssai-certificate.pdf
mime_type:
type: string
example: application/pdf
size:
type: integer
example: 102400
document_number:
type: string
example: FSSAI-10010022000123
issued_on:
type: string
example: '2026-04-01'
expires_on:
type: string
example: '2027-03-31'
is_current:
type: boolean
example: true
download_url:
type: string
example: 'https://billing.example.com/api/v1/businesses/16/compliance/documents/44/download'
tags:
- 'Business compliance guidance'
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
status:
type: string
description: 'Owner-tracked status. not_applicable requires notes.'
example: obtained
enum:
- action_required
- in_progress
- obtained
- not_applicable
- expired
- no_longer_applicable
reference_number:
type: string
description: 'Application, registration or licence number. Must not be greater than 128 characters.'
example: LIC-2026-1001
nullable: true
issued_on:
type: string
description: 'Must be a valid date.'
example: '2026-04-01'
nullable: true
due_on:
type: string
description: 'Must be a valid date.'
example: null
nullable: true
expires_on:
type: string
description: 'Must be a valid date. Must be a date after or equal to issued_on.'
example: '2027-03-31'
nullable: true
notes:
type: string
description: 'Must not be greater than 5000 characters.'
example: 'Renewal filed by the accountant.'
nullable: true
required:
- status
parameters:
-
in: path
name: business_id
description: 'The ID of the business.'
example: 1
required: true
schema:
type: integer
-
in: path
name: complianceObligation_id
description: 'The ID of the complianceObligation.'
example: 1
required: true
schema:
type: integer
'/api/v1/businesses/{business_id}/compliance/obligations/{complianceObligation_id}/documents':
post:
summary: ''
operationId: postApiV1BusinessesBusiness_idComplianceObligationsComplianceObligation_idDocuments
description: ''
parameters: []
responses:
200:
description: ''
content:
application/json:
schema:
type: object
example:
data:
id: 81
rule_code: fssai-registration-licence
rule_version: 1
title: 'FSSAI registration or food licence'
applicability_reason: 'Every food business operator must hold the matching registration or licence. Matched using Guidance Shop facts.'
applicability_level: required
obligation_type: licence
authority_name: 'Food Safety and Standards Authority of India'
source_url: 'https://fssai.gov.in/business/registration'
application_url: 'https://foscos.fssai.gov.in/'
status: obtained
reference_number: FSSAI-10010022000123
issued_on: '2026-04-01'
due_on: null
expires_on: '2027-03-31'
notes: 'Certificate verified.'
manual_override: false
last_assessed_at: '2026-08-20T12:00:00+05:30'
documents:
-
id: 44
original_name: fssai-certificate.pdf
mime_type: application/pdf
size: 102400
document_number: FSSAI-10010022000123
issued_on: '2026-04-01'
expires_on: '2027-03-31'
is_current: true
download_url: 'https://billing.example.com/api/v1/businesses/16/compliance/documents/44/download'
properties:
data:
type: object
properties:
id:
type: integer
example: 81
rule_code:
type: string
example: fssai-registration-licence
rule_version:
type: integer
example: 1
title:
type: string
example: 'FSSAI registration or food licence'
applicability_reason:
type: string
example: 'Every food business operator must hold the matching registration or licence. Matched using Guidance Shop facts.'
applicability_level:
type: string
example: required
obligation_type:
type: string
example: licence
authority_name:
type: string
example: 'Food Safety and Standards Authority of India'
source_url:
type: string
example: 'https://fssai.gov.in/business/registration'
application_url:
type: string
example: 'https://foscos.fssai.gov.in/'
status:
type: string
example: obtained
reference_number:
type: string
example: FSSAI-10010022000123
issued_on:
type: string
example: '2026-04-01'
due_on:
type: string
example: null
nullable: true
expires_on:
type: string
example: '2027-03-31'
notes:
type: string
example: 'Certificate verified.'
manual_override:
type: boolean
example: false
last_assessed_at:
type: string
example: '2026-08-20T12:00:00+05:30'
documents:
type: array
example:
- id: 44
original_name: fssai-certificate.pdf
mime_type: application/pdf
size: 102400
document_number: FSSAI-10010022000123
issued_on: '2026-04-01'
expires_on: '2027-03-31'
is_current: true
download_url: 'https://billing.example.com/api/v1/businesses/16/compliance/documents/44/download'
items:
type: object
properties:
id:
type: integer
example: 44
original_name:
type: string
example: fssai-certificate.pdf
mime_type:
type: string
example: application/pdf
size:
type: integer
example: 102400
document_number:
type: string
example: FSSAI-10010022000123
issued_on:
type: string
example: '2026-04-01'
expires_on:
type: string
example: '2027-03-31'
is_current:
type: boolean
example: true
download_url:
type: string
example: 'https://billing.example.com/api/v1/businesses/16/compliance/documents/44/download'
tags:
- 'Business compliance guidance'
requestBody:
required: true
content:
multipart/form-data:
schema:
type: object
properties:
document:
type: string
format: binary
description: 'Private PDF/JPEG/PNG/WebP evidence, maximum 10 MB. Must be a file. Must not be greater than 10240 kilobytes.'
document_number:
type: string
description: 'Certificate or licence number. Must not be greater than 128 characters.'
example: FSSAI-10010022000123
nullable: true
issued_on:
type: string
description: 'Must be a valid date.'
example: '2026-04-01'
nullable: true
expires_on:
type: string
description: 'Must be a valid date. Must be a date after or equal to issued_on.'
example: '2027-03-31'
nullable: true
required:
- document
parameters:
-
in: path
name: business_id
description: 'The ID of the business.'
example: 1
required: true
schema:
type: integer
-
in: path
name: complianceObligation_id
description: 'The ID of the complianceObligation.'
example: 1
required: true
schema:
type: integer
'/api/v1/businesses/{business_id}/compliance/documents/{complianceDocument_id}/download':
get:
summary: ''
operationId: getApiV1BusinessesBusiness_idComplianceDocumentsComplianceDocument_idDownload
description: ''
parameters: []
responses:
401:
description: ''
content:
application/json:
schema:
type: object
example:
message: Unauthenticated.
properties:
message:
type: string
example: Unauthenticated.
tags:
- 'Business compliance guidance'
parameters:
-
in: path
name: business_id
description: 'The ID of the business.'
example: 1
required: true
schema:
type: integer
-
in: path
name: complianceDocument_id
description: 'The ID of the complianceDocument.'
example: 1
required: true
schema:
type: integer
'/api/v1/businesses/{business}/gst':
get:
summary: 'View GST workspace.'
operationId: viewGSTWorkspace
description: 'Unregistered businesses cannot access this endpoint, regardless of plan.'
parameters: []
responses:
401:
description: ''
content:
application/json:
schema:
type: object
example:
message: Unauthenticated.
properties:
message:
type: string
example: Unauthenticated.
403:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'GST features are unavailable for an unregistered business.'
properties:
message:
type: string
example: 'GST features are unavailable for an unregistered business.'
tags:
- 'GST compliance'
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
'/api/v1/businesses/{business}/gst/gstr1':
get:
summary: 'Export GSTR-1.'
operationId: exportGSTR1
description: 'Unregistered businesses cannot access this endpoint, regardless of plan.'
parameters: []
responses:
401:
description: ''
content:
application/json:
schema:
type: object
example:
message: Unauthenticated.
properties:
message:
type: string
example: Unauthenticated.
403:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'GST features are unavailable for an unregistered business.'
properties:
message:
type: string
example: 'GST features are unavailable for an unregistered business.'
tags:
- 'GST compliance'
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
'/api/v1/businesses/{business}/gst/gstr2b':
post:
summary: 'Import GSTR-2B.'
operationId: importGSTR2B
description: 'Unregistered businesses cannot access this endpoint, regardless of plan.'
parameters: []
responses:
403:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'GST features are unavailable for an unregistered business.'
properties:
message:
type: string
example: 'GST features are unavailable for an unregistered business.'
tags:
- 'GST compliance'
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
'/api/v1/businesses/{business}/gst/gstr3b':
get:
summary: 'Unregistered businesses cannot access this endpoint, regardless of plan.'
operationId: unregisteredBusinessesCannotAccessThisEndpointRegardlessOfPlan
description: ''
parameters:
-
in: query
name: period
description: 'The month, as YYYY-MM.'
example: 2026-08
required: true
schema:
type: string
description: 'The month, as YYYY-MM.'
example: 2026-08
-
in: query
name: revision
description: 'A specific revision. Defaults to the latest.'
example: 1
required: false
schema:
type: integer
description: 'A specific revision. Defaults to the latest.'
example: 1
responses:
401:
description: ''
content:
application/json:
schema:
type: object
example:
message: Unauthenticated.
properties:
message:
type: string
example: Unauthenticated.
403:
description: ''
content:
text/plain:
schema:
type: string
example: "{\"message\":\"GST features are unavailable for an unregistered business.\"}\n\nPrepare GSTR-3B for a month.\n\nSections 3.1, 3.2, 4, 5 and 6.1 computed from saved documents — never from current\nmasters, so re-reading an old period reproduces it exactly. A locked revision answers with\nthe figures that were locked rather than recomputing; a draft recomputes on every read.\n\n`itc_basis` says where section 4's other ITC came from: `gstr2b_reconciled` when a GSTR-2B\nhas been imported for the period, `books` when it has not. `not_derived` lists the rows of\nthe form Dukanam cannot compute, which the filer enters on the portal.\n\nThis is preparation data. Dukanam does not file returns.\n\n**Offline:** online-only. It reads a month of recorded transactions, so it belongs in neither the\noffline cache nor the write queue."
tags:
- 'GST compliance'
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
'/api/v1/businesses/{business}/gst/gstr3b/{period}/export':
get:
summary: 'Unregistered businesses cannot access this endpoint, regardless of plan.'
operationId: unregisteredBusinessesCannotAccessThisEndpointRegardlessOfPlan
description: ''
parameters:
-
in: query
name: format
description: '`json` or `xlsx`. Defaults to `json`.'
example: xlsx
required: false
schema:
type: string
description: '`json` or `xlsx`. Defaults to `json`.'
example: xlsx
-
in: query
name: revision
description: 'A specific revision. Defaults to the latest.'
example: 1
required: false
schema:
type: integer
description: 'A specific revision. Defaults to the latest.'
example: 1
responses:
200:
description: 'JSON export'
content:
application/json:
schema:
type: object
example:
period: 2026-08
status: locked
sections: {}
properties:
period:
type: string
example: 2026-08
status:
type: string
example: locked
sections:
type: object
properties: {}
403:
description: ''
content:
text/plain:
schema:
type: string
example: "{\"message\":\"GST features are unavailable for an unregistered business.\"}\n\nDownload a prepared GSTR-3B.\n\n`format=json` returns the same payload as the summary endpoint as a file; `format=xlsx`\nreturns a workbook with one sheet per section, money as a number in rupees and a frozen\nheader row, for handing to the shop's accountant.\n\n**Offline:** online-only. The file is built on the server from recorded transactions."
tags:
- 'GST compliance'
requestBody:
required: false
content:
application/json:
schema:
type: object
properties:
format:
type: string
description: ''
example: json
enum:
- json
- xlsx
nullable: true
revision:
type: integer
description: 'Must be at least 1.'
example: 16
nullable: true
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
-
in: path
name: period
description: 'The month, as YYYY-MM.'
example: 2026-08
required: true
schema:
type: string
'/api/v1/businesses/{business}/gst/gstr3b/{period}/lock':
post:
summary: 'Unregistered businesses cannot access this endpoint, regardless of plan.'
operationId: unregisteredBusinessesCannotAccessThisEndpointRegardlessOfPlan
description: ''
parameters: []
responses:
403:
description: ''
content:
text/plain:
schema:
type: string
example: "{\"message\":\"GST features are unavailable for an unregistered business.\"}\n\nLock a GSTR-3B period after filing.\n\nComputes the figures once more, stores them and hashes them. The period then answers from\nthat snapshot however the books move afterwards. Locking twice answers `422`; so does\nlocking a period that has not ended, because it cannot have been filed yet.\n\n**Offline:** online-only. Nothing here may be replayed from the write queue — a lock\nrecords a filing that has already happened, and a stale replay would freeze stale figures."
tags:
- 'GST compliance'
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
-
in: path
name: period
description: 'The month, as YYYY-MM.'
example: 2026-08
required: true
schema:
type: string
'/api/v1/businesses/{business}/gst/gstr3b/{period}/amend':
post:
summary: 'Unregistered businesses cannot access this endpoint, regardless of plan.'
operationId: unregisteredBusinessesCannotAccessThisEndpointRegardlessOfPlan
description: ''
parameters: []
responses:
403:
description: ''
content:
text/plain:
schema:
type: string
example: "{\"message\":\"GST features are unavailable for an unregistered business.\"}\n\nOpen the next revision of a locked period.\n\nThe locked revision stays readable exactly as it was filed and the new revision starts as\na draft that recomputes from the corrected books. Amending a period that is not locked\nanswers `422` — an unlocked period is simply edited by correcting the documents.\n\n**Offline:** online-only, for the same reason a lock is."
tags:
- 'GST compliance'
requestBody:
required: false
content:
application/json:
schema:
type: object
properties:
reason:
type: string
description: 'Why the period is being amended, up to 255 characters.'
example: 'Missed a purchase invoice'
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
-
in: path
name: period
description: 'The month, as YYYY-MM.'
example: 2026-08
required: true
schema:
type: string
'/api/v1/businesses/{business}/gst/gstr9':
get:
summary: 'Unregistered businesses cannot access this endpoint, regardless of plan.'
operationId: unregisteredBusinessesCannotAccessThisEndpointRegardlessOfPlan
description: ''
parameters:
-
in: query
name: financial_year
description: 'The year, as YYYY-YY, starting in April.'
example: 2026-27
required: true
schema:
type: string
description: 'The year, as YYYY-YY, starting in April.'
example: 2026-27
-
in: query
name: revision
description: 'A specific revision. Defaults to the latest.'
example: 1
required: false
schema:
type: integer
description: 'A specific revision. Defaults to the latest.'
example: 1
responses:
401:
description: ''
content:
application/json:
schema:
type: object
example:
message: Unauthenticated.
properties:
message:
type: string
example: Unauthenticated.
403:
description: ''
content:
text/plain:
schema:
type: string
example: "{\"message\":\"GST features are unavailable for an unregistered business.\"}\n\nPrepare the GSTR-9 annual working for a financial year.\n\nThis is the **working data**, not the return: the year's outward supplies by nature\n(tables 4 and 5), ITC availed and reversed (6 and 7), tax paid as it was declared (9), the\nHSN summaries (17 and 18), an outward and inward summary by rate, and the month-by-month\nreconciliation of the sales register against GSTR-3B against the journals. The filer reads\nthese onto the portal; Dukanam submits nothing.\n\nTables 4, 5, 6, 7, 17 and 18 are computed from the year's saved documents. Table 9 and the\nreconciliation are rolled up from the twelve monthly GSTR-3B returns, using a month's\n**locked** revision where it has one — that table reports what was declared, not what the\nbooks say today. `sections.9.filed_months` says how many of the twelve were locked, and\nevery reconciliation row carries `gstr3b_source` of `filed` or `computed`.\n\n`applicability` measures this workspace's turnover against the configured threshold. It is\nnever a gate: aggregate turnover is a PAN-wide figure Dukanam cannot see, and the threshold\nitself moves. A locked revision answers with the figures that were locked; a draft\nrecomputes on every read."
tags:
- 'GST compliance'
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
financial_year:
type: string
description: 'Must not be greater than 7 characters.'
example: bngzmiy
revision:
type: integer
description: 'Must be at least 1.'
example: 16
nullable: true
required:
- financial_year
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
'/api/v1/businesses/{business}/gst/gstr9/{financialYear}/export':
get:
summary: 'Unregistered businesses cannot access this endpoint, regardless of plan.'
operationId: unregisteredBusinessesCannotAccessThisEndpointRegardlessOfPlan
description: ''
parameters:
-
in: query
name: format
description: '`json` or `xlsx`. Defaults to `json`.'
example: xlsx
required: false
schema:
type: string
description: '`json` or `xlsx`. Defaults to `json`.'
example: xlsx
-
in: query
name: revision
description: 'A specific revision. Defaults to the latest.'
example: 1
required: false
schema:
type: integer
description: 'A specific revision. Defaults to the latest.'
example: 1
responses:
200:
description: 'JSON export'
content:
application/json:
schema:
type: object
example:
financial_year: 2026-27
status: locked
sections: {}
properties:
financial_year:
type: string
example: 2026-27
status:
type: string
example: locked
sections:
type: object
properties: {}
403:
description: ''
content:
text/plain:
schema:
type: string
example: "{\"message\":\"GST features are unavailable for an unregistered business.\"}\n\nDownload a prepared GSTR-9 working.\n\n`format=json` returns the same payload as the summary endpoint as a file; `format=xlsx`\nreturns the workbook to hand to the shop's accountant — one sheet per table plus the\nreconciliation, money as a number in rupees and a frozen header row."
tags:
- 'GST compliance'
requestBody:
required: false
content:
application/json:
schema:
type: object
properties:
format:
type: string
description: ''
example: json
enum:
- json
- xlsx
nullable: true
revision:
type: integer
description: 'Must be at least 1.'
example: 16
nullable: true
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
-
in: path
name: financialYear
description: 'The year, as YYYY-YY.'
example: 2026-27
required: true
schema:
type: string
'/api/v1/businesses/{business}/gst/gstr9/{financialYear}/lock':
post:
summary: 'Unregistered businesses cannot access this endpoint, regardless of plan.'
operationId: unregisteredBusinessesCannotAccessThisEndpointRegardlessOfPlan
description: ''
parameters: []
responses:
403:
description: ''
content:
text/plain:
schema:
type: string
example: "{\"message\":\"GST features are unavailable for an unregistered business.\"}\n\nLock a GSTR-9 year after filing.\n\nComputes the working once more, stores it and hashes it. The year then answers from that\nsnapshot however the books move afterwards. Locking twice answers `422`; so does locking a\nyear that has not ended, because its annual return cannot have been filed yet."
tags:
- 'GST compliance'
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
-
in: path
name: financialYear
description: 'The year, as YYYY-YY.'
example: 2026-27
required: true
schema:
type: string
'/api/v1/businesses/{business}/gst/gstr9/{financialYear}/amend':
post:
summary: 'Unregistered businesses cannot access this endpoint, regardless of plan.'
operationId: unregisteredBusinessesCannotAccessThisEndpointRegardlessOfPlan
description: ''
parameters: []
responses:
403:
description: ''
content:
text/plain:
schema:
type: string
example: "{\"message\":\"GST features are unavailable for an unregistered business.\"}\n\nOpen the next revision of a locked year.\n\nThe locked revision stays readable exactly as it was filed and the new revision starts as a\ndraft that recomputes from the corrected books. Amending a year that is not locked answers\n`422` — an unlocked year is simply corrected by correcting the documents."
tags:
- 'GST compliance'
requestBody:
required: false
content:
application/json:
schema:
type: object
properties:
reason:
type: string
description: 'Why the year is being amended, up to 255 characters.'
example: 'Credit note saved after filing'
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
-
in: path
name: financialYear
description: 'The year, as YYYY-YY.'
example: 2026-27
required: true
schema:
type: string
'/api/v1/businesses/{business}/billing':
get:
summary: 'List plans and activation availability'
operationId: listPlansAndActivationAvailability
description: "Use `razorpay_checkout_enabled` for paid checkout. `paid_plan_activation_enabled` refers only to the\nsupport-only manual activation fallback, while each plan's `activation_available` covers either path.\nEnded paid subscriptions automatically fall back to Free Essentials, preserving paid history and trial ineligibility."
parameters: []
responses:
200:
description: 'Free fallback (excerpt)'
content:
application/json:
schema:
type: object
example:
data:
business:
id: 1
free_transition:
subscription_id: 3
reason: trial_ended
requires_acknowledgement: true
invoice_allowance:
limit: 50
used: 70
remaining: 0
can_create: false
resets_at: '2026-09-30T18:30:00.000000Z'
paid_plan_activation_enabled: false
razorpay_checkout_enabled: true
plans: []
properties:
data:
type: object
properties:
business:
type: object
properties:
id:
type: integer
example: 1
free_transition:
type: object
properties:
subscription_id:
type: integer
example: 3
description: 'Current Free subscription ID to acknowledge.'
reason:
type: string
example: trial_ended
description: 'Either trial_ended or subscription_ended.'
requires_acknowledgement:
type: boolean
example: true
description: 'Show a plan choice when true; never block access to existing invoices.'
description: 'Owner-only automatic fallback notice; null when no automatic fallback applies.'
invoice_allowance:
type: object
properties:
limit:
type: integer
example: 50
description: 'Monthly limit; null means unlimited.'
used:
type: integer
example: 70
description: 'Invoices created this application-calendar month, including voided invoices.'
remaining:
type: integer
example: 0
description: 'Remaining allowance, clamped to zero; null means unlimited.'
can_create:
type: boolean
example: false
description: 'Whether another invoice fits in the allowance.'
resets_at:
type: string
example: '2026-09-30T18:30:00.000000Z'
description: 'ISO-8601 timestamp of the next monthly reset.'
description: 'Calendar-month usage, including trial invoices. Downgrading never resets it.'
paid_plan_activation_enabled:
type: boolean
example: false
description: 'Whether customers may self-activate paid plans.'
razorpay_checkout_enabled:
type: boolean
example: true
description: 'Whether Razorpay subscription checkout is configured.'
plans:
type: array
example: []
tags:
- Billing
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
'/api/v1/businesses/{business}/billing/payments':
get:
summary: 'List subscription payments'
operationId: listSubscriptionPayments
description: "Returns successful subscription charges and mandate authorisations for the business owner.\nOnly captured recurring charges expose an invoice download URL."
parameters:
-
in: query
name: per_page
description: 'Results per page.'
example: 20
required: false
schema:
type: integer
description: 'Results per page.'
example: 20
responses:
401:
description: ''
content:
application/json:
schema:
type: object
example:
message: Unauthenticated.
properties:
message:
type: string
example: Unauthenticated.
tags:
- Billing
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
'/api/v1/businesses/{business}/billing/payments/{subscriptionPayment_id}/invoice':
get:
summary: 'Download a subscription invoice'
operationId: downloadASubscriptionInvoice
description: 'Downloads the immutable PDF invoice for a captured recurring subscription charge.'
parameters: []
responses:
401:
description: ''
content:
application/json:
schema:
type: object
example:
message: Unauthenticated.
properties:
message:
type: string
example: Unauthenticated.
tags:
- Billing
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
-
in: path
name: subscriptionPayment_id
description: 'The ID of the subscriptionPayment.'
example: 1
required: true
schema:
type: integer
'/api/v1/businesses/{business}/billing/continue-free':
post:
summary: 'Continue on Free after a subscription ends'
operationId: continueOnFreeAfterASubscriptionEnds
description: "Owner only. Acknowledges the current automatic Free fallback without changing plans,\nresetting invoice usage, or granting another trial. Repeating the same request is safe.\nA stale or unrelated subscription ID returns HTTP 422; another tenant returns HTTP 404.\nExisting invoices remain accessible before and after acknowledgement."
parameters: []
responses:
200:
description: 'Acknowledged Free fallback (excerpt)'
content:
application/json:
schema:
type: object
example:
data:
id: 1
free_transition:
subscription_id: 3
reason: trial_ended
requires_acknowledgement: false
invoice_allowance:
limit: 50
used: 70
remaining: 0
can_create: false
resets_at: '2026-09-30T18:30:00.000000Z'
properties:
data:
type: object
properties:
id:
type: integer
example: 1
free_transition:
type: object
properties:
subscription_id:
type: integer
example: 3
reason:
type: string
example: trial_ended
requires_acknowledgement:
type: boolean
example: false
description: 'False after acknowledgement.'
invoice_allowance:
type: object
properties:
limit:
type: integer
example: 50
used:
type: integer
example: 70
remaining:
type: integer
example: 0
can_create:
type: boolean
example: false
resets_at:
type: string
example: '2026-09-30T18:30:00.000000Z'
422:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'Your plan has changed. Refresh billing before continuing.'
errors:
subscription_id:
- 'Your plan has changed. Refresh billing before continuing.'
properties:
message:
type: string
example: 'Your plan has changed. Refresh billing before continuing.'
errors:
type: object
properties:
subscription_id:
type: array
example:
- 'Your plan has changed. Refresh billing before continuing.'
items:
type: string
tags:
- Billing
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
subscription_id:
type: integer
description: 'The current free_transition.subscription_id from the business resource.'
example: 3
required:
- subscription_id
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
'/api/v1/businesses/{business}/billing/change':
post:
summary: 'Change the current plan'
operationId: changeTheCurrentPlan
description: "Paid plans return HTTP 422 while online paid-plan activation is disabled, except for an upgrade from an\nactive Razorpay paid plan. Smart Books to Business is updated in place immediately; Razorpay credits the\nunused current-plan value and charges the prorated difference without creating another trial.\nChanging away from a Razorpay-backed plan cancels that provider subscription immediately before the new\nplan is activated, so mobile clients must confirm an immediate loss of paid access before submitting. If\nRazorpay cannot confirm cancellation, the endpoint returns HTTP 422 and leaves the current plan unchanged.\nSend an `Idempotency-Key` when applying a prorated upgrade. Retrying with the same key returns the completed\noperation and never sends a second provider update; concurrent retries return HTTP 409 until it completes.\nA conflicting target or interval also returns HTTP 409 while another transition owns the subscription."
parameters:
-
in: header
name: Idempotency-Key
description: ''
example: upgrade-2026-08-25-01
schema:
type: string
responses:
409:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'This plan change is already processing. Retry with the same idempotency key.'
properties:
message:
type: string
example: 'This plan change is already processing. Retry with the same idempotency key.'
422:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'Online paid-plan activation is not configured. Contact support to change this plan.'
errors:
plan_id:
- 'Online paid-plan activation is not configured. Contact support to change this plan.'
properties:
message:
type: string
example: 'Online paid-plan activation is not configured. Contact support to change this plan.'
errors:
type: object
properties:
plan_id:
type: array
example:
- 'Online paid-plan activation is not configured. Contact support to change this plan.'
items:
type: string
tags:
- Billing
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
plan_id:
type: string
description: 'The active plan ID selected from the billing discovery endpoint. Must match an existing stored value.'
example: 3
billing_interval:
type: string
description: 'The selected billing frequency.'
example: monthly
enum:
- monthly
- yearly
idempotency_key:
type: string
description: 'Optional body equivalent of the Idempotency-Key header for safely retrying a prorated upgrade. Must be at least 8 characters. Must not be greater than 128 characters.'
example: upgrade-2026-08-25-01
nullable: true
required:
- plan_id
- billing_interval
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
'/api/v1/businesses/{business}/billing/checkout':
post:
summary: 'Start Razorpay subscription checkout'
operationId: startRazorpaySubscriptionCheckout
description: "Creates a price-versioned Razorpay subscription. Mobile clients must pass the returned `subscription_id`\nto Razorpay Standard Checkout and then send the signed result to the confirm endpoint. The effective plan\nprice is calculated on the server and must be at least 100 paise; clients cannot submit an arbitrary amount."
parameters: []
responses:
201:
description: ''
content:
application/json:
schema:
type: object
example:
data:
checkout_id: 1
key: rzp_test_example
subscription_id: sub_example
name: Dukanam
description: 'Smart Books · Monthly'
amount_paise: 99900
currency: INR
trial_days: 14
prefill:
name: 'Shop Owner'
email: owner@example.com
contact: '9876543210'
properties:
data:
type: object
properties:
checkout_id:
type: integer
example: 1
key:
type: string
example: rzp_test_example
subscription_id:
type: string
example: sub_example
name:
type: string
example: Dukanam
description:
type: string
example: 'Smart Books · Monthly'
amount_paise:
type: integer
example: 99900
currency:
type: string
example: INR
trial_days:
type: integer
example: 14
prefill:
type: object
properties:
name:
type: string
example: 'Shop Owner'
email:
type: string
example: owner@example.com
contact:
type: string
example: '9876543210'
401:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'Razorpay rejected the configured API credentials.'
properties:
message:
type: string
example: 'Razorpay rejected the configured API credentials.'
422:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'Razorpay checkout requires a charge of at least 100 paise.'
errors:
plan_id:
- 'Razorpay checkout requires a charge of at least 100 paise.'
properties:
message:
type: string
example: 'Razorpay checkout requires a charge of at least 100 paise.'
errors:
type: object
properties:
plan_id:
type: array
example:
- 'Razorpay checkout requires a charge of at least 100 paise.'
items:
type: string
tags:
- Billing
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
plan_id:
type: string
description: 'The active plan ID selected from the billing discovery endpoint. Must match an existing stored value.'
example: 3
billing_interval:
type: string
description: 'The selected billing frequency.'
example: monthly
enum:
- monthly
- yearly
required:
- plan_id
- billing_interval
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
'/api/v1/businesses/{business}/billing/checkouts/{billingCheckout_id}/confirm':
post:
summary: 'Confirm Razorpay subscription authorisation'
operationId: confirmRazorpaySubscriptionAuthorisation
description: "The server verifies the Razorpay HMAC signature and fetches provider state before granting access.\nTrial access starts only after successful payment-method authorisation; a plan without a trial waits\nfor Razorpay to report the subscription as active. When the checkout replaces another Razorpay plan,\nthe previous provider subscription is cancelled immediately before the replacement is activated."
parameters: []
responses: {}
tags:
- Billing
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
razorpay_payment_id:
type: string
description: 'Must match the regex /^pay_[A-Za-z0-9]+$/. Must not be greater than 191 characters.'
example: b
razorpay_subscription_id:
type: string
description: 'Must match the regex /^sub_[A-Za-z0-9]+$/. Must not be greater than 191 characters.'
example: 'n'
razorpay_signature:
type: string
description: 'Must match the regex /^[a-f0-9]{64}$/i. Must be 64 characters.'
example: gzmiyvdljnikhwaykcmyuwpwlvqwrsitcpscqldzsnrwtujwvlxjklqppwqbewtn
required:
- razorpay_payment_id
- razorpay_subscription_id
- razorpay_signature
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
-
in: path
name: billingCheckout_id
description: 'The ID of the billingCheckout.'
example: 1
required: true
schema:
type: integer
'/api/v1/businesses/{business}/billing/cancel':
post:
summary: 'Cancel the paid plan at the end of the current period.'
operationId: cancelThePaidPlanAtTheEndOfTheCurrentPeriod
description: "Owner only. Access continues until the provider's current cycle ends. Free referral\ntime (`subscription.provider` is `referral`) ends on its own date and cannot be cancelled."
parameters: []
responses:
422:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'Free referral time ends on its own date and does not need to be cancelled.'
properties:
message:
type: string
example: 'Free referral time ends on its own date and does not need to be cancelled.'
tags:
- Billing
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
'/api/v1/businesses/{business}/billing/pos-seats':
post:
summary: 'Buy or give up POS logins.'
operationId: buyOrGiveUpPOSLogins
description: "A POS login costs the seat price per month and is added to the running subscription:\nRazorpay charges the prorated difference for the rest of the cycle immediately and\nevery renewal after it bills the new total. Owner only. The workspace must be on an\nactive paid plan billed online, and seats cannot drop below the logins already in use."
parameters: []
responses:
422:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'POS logins are billed on an active paid subscription. Choose a plan first, then add counter logins.'
errors:
pos_seats:
- 'POS logins are billed on an active paid subscription. Choose a plan first, then add counter logins.'
properties:
message:
type: string
example: 'POS logins are billed on an active paid subscription. Choose a plan first, then add counter logins.'
errors:
type: object
properties:
pos_seats:
type: array
example:
- 'POS logins are billed on an active paid subscription. Choose a plan first, then add counter logins.'
items:
type: string
tags:
- Billing
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
pos_seats:
type: integer
description: 'The total number of POS logins to pay for.'
example: 2
required:
- pos_seats
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
'/api/v1/referrals/codes/{code}':
get:
summary: 'Check a referral code.'
operationId: checkAReferralCode
description: "For a registration screen opened from a shared link: tells the app whether the code\nwill be accepted and whose it is (first name only). The code may be a customer's\nreferral code or an influencer partner's link code; `referrer_type` says which.\nSend the same code as `referral_code` on registration."
parameters: []
responses:
200:
description: ''
content:
application/json:
schema:
oneOf:
-
description: ''
type: object
example:
data:
code: K7M2QX9A
valid: true
program_enabled: true
referrer_type: customer
referrer_first_name: Priya
properties:
data:
type: object
properties:
code:
type: string
example: K7M2QX9A
valid:
type: boolean
example: true
program_enabled:
type: boolean
example: true
referrer_type:
type: string
example: customer
referrer_first_name:
type: string
example: Priya
-
description: 'Partner link code'
type: object
example:
data:
code: RAVI7K
valid: true
program_enabled: true
referrer_type: partner
referrer_first_name: Ravi
properties:
data:
type: object
properties:
code:
type: string
example: RAVI7K
valid:
type: boolean
example: true
program_enabled:
type: boolean
example: true
referrer_type:
type: string
example: partner
referrer_first_name:
type: string
example: Ravi
tags:
- Referrals
security: []
parameters:
-
in: path
name: code
description: 'The referral code.'
example: K7M2QX9A
required: true
schema:
type: string
/api/v1/referrals:
get:
summary: 'Get the referral dashboard.'
operationId: getTheReferralDashboard
description: "Returns the account's referral code and link (created on first call), a ready-to-send\nWhatsApp message and `wa.me` URL, the counters for the Referral & Rewards screen, the\nrunning reward and the projected dates of every waiting one. Waiting rewards start\nonly after all paid time ends; when `paid_coverage.auto_renews` is true those dates\nmove forward each time the paid plan renews. POS counter logins cannot refer (403)."
parameters: []
responses:
200:
description: ''
content:
application/json:
schema:
type: object
example:
data:
program:
enabled: true
reward_months: 1
reward_plan:
id: 2
name: 'Smart Books'
slug: smart
reward_plan_source: program_default
referral_code: K7M2QX9A
referral_link: 'https://dukanam.com/register?ref=K7M2QX9A'
share:
code: K7M2QX9A
link: 'https://dukanam.com/register?ref=K7M2QX9A'
message: 'Hey! Check out Dukanam for billing, stock and khata. You can register using my referral link: https://dukanam.com/register?ref=K7M2QX9A'
whatsapp_url: 'https://wa.me/?text=Hey%21%20Check%20out%20Dukanam'
stats:
total_referrals: 3
pending_referrals: 1
successful_referrals: 2
rewards_earned: 2
months_earned: 2
available_rewards: 1
available_months: 1
available_extra_days: 0
active_rewards: 1
used_rewards: 0
paid_coverage:
ends_at: null
auto_renews: false
current_reward:
id: 7
status: active
months: 1
remaining_days: null
plan:
id: 2
name: 'Smart Books'
slug: smart
business:
id: 17
name: 'Aarogya Medical Store'
referred_name: 'Ravi Kumar'
earned_at: '2026-09-01T10:00:00.000000Z'
activated_at: '2026-09-01T10:00:00.000000Z'
starts_at: '2026-09-01T10:00:00.000000Z'
ends_at: '2026-10-01T10:00:00.000000Z'
consumed_at: null
upcoming_rewards:
-
id: 8
status: available
months: 1
remaining_days: null
plan:
id: 2
name: 'Smart Books'
slug: smart
business: null
referred_name: 'Meena S'
earned_at: '2026-09-15T10:00:00.000000Z'
activated_at: null
starts_at: null
ends_at: null
consumed_at: null
projected_starts_at: '2026-10-01T10:00:00.000000Z'
projected_ends_at: '2026-11-01T10:00:00.000000Z'
starts_after_paid_plan: false
properties:
data:
type: object
properties:
program:
type: object
properties:
enabled:
type: boolean
example: true
reward_months:
type: integer
example: 1
reward_plan:
type: object
properties:
id:
type: integer
example: 2
name:
type: string
example: 'Smart Books'
slug:
type: string
example: smart
description: "The plan this account's next reward grants: the plan it last paid for, otherwise the program plan."
reward_plan_source:
type: string
example: program_default
description: '`paid_plan` when the reward matches the plan this account last paid for, `program_default` when it uses the admin-configured plan.'
referral_code:
type: string
example: K7M2QX9A
description: "The account's unique referral code."
referral_link:
type: string
example: 'https://dukanam.com/register?ref=K7M2QX9A'
description: 'The registration link carrying the code as `ref`.'
share:
type: object
properties:
code:
type: string
example: K7M2QX9A
link:
type: string
example: 'https://dukanam.com/register?ref=K7M2QX9A'
message:
type: string
example: 'Hey! Check out Dukanam for billing, stock and khata. You can register using my referral link: https://dukanam.com/register?ref=K7M2QX9A'
description: 'The admin-configured share message with the link filled in.'
whatsapp_url:
type: string
example: 'https://wa.me/?text=Hey%21%20Check%20out%20Dukanam'
description: 'Opens WhatsApp (app on phones, WhatsApp Web or Desktop on computers) with the message pre-filled.'
stats:
type: object
properties:
total_referrals:
type: integer
example: 3
pending_referrals:
type: integer
example: 1
description: 'Registered or subscription pending.'
successful_referrals:
type: integer
example: 2
description: 'Bought a paid plan.'
rewards_earned:
type: integer
example: 2
months_earned:
type: integer
example: 2
available_rewards:
type: integer
example: 1
available_months:
type: integer
example: 1
description: 'Whole months waiting to start.'
available_extra_days:
type: integer
example: 0
description: 'Days carried over from rewards a paid plan interrupted.'
active_rewards:
type: integer
example: 1
used_rewards:
type: integer
example: 0
paid_coverage:
type: object
properties:
ends_at:
type: string
example: null
nullable: true
auto_renews:
type: boolean
example: false
current_reward:
type: object
properties:
id:
type: integer
example: 7
status:
type: string
example: active
months:
type: integer
example: 1
remaining_days:
type: string
example: null
nullable: true
plan:
type: object
properties:
id:
type: integer
example: 2
name:
type: string
example: 'Smart Books'
slug:
type: string
example: smart
business:
type: object
properties:
id:
type: integer
example: 17
name:
type: string
example: 'Aarogya Medical Store'
referred_name:
type: string
example: 'Ravi Kumar'
earned_at:
type: string
example: '2026-09-01T10:00:00.000000Z'
activated_at:
type: string
example: '2026-09-01T10:00:00.000000Z'
starts_at:
type: string
example: '2026-09-01T10:00:00.000000Z'
ends_at:
type: string
example: '2026-10-01T10:00:00.000000Z'
consumed_at:
type: string
example: null
nullable: true
upcoming_rewards:
type: array
example:
-
id: 8
status: available
months: 1
remaining_days: null
plan:
id: 2
name: 'Smart Books'
slug: smart
business: null
referred_name: 'Meena S'
earned_at: '2026-09-15T10:00:00.000000Z'
activated_at: null
starts_at: null
ends_at: null
consumed_at: null
projected_starts_at: '2026-10-01T10:00:00.000000Z'
projected_ends_at: '2026-11-01T10:00:00.000000Z'
starts_after_paid_plan: false
items:
type: object
properties:
id:
type: integer
example: 8
status:
type: string
example: available
months:
type: integer
example: 1
remaining_days:
type: string
example: null
nullable: true
plan:
type: object
properties:
id:
type: integer
example: 2
name:
type: string
example: 'Smart Books'
slug:
type: string
example: smart
business:
type: string
example: null
nullable: true
referred_name:
type: string
example: 'Meena S'
earned_at:
type: string
example: '2026-09-15T10:00:00.000000Z'
activated_at:
type: string
example: null
nullable: true
starts_at:
type: string
example: null
nullable: true
ends_at:
type: string
example: null
nullable: true
consumed_at:
type: string
example: null
nullable: true
projected_starts_at:
type: string
example: '2026-10-01T10:00:00.000000Z'
projected_ends_at:
type: string
example: '2026-11-01T10:00:00.000000Z'
starts_after_paid_plan:
type: boolean
example: false
403:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'This action is unauthorized.'
properties:
message:
type: string
example: 'This action is unauthorized.'
tags:
- Referrals
/api/v1/referrals/history:
get:
summary: 'List referral history.'
operationId: listReferralHistory
description: "The people who registered with the account's code, newest first, with each one's\nstatus (`registered`, `subscription_pending`, `subscription_purchased`,\n`reward_earned`, `reward_applied`) and the reward it earned."
parameters:
-
in: query
name: status
description: 'Filter by one status.'
example: reward_earned
required: false
schema:
type: string
description: 'Filter by one status.'
example: reward_earned
-
in: query
name: per_page
description: 'Results per page, from 1 to 50.'
example: 20
required: false
schema:
type: integer
description: 'Results per page, from 1 to 50.'
example: 20
responses:
200:
description: ''
content:
application/json:
schema:
type: object
example:
data:
-
id: 12
status: reward_earned
status_label: 'Reward earned'
steps:
-
status: registered
label: Registered
reached: true
-
status: subscription_pending
label: 'Subscription pending'
reached: true
-
status: subscription_purchased
label: 'Subscription purchased'
reached: true
-
status: reward_earned
label: 'Reward earned'
reached: true
-
status: reward_applied
label: 'Reward applied'
reached: false
referred:
name: 'Meena S'
source: api
registered_at: '2026-09-10T08:00:00.000000Z'
subscription_pending_at: '2026-09-12T08:00:00.000000Z'
subscription_purchased_at: '2026-09-15T10:00:00.000000Z'
reward_earned_at: '2026-09-15T10:00:00.000000Z'
reward_applied_at: null
reward_skipped_reason: null
reward:
id: 8
status: available
months: 1
remaining_days: null
plan:
id: 2
name: 'Smart Books'
slug: smart
business: null
referred_name: null
earned_at: '2026-09-15T10:00:00.000000Z'
activated_at: null
starts_at: null
ends_at: null
consumed_at: null
links: {}
meta:
current_page: 1
per_page: 20
total: 1
properties:
data:
type: array
example:
-
id: 12
status: reward_earned
status_label: 'Reward earned'
steps:
- status: registered
label: Registered
reached: true
- status: subscription_pending
label: 'Subscription pending'
reached: true
- status: subscription_purchased
label: 'Subscription purchased'
reached: true
- status: reward_earned
label: 'Reward earned'
reached: true
- status: reward_applied
label: 'Reward applied'
reached: false
referred:
name: 'Meena S'
source: api
registered_at: '2026-09-10T08:00:00.000000Z'
subscription_pending_at: '2026-09-12T08:00:00.000000Z'
subscription_purchased_at: '2026-09-15T10:00:00.000000Z'
reward_earned_at: '2026-09-15T10:00:00.000000Z'
reward_applied_at: null
reward_skipped_reason: null
reward:
id: 8
status: available
months: 1
remaining_days: null
plan:
id: 2
name: 'Smart Books'
slug: smart
business: null
referred_name: null
earned_at: '2026-09-15T10:00:00.000000Z'
activated_at: null
starts_at: null
ends_at: null
consumed_at: null
items:
type: object
properties:
id:
type: integer
example: 12
status:
type: string
example: reward_earned
status_label:
type: string
example: 'Reward earned'
steps:
type: array
example:
- status: registered
label: Registered
reached: true
- status: subscription_pending
label: 'Subscription pending'
reached: true
- status: subscription_purchased
label: 'Subscription purchased'
reached: true
- status: reward_earned
label: 'Reward earned'
reached: true
- status: reward_applied
label: 'Reward applied'
reached: false
items:
type: object
properties:
status:
type: string
example: registered
label:
type: string
example: Registered
reached:
type: boolean
example: true
referred:
type: object
properties:
name:
type: string
example: 'Meena S'
source:
type: string
example: api
registered_at:
type: string
example: '2026-09-10T08:00:00.000000Z'
subscription_pending_at:
type: string
example: '2026-09-12T08:00:00.000000Z'
subscription_purchased_at:
type: string
example: '2026-09-15T10:00:00.000000Z'
reward_earned_at:
type: string
example: '2026-09-15T10:00:00.000000Z'
reward_applied_at:
type: string
example: null
nullable: true
reward_skipped_reason:
type: string
example: null
nullable: true
reward:
type: object
properties:
id:
type: integer
example: 8
status:
type: string
example: available
months:
type: integer
example: 1
remaining_days:
type: string
example: null
nullable: true
plan:
type: object
properties:
id:
type: integer
example: 2
name:
type: string
example: 'Smart Books'
slug:
type: string
example: smart
business:
type: string
example: null
nullable: true
referred_name:
type: string
example: null
nullable: true
earned_at:
type: string
example: '2026-09-15T10:00:00.000000Z'
activated_at:
type: string
example: null
nullable: true
starts_at:
type: string
example: null
nullable: true
ends_at:
type: string
example: null
nullable: true
consumed_at:
type: string
example: null
nullable: true
links:
type: object
properties: {}
meta:
type: object
properties:
current_page:
type: integer
example: 1
per_page:
type: integer
example: 20
total:
type: integer
example: 1
tags:
- Referrals
requestBody:
required: false
content:
application/json:
schema:
type: object
properties:
status:
type: string
description: ''
example: architecto
nullable: true
per_page:
type: integer
description: 'Must be at least 1. Must not be greater than 50.'
example: 22
nullable: true
/api/v1/referrals/rewards:
get:
summary: 'List earned rewards.'
operationId: listEarnedRewards
description: "Every reward the account has earned. `available` rewards are earned and waiting,\n`active` is running now as free subscription time, and `consumed` ones are used up.\nA reward a paid plan interrupted is `available` again with `remaining_days` set."
parameters:
-
in: query
name: status
description: 'Filter by available, active or consumed.'
example: available
required: false
schema:
type: string
description: 'Filter by available, active or consumed.'
example: available
responses:
200:
description: ''
content:
application/json:
schema:
type: object
example:
data:
-
id: 8
status: available
months: 1
remaining_days: null
plan:
id: 2
name: 'Smart Books'
slug: smart
business: null
referred_name: 'Meena S'
earned_at: '2026-09-15T10:00:00.000000Z'
activated_at: null
starts_at: null
ends_at: null
consumed_at: null
properties:
data:
type: array
example:
-
id: 8
status: available
months: 1
remaining_days: null
plan:
id: 2
name: 'Smart Books'
slug: smart
business: null
referred_name: 'Meena S'
earned_at: '2026-09-15T10:00:00.000000Z'
activated_at: null
starts_at: null
ends_at: null
consumed_at: null
items:
type: object
properties:
id:
type: integer
example: 8
status:
type: string
example: available
months:
type: integer
example: 1
remaining_days:
type: string
example: null
nullable: true
plan:
type: object
properties:
id:
type: integer
example: 2
name:
type: string
example: 'Smart Books'
slug:
type: string
example: smart
business:
type: string
example: null
nullable: true
referred_name:
type: string
example: 'Meena S'
earned_at:
type: string
example: '2026-09-15T10:00:00.000000Z'
activated_at:
type: string
example: null
nullable: true
starts_at:
type: string
example: null
nullable: true
ends_at:
type: string
example: null
nullable: true
consumed_at:
type: string
example: null
nullable: true
tags:
- Referrals
requestBody:
required: false
content:
application/json:
schema:
type: object
properties:
status:
type: string
description: ''
example: architecto
nullable: true
/api/v1/referrals/rewards/apply:
post:
summary: 'Apply waiting rewards.'
operationId: applyWaitingRewards
description: "Optionally chooses which owned workspace waiting rewards run on, then starts the next\none immediately when no paid plan is running. With a paid plan running, nothing\nstarts: rewards never interrupt paid time and begin on their own once it ends.\nIdempotent. Returns the refreshed referral dashboard."
parameters: []
responses:
200:
description: ''
content:
application/json:
schema:
type: object
example:
data:
program:
enabled: true
reward_months: 1
reward_plan:
id: 2
name: 'Smart Books'
slug: smart
reward_plan_source: program_default
referral_code: K7M2QX9A
referral_link: 'https://dukanam.com/register?ref=K7M2QX9A'
share:
code: K7M2QX9A
link: 'https://dukanam.com/register?ref=K7M2QX9A'
message: 'Hey! You can register using my referral link: https://dukanam.com/register?ref=K7M2QX9A'
whatsapp_url: 'https://wa.me/?text=Hey%21'
stats:
total_referrals: 1
pending_referrals: 0
successful_referrals: 1
rewards_earned: 1
months_earned: 1
available_rewards: 0
available_months: 0
available_extra_days: 0
active_rewards: 1
used_rewards: 0
paid_coverage:
ends_at: null
auto_renews: false
current_reward:
id: 8
status: active
months: 1
remaining_days: null
plan:
id: 2
name: 'Smart Books'
slug: smart
business:
id: 17
name: 'Aarogya Medical Store'
referred_name: 'Meena S'
earned_at: '2026-09-15T10:00:00.000000Z'
activated_at: '2026-09-24T10:00:00.000000Z'
starts_at: '2026-09-24T10:00:00.000000Z'
ends_at: '2026-10-24T10:00:00.000000Z'
consumed_at: null
upcoming_rewards: []
properties:
data:
type: object
properties:
program:
type: object
properties:
enabled:
type: boolean
example: true
reward_months:
type: integer
example: 1
reward_plan:
type: object
properties:
id:
type: integer
example: 2
name:
type: string
example: 'Smart Books'
slug:
type: string
example: smart
reward_plan_source:
type: string
example: program_default
referral_code:
type: string
example: K7M2QX9A
referral_link:
type: string
example: 'https://dukanam.com/register?ref=K7M2QX9A'
share:
type: object
properties:
code:
type: string
example: K7M2QX9A
link:
type: string
example: 'https://dukanam.com/register?ref=K7M2QX9A'
message:
type: string
example: 'Hey! You can register using my referral link: https://dukanam.com/register?ref=K7M2QX9A'
whatsapp_url:
type: string
example: 'https://wa.me/?text=Hey%21'
stats:
type: object
properties:
total_referrals:
type: integer
example: 1
pending_referrals:
type: integer
example: 0
successful_referrals:
type: integer
example: 1
rewards_earned:
type: integer
example: 1
months_earned:
type: integer
example: 1
available_rewards:
type: integer
example: 0
available_months:
type: integer
example: 0
available_extra_days:
type: integer
example: 0
active_rewards:
type: integer
example: 1
used_rewards:
type: integer
example: 0
paid_coverage:
type: object
properties:
ends_at:
type: string
example: null
nullable: true
auto_renews:
type: boolean
example: false
current_reward:
type: object
properties:
id:
type: integer
example: 8
status:
type: string
example: active
months:
type: integer
example: 1
remaining_days:
type: string
example: null
nullable: true
plan:
type: object
properties:
id:
type: integer
example: 2
name:
type: string
example: 'Smart Books'
slug:
type: string
example: smart
business:
type: object
properties:
id:
type: integer
example: 17
name:
type: string
example: 'Aarogya Medical Store'
referred_name:
type: string
example: 'Meena S'
earned_at:
type: string
example: '2026-09-15T10:00:00.000000Z'
activated_at:
type: string
example: '2026-09-24T10:00:00.000000Z'
starts_at:
type: string
example: '2026-09-24T10:00:00.000000Z'
ends_at:
type: string
example: '2026-10-24T10:00:00.000000Z'
consumed_at:
type: string
example: null
nullable: true
upcoming_rewards:
type: array
example: []
422:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'The selected business id is invalid.'
errors:
business_id:
- 'The selected business id is invalid.'
properties:
message:
type: string
example: 'The selected business id is invalid.'
errors:
type: object
properties:
business_id:
type: array
example:
- 'The selected business id is invalid.'
items:
type: string
tags:
- Referrals
requestBody:
required: false
content:
application/json:
schema:
type: object
properties:
business_id:
type: integer
description: 'An active workspace the signed-in account owns.'
example: 17
nullable: true
/api/v1/admin/referral-settings:
get:
summary: 'Get referral program settings.'
operationId: getReferralProgramSettings
description: "Super admin only. `reward_plan_id` is the configured plan, or the entry paid plan\nwhen none is configured. It is the fallback: a referrer who has paid before is\nrewarded with the plan they last paid for, and only a referrer who never paid gets\nthis plan. Program totals are included for the admin overview."
parameters: []
responses:
200:
description: ''
content:
application/json:
schema:
type: object
example:
data:
enabled: true
reward_plan_id: 2
reward_months: 1
whatsapp_message: 'Hey! Check out Dukanam. You can register using my referral link: {link}'
placeholders:
- '{link}'
- '{code}'
- '{name}'
totals:
referrals: 42
successful: 9
rewards: 9
months_granted: 9
rewards_running: 3
properties:
data:
type: object
properties:
enabled:
type: boolean
example: true
reward_plan_id:
type: integer
example: 2
reward_months:
type: integer
example: 1
whatsapp_message:
type: string
example: 'Hey! Check out Dukanam. You can register using my referral link: {link}'
placeholders:
type: array
example:
- '{link}'
- '{code}'
- '{name}'
items:
type: string
totals:
type: object
properties:
referrals:
type: integer
example: 42
successful:
type: integer
example: 9
rewards:
type: integer
example: 9
months_granted:
type: integer
example: 9
rewards_running:
type: integer
example: 3
tags:
- 'Referral program administration'
put:
summary: 'Update referral program settings.'
operationId: updateReferralProgramSettings
description: "Super admin only. Takes effect for the next reward earned; rewards already earned\nkeep the plan and months they were earned with. Turning the program off stops new\nsign-ups from being attributed and new rewards from being earned."
parameters: []
responses:
200:
description: ''
content:
application/json:
schema:
type: object
example:
data:
enabled: true
reward_plan_id: 3
reward_months: 2
whatsapp_message: 'Join me on Dukanam: {link}'
placeholders:
- '{link}'
- '{code}'
- '{name}'
totals:
referrals: 42
successful: 9
rewards: 9
months_granted: 9
rewards_running: 3
properties:
data:
type: object
properties:
enabled:
type: boolean
example: true
reward_plan_id:
type: integer
example: 3
reward_months:
type: integer
example: 2
whatsapp_message:
type: string
example: 'Join me on Dukanam: {link}'
placeholders:
type: array
example:
- '{link}'
- '{code}'
- '{name}'
items:
type: string
totals:
type: object
properties:
referrals:
type: integer
example: 42
successful:
type: integer
example: 9
rewards:
type: integer
example: 9
months_granted:
type: integer
example: 9
rewards_running:
type: integer
example: 3
422:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'Choose an active paid plan as the referral reward.'
errors:
reward_plan_id:
- 'Choose an active paid plan as the referral reward.'
properties:
message:
type: string
example: 'Choose an active paid plan as the referral reward.'
errors:
type: object
properties:
reward_plan_id:
type: array
example:
- 'Choose an active paid plan as the referral reward.'
items:
type: string
tags:
- 'Referral program administration'
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
enabled:
type: boolean
description: 'Turn the referral program on or off. While off, new sign-ups are not attributed and no new rewards are earned; rewards already earned still apply.'
example: true
reward_plan_id:
type: integer
description: 'The active paid plan a reward grants when the referrer has never paid. Referrers who paid before get the plan they last paid for.'
example: 2
reward_months:
type: integer
description: 'Free months granted per successful referral, from 1 to 24. Must be at least 1. Must not be greater than 24.'
example: 1
whatsapp_message:
type: string
description: "The share message. `{link}`, `{code}` and `{name}` are replaced with the referrer's link, code and name; the link is appended when `{link}` is missing. Must not be greater than 1000 characters."
example: 'Hey! Check out Dukanam. You can register using my referral link: {link}'
required:
- enabled
- reward_plan_id
- reward_months
- whatsapp_message
/api/v1/admin/referrals:
get:
summary: 'List all referrals.'
operationId: listAllReferrals
description: 'Super admin only. Every referral with both accounts, its status and any reward.'
parameters:
-
in: query
name: status
description: 'Filter by registered, subscription_pending, subscription_purchased, reward_earned or reward_applied.'
example: reward_earned
required: false
schema:
type: string
description: 'Filter by registered, subscription_pending, subscription_purchased, reward_earned or reward_applied.'
example: reward_earned
-
in: query
name: search
description: "Match the referrer's or referred person's name or email, or the code."
example: priya
required: false
schema:
type: string
description: "Match the referrer's or referred person's name or email, or the code."
example: priya
-
in: query
name: per_page
description: 'Results per page, from 1 to 50.'
example: 25
required: false
schema:
type: integer
description: 'Results per page, from 1 to 50.'
example: 25
responses:
200:
description: ''
content:
application/json:
schema:
type: object
example:
data:
-
id: 12
code: K7M2QX9A
status: reward_earned
status_label: 'Reward earned'
source: web
referrer:
id: 4
name: 'Priya Rao'
email: priya@example.com
referred:
id: 31
name: 'Meena S'
email: meena@example.com
qualifying_business:
id: 40
name: 'Meena Stores'
registered_at: '2026-09-10T08:00:00.000000Z'
subscription_pending_at: '2026-09-12T08:00:00.000000Z'
subscription_purchased_at: '2026-09-15T10:00:00.000000Z'
reward_earned_at: '2026-09-15T10:00:00.000000Z'
reward_applied_at: null
reward_skipped_reason: null
reward:
id: 8
status: available
months: 1
remaining_days: null
plan:
id: 2
name: 'Smart Books'
slug: smart
business: null
referred_name: null
earned_at: '2026-09-15T10:00:00.000000Z'
activated_at: null
starts_at: null
ends_at: null
consumed_at: null
links: {}
meta:
current_page: 1
per_page: 25
total: 1
properties:
data:
type: array
example:
-
id: 12
code: K7M2QX9A
status: reward_earned
status_label: 'Reward earned'
source: web
referrer:
id: 4
name: 'Priya Rao'
email: priya@example.com
referred:
id: 31
name: 'Meena S'
email: meena@example.com
qualifying_business:
id: 40
name: 'Meena Stores'
registered_at: '2026-09-10T08:00:00.000000Z'
subscription_pending_at: '2026-09-12T08:00:00.000000Z'
subscription_purchased_at: '2026-09-15T10:00:00.000000Z'
reward_earned_at: '2026-09-15T10:00:00.000000Z'
reward_applied_at: null
reward_skipped_reason: null
reward:
id: 8
status: available
months: 1
remaining_days: null
plan:
id: 2
name: 'Smart Books'
slug: smart
business: null
referred_name: null
earned_at: '2026-09-15T10:00:00.000000Z'
activated_at: null
starts_at: null
ends_at: null
consumed_at: null
items:
type: object
properties:
id:
type: integer
example: 12
code:
type: string
example: K7M2QX9A
status:
type: string
example: reward_earned
status_label:
type: string
example: 'Reward earned'
source:
type: string
example: web
referrer:
type: object
properties:
id:
type: integer
example: 4
name:
type: string
example: 'Priya Rao'
email:
type: string
example: priya@example.com
referred:
type: object
properties:
id:
type: integer
example: 31
name:
type: string
example: 'Meena S'
email:
type: string
example: meena@example.com
qualifying_business:
type: object
properties:
id:
type: integer
example: 40
name:
type: string
example: 'Meena Stores'
registered_at:
type: string
example: '2026-09-10T08:00:00.000000Z'
subscription_pending_at:
type: string
example: '2026-09-12T08:00:00.000000Z'
subscription_purchased_at:
type: string
example: '2026-09-15T10:00:00.000000Z'
reward_earned_at:
type: string
example: '2026-09-15T10:00:00.000000Z'
reward_applied_at:
type: string
example: null
nullable: true
reward_skipped_reason:
type: string
example: null
nullable: true
reward:
type: object
properties:
id:
type: integer
example: 8
status:
type: string
example: available
months:
type: integer
example: 1
remaining_days:
type: string
example: null
nullable: true
plan:
type: object
properties:
id:
type: integer
example: 2
name:
type: string
example: 'Smart Books'
slug:
type: string
example: smart
business:
type: string
example: null
nullable: true
referred_name:
type: string
example: null
nullable: true
earned_at:
type: string
example: '2026-09-15T10:00:00.000000Z'
activated_at:
type: string
example: null
nullable: true
starts_at:
type: string
example: null
nullable: true
ends_at:
type: string
example: null
nullable: true
consumed_at:
type: string
example: null
nullable: true
links:
type: object
properties: {}
meta:
type: object
properties:
current_page:
type: integer
example: 1
per_page:
type: integer
example: 25
total:
type: integer
example: 1
tags:
- 'Referral program administration'
requestBody:
required: false
content:
application/json:
schema:
type: object
properties:
status:
type: string
description: ''
example: architecto
nullable: true
search:
type: string
description: 'Must not be greater than 100 characters.'
example: 'n'
nullable: true
per_page:
type: integer
description: 'Must be at least 1. Must not be greater than 50.'
example: 7
nullable: true
/api/v1/admin/referral-rewards:
get:
summary: 'List rewards granted.'
operationId: listRewardsGranted
description: "Super admin only. Who received referral rewards, on which workspace and plan, and\nwhether each is waiting, running or used."
parameters:
-
in: query
name: status
description: 'Filter by available, active or consumed.'
example: active
required: false
schema:
type: string
description: 'Filter by available, active or consumed.'
example: active
-
in: query
name: search
description: "Match the receiving account's name or email."
example: priya
required: false
schema:
type: string
description: "Match the receiving account's name or email."
example: priya
-
in: query
name: per_page
description: 'Results per page, from 1 to 50.'
example: 25
required: false
schema:
type: integer
description: 'Results per page, from 1 to 50.'
example: 25
responses:
200:
description: ''
content:
application/json:
schema:
type: object
example:
data:
-
id: 8
status: active
months: 1
remaining_days: null
plan:
id: 2
name: 'Smart Books'
slug: smart
business:
id: 17
name: 'Aarogya Medical Store'
referred_name: 'Meena S'
earned_at: '2026-09-15T10:00:00.000000Z'
activated_at: '2026-09-24T10:00:00.000000Z'
starts_at: '2026-09-24T10:00:00.000000Z'
ends_at: '2026-10-24T10:00:00.000000Z'
consumed_at: null
user:
id: 4
name: 'Priya Rao'
email: priya@example.com
referral_id: 12
subscription_id: 95
links: {}
meta:
current_page: 1
per_page: 25
total: 1
properties:
data:
type: array
example:
-
id: 8
status: active
months: 1
remaining_days: null
plan:
id: 2
name: 'Smart Books'
slug: smart
business:
id: 17
name: 'Aarogya Medical Store'
referred_name: 'Meena S'
earned_at: '2026-09-15T10:00:00.000000Z'
activated_at: '2026-09-24T10:00:00.000000Z'
starts_at: '2026-09-24T10:00:00.000000Z'
ends_at: '2026-10-24T10:00:00.000000Z'
consumed_at: null
user:
id: 4
name: 'Priya Rao'
email: priya@example.com
referral_id: 12
subscription_id: 95
items:
type: object
properties:
id:
type: integer
example: 8
status:
type: string
example: active
months:
type: integer
example: 1
remaining_days:
type: string
example: null
nullable: true
plan:
type: object
properties:
id:
type: integer
example: 2
name:
type: string
example: 'Smart Books'
slug:
type: string
example: smart
business:
type: object
properties:
id:
type: integer
example: 17
name:
type: string
example: 'Aarogya Medical Store'
referred_name:
type: string
example: 'Meena S'
earned_at:
type: string
example: '2026-09-15T10:00:00.000000Z'
activated_at:
type: string
example: '2026-09-24T10:00:00.000000Z'
starts_at:
type: string
example: '2026-09-24T10:00:00.000000Z'
ends_at:
type: string
example: '2026-10-24T10:00:00.000000Z'
consumed_at:
type: string
example: null
nullable: true
user:
type: object
properties:
id:
type: integer
example: 4
name:
type: string
example: 'Priya Rao'
email:
type: string
example: priya@example.com
referral_id:
type: integer
example: 12
subscription_id:
type: integer
example: 95
links:
type: object
properties: {}
meta:
type: object
properties:
current_page:
type: integer
example: 1
per_page:
type: integer
example: 25
total:
type: integer
example: 1
tags:
- 'Referral program administration'
requestBody:
required: false
content:
application/json:
schema:
type: object
properties:
status:
type: string
description: ''
example: architecto
nullable: true
search:
type: string
description: 'Must not be greater than 100 characters.'
example: 'n'
nullable: true
per_page:
type: integer
description: 'Must be at least 1. Must not be greater than 50.'
example: 7
nullable: true
'/api/v1/businesses/{business}/exports':
get:
summary: 'Export business data'
operationId: exportBusinessData
description: "Reporting exports download as `dukanam-{type}-YYYY-MM-DD.{csv|xlsx}`. `format=csv` stays the default,\nso an existing caller sees no change. `format=xlsx` returns a workbook with one sheet per requested\nsection, a frozen header row, real dates and money as numbers a spreadsheet can sum.\n\n`type` accepts several sections at once — `type=invoices,expenses` or `type[]=invoices&type[]=expenses` —\nwhich requires `format=xlsx`, since a CSV file holds one table. Several sections download as\n`dukanam-export-YYYY-MM-DD.xlsx`.\n\n`from` and `to` narrow ledger, invoice and expense exports to a period. Contact exports carry lifetime\nbalances and refuse a date range rather than quietly ignoring it.\n\nAn export larger than the queued-export threshold is generated on the queue: the response is **202**\ncarrying the export record, which is polled until `status` is `completed` and then fetched from\n`download_url`. Pass `async=1` to ask for that regardless of size. The web workspace keeps streaming\nits downloads inline, since a browser link has nowhere to poll.\n\nInvoice CSV and Excel exports use readable status labels, such as Saved, instead of internal status codes.\n\nExports are online-only: the file is generated on the server from the recorded transactions, and nothing here\nbelongs in the app's offline cache or its write queue.\n\nThe legacy `business-backup` type returns a `dukanam-business-export-v2` owner/admin-only customer-data\nportability export named `dukanam-business-export-YYYY-MM-DD.json`. It includes item photos and compliance\nevidence content, item classifications, GST percentage masters, sale warranty snapshots and warranty delivery records,\nbut is not a restorable platform backup. Warranty tokens are private buyer access links;\nprotect the archive accordingly."
parameters:
-
in: query
name: type
description: 'Export type, or several comma-separated.'
example: invoices
required: true
schema:
type: string
description: 'Export type, or several comma-separated.'
example: invoices
-
in: query
name: format
description: 'Output format, `csv` or `xlsx`. Defaults to `csv`.'
example: xlsx
required: false
schema:
type: string
description: 'Output format, `csv` or `xlsx`. Defaults to `csv`.'
example: xlsx
-
in: query
name: from
description: 'date Start of the period, for exports that carry a date.'
example: '2026-04-01'
required: false
schema:
type: string
description: 'date Start of the period, for exports that carry a date.'
example: '2026-04-01'
-
in: query
name: to
description: 'date End of the period, for exports that carry a date.'
example: '2027-03-31'
required: false
schema:
type: string
description: 'date End of the period, for exports that carry a date.'
example: '2027-03-31'
-
in: query
name: async
description: 'Generate on the queue and return the export to poll, whatever its size.'
example: true
required: false
schema:
type: boolean
description: 'Generate on the queue and return the export to poll, whatever its size.'
example: true
responses:
202:
description: ''
content:
application/json:
schema:
type: object
example:
data:
uuid: 9b5f…
types:
- invoices
format: xlsx
status: queued
download_url: null
properties:
data:
type: object
properties:
uuid:
type: string
example: 9b5f…
types:
type: array
example:
- invoices
items:
type: string
format:
type: string
example: xlsx
status:
type: string
example: queued
download_url:
type: string
example: null
nullable: true
422:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'Several sections can only be exported together as a workbook. Use format=xlsx or export one type at a time.'
errors:
format:
- 'Several sections can only be exported together as a workbook. Use format=xlsx or export one type at a time.'
properties:
message:
type: string
example: 'Several sections can only be exported together as a workbook. Use format=xlsx or export one type at a time.'
errors:
type: object
properties:
format:
type: array
example:
- 'Several sections can only be exported together as a workbook. Use format=xlsx or export one type at a time.'
items:
type: string
tags:
- 'Data exports'
requestBody:
required: false
content:
application/json:
schema:
type: object
properties:
type:
type: array
description: ''
example:
- architecto
items:
type: string
format:
type: string
description: ''
example: csv
enum:
- csv
- xlsx
from:
type: string
description: 'Must be a valid date.'
example: '2026-01-15'
nullable: true
to:
type: string
description: 'Must be a valid date. Must be a date after or equal to from.'
example: '2026-01-15'
nullable: true
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
'/api/v1/businesses/{business}/exports/tally':
get:
summary: 'Export the books for Tally Prime'
operationId: exportTheBooksForTallyPrime
description: "Returns Tally-compatible XML: the shop's chart of accounts, parties, units and stock items as\nmasters, and its accounting entries as accounting vouchers. The CA imports the file into a Tally\ncompany with **Gateway of Tally → Import → Vouchers**, which is how a shop hands its year over\nwithout keeping a second set of books.\n\n`mode` chooses what the file carries — `masters`, `vouchers`, or `both` (the default). A voucher\nexport needs `from` and `to` and covers at most one financial year per call; a masters export is\nthe whole chart and refuses a date range.\n\nVouchers are built from the same accounting entries Dukanam's own trial balance is built from, so the\ntwo agree. A voucher export starting after the books did also carries one opening journal dated the\nday before `from`, holding every ledger and party balance as it stood — without it the CA would\nreceive a year of movement with no starting position.\n\nEach voucher carries a `REMOTEID` derived from Dukanam's own ids, so **re-importing an overlapping\ndate range updates the vouchers already there instead of writing a second copy of the same sales**.\n\nThe file is accounting-only: stock items arrive as masters with their units and HSN codes, but the\nvouchers carry ledger entries rather than inventory allocations, so Tally's stock summary is not\npopulated from this export. The mapping from Dukanam's chart of accounts onto Tally's groups is\nwritten into the top of the file as a comment, for the accountant to check.\n\nA large export is generated on the queue exactly as the workbook export is: the response is **202**\ncarrying the export record, polled until `status` is `completed` and then fetched from `download_url`.\nPass `async=1` to ask for that whatever the size. This is online-only — nothing here belongs in the\napp's offline cache or its write queue."
parameters:
-
in: query
name: mode
description: 'What to export: `masters`, `vouchers` or `both`. Defaults to `both`.'
example: both
required: false
schema:
type: string
description: 'What to export: `masters`, `vouchers` or `both`. Defaults to `both`.'
example: both
-
in: query
name: from
description: 'date Start of the period. Required unless `mode=masters`.'
example: '2026-04-01'
required: false
schema:
type: string
description: 'date Start of the period. Required unless `mode=masters`.'
example: '2026-04-01'
-
in: query
name: to
description: 'date End of the period. Required unless `mode=masters`.'
example: '2027-03-31'
required: false
schema:
type: string
description: 'date End of the period. Required unless `mode=masters`.'
example: '2027-03-31'
-
in: query
name: async
description: 'Generate on the queue and return the export to poll, whatever its size.'
example: true
required: false
schema:
type: boolean
description: 'Generate on the queue and return the export to poll, whatever its size.'
example: true
responses:
200:
description: 'The generated Tally XML.'
content:
application/octet-stream:
schema:
type: string
format: binary
202:
description: ''
content:
application/json:
schema:
type: object
example:
data:
uuid: 9b5f…
types:
- tally-both
format: tally
status: queued
filename: dukanam-tally-2026-09-15.xml
download_url: null
properties:
data:
type: object
properties:
uuid:
type: string
example: 9b5f…
types:
type: array
example:
- tally-both
items:
type: string
format:
type: string
example: tally
status:
type: string
example: queued
filename:
type: string
example: dukanam-tally-2026-09-15.xml
download_url:
type: string
example: null
nullable: true
422:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'A Tally voucher export needs both a start and an end date.'
errors:
from:
- 'A Tally voucher export needs both a start and an end date.'
properties:
message:
type: string
example: 'A Tally voucher export needs both a start and an end date.'
errors:
type: object
properties:
from:
type: array
example:
- 'A Tally voucher export needs both a start and an end date.'
items:
type: string
tags:
- 'Data exports'
requestBody:
required: false
content:
application/json:
schema:
type: object
properties:
mode:
type: string
description: ''
example: masters
enum:
- masters
- vouchers
- both
from:
type: string
description: 'Must be a valid date.'
example: '2026-01-15'
nullable: true
to:
type: string
description: 'Must be a valid date. Must be a date after or equal to from.'
example: '2026-01-15'
nullable: true
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
'/api/v1/businesses/{business}/exports/queued':
get:
summary: 'List queued exports.'
operationId: listQueuedExports
description: 'Newest first, whatever their status, so a failed export is visible rather than silent.'
parameters: []
responses:
401:
description: ''
content:
application/json:
schema:
type: object
example:
message: Unauthenticated.
properties:
message:
type: string
example: Unauthenticated.
tags:
- 'Data exports'
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
'/api/v1/businesses/{business}/exports/queued/{dataExport_id}':
get:
summary: 'Read one queued export.'
operationId: readOneQueuedExport
description: ''
parameters: []
responses:
401:
description: ''
content:
application/json:
schema:
type: object
example:
message: Unauthenticated.
properties:
message:
type: string
example: Unauthenticated.
tags:
- 'Data exports'
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
-
in: path
name: dataExport_id
description: 'The ID of the dataExport.'
example: 1
required: true
schema:
type: integer
'/api/v1/businesses/{business}/exports/queued/{dataExport_id}/download':
get:
summary: 'Download a generated export.'
operationId: downloadAGeneratedExport
description: ''
parameters: []
responses:
200:
description: 'The generated CSV or workbook.'
content:
application/octet-stream:
schema:
type: string
format: binary
409:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'This export is not ready yet.'
properties:
message:
type: string
example: 'This export is not ready yet.'
410:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'This export has expired. Request it again.'
properties:
message:
type: string
example: 'This export has expired. Request it again.'
tags:
- 'Data exports'
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
-
in: path
name: dataExport_id
description: 'The ID of the dataExport.'
example: 1
required: true
schema:
type: integer
'/api/v1/businesses/{business}/team':
get:
summary: 'List team members, pending invitations, and the 25 most recent expired invitations.'
operationId: listTeamMembersPendingInvitationsAndThe25MostRecentExpiredInvitations
description: ''
parameters: []
responses:
200:
description: ''
content:
application/json:
schema:
type: object
example:
data:
members:
-
id: 2
name: 'Priya Rao'
email: priya@example.com
role: cashier
status: active
joined_at: '2026-08-21T12:00:00.000000Z'
invitations: []
roles:
cashier:
label: Cashier
description: 'POS, sales, and cash register access.'
accountant:
label: Accountant
description: 'Sales, purchases, inventory, accounting, reports, compliance, and exports.'
seats:
used: 2
limit: 5
active: 2
pending: 0
properties:
data:
type: object
properties:
members:
type: array
example:
- id: 2
name: 'Priya Rao'
email: priya@example.com
role: cashier
status: active
joined_at: '2026-08-21T12:00:00.000000Z'
items:
type: object
properties:
id:
type: integer
example: 2
name:
type: string
example: 'Priya Rao'
email:
type: string
example: priya@example.com
role:
type: string
example: cashier
status:
type: string
example: active
joined_at:
type: string
example: '2026-08-21T12:00:00.000000Z'
invitations:
type: array
example: []
roles:
type: object
properties:
cashier:
type: object
properties:
label:
type: string
example: Cashier
description:
type: string
example: 'POS, sales, and cash register access.'
accountant:
type: object
properties:
label:
type: string
example: Accountant
description:
type: string
example: 'Sales, purchases, inventory, accounting, reports, compliance, and exports.'
seats:
type: object
properties:
used:
type: integer
example: 2
limit:
type: integer
example: 5
description: 'Total workspace seat limit. A value of 0 means unlimited seats.'
active:
type: integer
example: 2
pending:
type: integer
example: 0
tags:
- 'Team access'
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
'/api/v1/businesses/{business}/team/invitations':
post:
summary: 'Invite a team member.'
operationId: inviteATeamMember
description: 'Pending invitations reserve a seat and expire after seven days.'
parameters: []
responses:
201:
description: ''
content:
application/json:
schema:
type: object
example:
data:
id: 12
email: cashier@example.com
role: cashier
status: pending
expires_at: '2026-08-28T12:00:00.000000Z'
accepted_at: null
invited_by:
id: 1
name: 'Workspace Owner'
properties:
data:
type: object
properties:
id:
type: integer
example: 12
email:
type: string
example: cashier@example.com
role:
type: string
example: cashier
status:
type: string
example: pending
expires_at:
type: string
example: '2026-08-28T12:00:00.000000Z'
accepted_at:
type: string
example: null
nullable: true
invited_by:
type: object
properties:
id:
type: integer
example: 1
name:
type: string
example: 'Workspace Owner'
tags:
- 'Team access'
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
email:
type: string
description: "The invited person's email."
example: cashier@example.com
role:
type: string
description: 'One of admin, manager, cashier, accountant, staff, or viewer. The `pos` role is not invitable: POS logins are bought per seat and created with `POST /businesses/{business}/team/pos-logins`.'
example: cashier
required:
- email
- role
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
'/api/v1/businesses/{business}/team/invitations/{teamInvitation_id}/resend':
post:
summary: 'Resend a pending or expired invitation with a new secure token.'
operationId: resendAPendingOrExpiredInvitationWithANewSecureToken
description: ''
parameters: []
responses:
200:
description: ''
content:
application/json:
schema:
type: object
example:
data:
id: 12
email: cashier@example.com
role: cashier
status: pending
expires_at: '2026-08-28T12:00:00.000000Z'
accepted_at: null
invited_by:
id: 1
name: 'Workspace Owner'
properties:
data:
type: object
properties:
id:
type: integer
example: 12
email:
type: string
example: cashier@example.com
role:
type: string
example: cashier
status:
type: string
example: pending
expires_at:
type: string
example: '2026-08-28T12:00:00.000000Z'
accepted_at:
type: string
example: null
nullable: true
invited_by:
type: object
properties:
id:
type: integer
example: 1
name:
type: string
example: 'Workspace Owner'
422:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'All 2 workspace seats are already assigned or reserved.'
errors:
email:
- 'All 2 workspace seats are already assigned or reserved.'
properties:
message:
type: string
example: 'All 2 workspace seats are already assigned or reserved.'
errors:
type: object
properties:
email:
type: array
example:
- 'All 2 workspace seats are already assigned or reserved.'
items:
type: string
tags:
- 'Team access'
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
-
in: path
name: teamInvitation_id
description: 'The ID of the teamInvitation.'
example: 1
required: true
schema:
type: integer
'/api/v1/businesses/{business}/team/invitations/{teamInvitation_id}':
delete:
summary: 'Cancel a pending invitation and release its reserved seat.'
operationId: cancelAPendingInvitationAndReleaseItsReservedSeat
description: ''
parameters: []
responses:
204:
description: ''
content:
application/json:
schema:
type: object
example: {}
properties: {}
403:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'Only the owner can manage administrator invitations.'
properties:
message:
type: string
example: 'Only the owner can manage administrator invitations.'
tags:
- 'Team access'
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
-
in: path
name: teamInvitation_id
description: 'The ID of the teamInvitation.'
example: 1
required: true
schema:
type: integer
'/api/v1/businesses/{business}/team/members/{member_id}':
patch:
summary: "Change a member's role or suspend/reactivate access."
operationId: changeAMembersRoleOrSuspendreactivateAccess
description: ''
parameters: []
responses:
200:
description: ''
content:
application/json:
schema:
type: object
example:
data:
id: 2
name: 'Priya Rao'
email: priya@example.com
role: manager
status: active
joined_at: '2026-08-21T12:00:00.000000Z'
properties:
data:
type: object
properties:
id:
type: integer
example: 2
name:
type: string
example: 'Priya Rao'
email:
type: string
example: priya@example.com
role:
type: string
example: manager
status:
type: string
example: active
joined_at:
type: string
example: '2026-08-21T12:00:00.000000Z'
422:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'All 2 workspace seats are already assigned or reserved.'
errors:
role:
- 'All 2 workspace seats are already assigned or reserved.'
status:
- 'All 2 workspace seats are already assigned or reserved.'
properties:
message:
type: string
example: 'All 2 workspace seats are already assigned or reserved.'
errors:
type: object
properties:
role:
type: array
example:
- 'All 2 workspace seats are already assigned or reserved.'
items:
type: string
status:
type: array
example:
- 'All 2 workspace seats are already assigned or reserved.'
items:
type: string
tags:
- 'Team access'
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
role:
type: string
description: 'One of admin, manager, cashier, accountant, staff, or viewer.'
example: manager
status:
type: string
description: 'Either active or suspended.'
example: active
required:
- role
- status
delete:
summary: "Remove a member's workspace access without deleting their user account."
operationId: removeAMembersWorkspaceAccessWithoutDeletingTheirUserAccount
description: ''
parameters: []
responses:
204:
description: ''
content:
application/json:
schema:
type: object
example: {}
properties: {}
tags:
- 'Team access'
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
-
in: path
name: member_id
description: 'The ID of the member.'
example: 1
required: true
schema:
type: integer
'/api/v1/businesses/{business}/team/pos-logins':
post:
summary: 'Create a POS login against a paid counter seat.'
operationId: createAPOSLoginAgainstAPaidCounterSeat
description: "A POS login can open the billing counter and its cash drawer and nothing else. Seats\nare bought with `POST /businesses/{business}/billing/pos-seats`; creating a login\nwithout a free paid seat returns `422`. Unlike an invitation the password is set here\nand handed to the counter staff directly."
parameters: []
responses:
201:
description: ''
content:
application/json:
schema:
type: object
example:
data:
id: 7
name: 'Ravi Kumar'
email: counter@example.com
role: pos
status: active
joined_at: '2026-09-02T12:00:00.000000Z'
properties:
data:
type: object
properties:
id:
type: integer
example: 7
name:
type: string
example: 'Ravi Kumar'
email:
type: string
example: counter@example.com
role:
type: string
example: pos
status:
type: string
example: active
joined_at:
type: string
example: '2026-09-02T12:00:00.000000Z'
422:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'Buy a POS login before creating counter staff.'
errors:
email:
- 'Buy a POS login before creating counter staff.'
properties:
message:
type: string
example: 'Buy a POS login before creating counter staff.'
errors:
type: object
properties:
email:
type: array
example:
- 'Buy a POS login before creating counter staff.'
items:
type: string
tags:
- 'Team access'
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
name:
type: string
description: "The counter staff member's name."
example: 'Ravi Kumar'
email:
type: string
description: 'The email they sign in with.'
example: counter@example.com
password:
type: string
description: 'At least eight characters.'
example: counter-password
required:
- name
- email
- password
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
'/api/v1/businesses/{business}/team/pos-logins/{member_id}/password':
patch:
summary: "Replace a POS login's password and sign its devices out."
operationId: replaceAPOSLoginsPasswordAndSignItsDevicesOut
description: ''
parameters: []
responses:
200:
description: ''
content:
application/json:
schema:
type: object
example:
data:
id: 7
name: 'Ravi Kumar'
email: counter@example.com
role: pos
status: active
joined_at: '2026-09-02T12:00:00.000000Z'
properties:
data:
type: object
properties:
id:
type: integer
example: 7
name:
type: string
example: 'Ravi Kumar'
email:
type: string
example: counter@example.com
role:
type: string
example: pos
status:
type: string
example: active
joined_at:
type: string
example: '2026-09-02T12:00:00.000000Z'
422:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'That member is not a POS login.'
properties:
message:
type: string
example: 'That member is not a POS login.'
tags:
- 'Team access'
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
password:
type: string
description: 'The new password, at least eight characters.'
example: new-counter-password
required:
- password
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
-
in: path
name: member_id
description: 'The ID of the member.'
example: 1
required: true
schema:
type: integer
'/api/v1/team-invitations/{token}/accept':
post:
summary: 'Accept a team invitation as an existing user.'
operationId: acceptATeamInvitationAsAnExistingUser
description: 'Send a valid Sanctum bearer token belonging to the invited email. This endpoint does not create a new token.'
parameters: []
responses:
200:
description: ''
content:
application/json:
schema:
type: object
example:
data:
user:
id: 2
name: 'Priya Rao'
email: cashier@example.com
business:
id: 1
name: 'Anika Stores'
role: cashier
properties:
data:
type: object
properties:
user:
type: object
properties:
id:
type: integer
example: 2
name:
type: string
example: 'Priya Rao'
email:
type: string
example: cashier@example.com
business:
type: object
properties:
id:
type: integer
example: 1
name:
type: string
example: 'Anika Stores'
role:
type: string
example: cashier
404:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'Not Found'
properties:
message:
type: string
example: 'Not Found'
422:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'The given data was invalid.'
errors:
email:
- 'Sign in with the email address that received this invitation.'
invitation:
- 'This workspace is no longer available.'
properties:
message:
type: string
example: 'The given data was invalid.'
errors:
type: object
properties:
email:
type: array
example:
- 'Sign in with the email address that received this invitation.'
items:
type: string
invitation:
type: array
example:
- 'This workspace is no longer available.'
items:
type: string
tags:
- 'Team invitations'
parameters:
-
in: path
name: token
description: 'The opaque token from the invitation email.'
example: 4WvBr4F8Dmcx3FJkL1nEz7PcQs2Yu9Aa
required: true
schema:
type: string
'/api/v1/team-invitations/{token}':
get:
summary: 'Inspect a team invitation.'
operationId: inspectATeamInvitation
description: 'This endpoint does not require authentication. The opaque token is supplied by the invitation email.'
parameters: []
responses:
200:
description: ''
content:
application/json:
schema:
type: object
example:
data:
business:
id: 1
name: 'Anika Stores'
email: cashier@example.com
role: cashier
role_label: Cashier
status: pending
expires_at: '2026-08-28T12:00:00.000000Z'
existing_account: false
properties:
data:
type: object
properties:
business:
type: object
properties:
id:
type: integer
example: 1
name:
type: string
example: 'Anika Stores'
email:
type: string
example: cashier@example.com
role:
type: string
example: cashier
role_label:
type: string
example: Cashier
status:
type: string
example: pending
expires_at:
type: string
example: '2026-08-28T12:00:00.000000Z'
existing_account:
type: boolean
example: false
404:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'Not Found'
properties:
message:
type: string
example: 'Not Found'
tags:
- 'Team invitations'
security: []
parameters:
-
in: path
name: token
description: 'The opaque token from the invitation email.'
example: 4WvBr4F8Dmcx3FJkL1nEz7PcQs2Yu9Aa
required: true
schema:
type: string
'/api/v1/team-invitations/{token}/register-and-accept':
post:
summary: 'Create an account and accept a team invitation.'
operationId: createAnAccountAndAcceptATeamInvitation
description: 'Use this unauthenticated endpoint only when the invited email does not already belong to a Dukanam user. The response includes a new Sanctum device token.'
parameters: []
responses:
200:
description: ''
content:
application/json:
schema:
type: object
example:
data:
user:
id: 2
name: 'Priya Rao'
email: cashier@example.com
business:
id: 1
name: 'Anika Stores'
role: cashier
token: 1|new-mobile-token
properties:
data:
type: object
properties:
user:
type: object
properties:
id:
type: integer
example: 2
name:
type: string
example: 'Priya Rao'
email:
type: string
example: cashier@example.com
business:
type: object
properties:
id:
type: integer
example: 1
name:
type: string
example: 'Anika Stores'
role:
type: string
example: cashier
token:
type: string
example: 1|new-mobile-token
403:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'Sign in as the invited user to accept this invitation.'
properties:
message:
type: string
example: 'Sign in as the invited user to accept this invitation.'
404:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'Not Found'
properties:
message:
type: string
example: 'Not Found'
422:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'This invitation has expired or is no longer available.'
errors:
invitation:
- 'This invitation has expired or is no longer available.'
properties:
message:
type: string
example: 'This invitation has expired or is no longer available.'
errors:
type: object
properties:
invitation:
type: array
example:
- 'This invitation has expired or is no longer available.'
items:
type: string
tags:
- 'Team invitations'
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
name:
type: string
description: "The new user's name."
example: 'Priya Rao'
password:
type: string
description: "The new user's password; minimum eight characters."
example: secret-pass-123
device_name:
type: string
description: 'The name for the new API token.'
example: "Priya's phone"
password_confirmation:
type: string
description: 'Password confirmation.'
example: secret-pass-123
required:
- name
- password
- device_name
- password_confirmation
security: []
parameters:
-
in: path
name: token
description: 'The opaque token from the invitation email.'
example: 4WvBr4F8Dmcx3FJkL1nEz7PcQs2Yu9Aa
required: true
schema:
type: string
'/api/v1/businesses/{business_id}/testimonials':
get:
summary: 'List my workspace testimonials.'
operationId: listMyWorkspaceTestimonials
description: "Returns only testimonials submitted by the signed-in user for this workspace.\nIncludes admin-authored drafts linked to this business owner; this grants\nno workspace access. photo_url may be null for a text-only draft."
parameters:
-
in: query
name: per_page
description: 'Results per page, from 1 to 24.'
example: 12
required: false
schema:
type: integer
description: 'Results per page, from 1 to 24.'
example: 12
responses:
200:
description: ''
content:
application/json:
schema:
type: object
example:
data:
-
id: 53d5597b-4a34-4cf0-9dd3-7a0ae9f64c29
store_type: pharmacy
feedback: 'Dukanam keeps our counter records together.'
photo_url: 'https://dukanam.com/testimonials/53d5597b-4a34-4cf0-9dd3-7a0ae9f64c29/photo'
status: pending
submitted_at: '2026-08-31T10:15:00.000000Z'
published_at: null
links: {}
meta:
current_page: 1
per_page: 12
total: 1
properties:
data:
type: array
example:
- id: 53d5597b-4a34-4cf0-9dd3-7a0ae9f64c29
store_type: pharmacy
feedback: 'Dukanam keeps our counter records together.'
photo_url: 'https://dukanam.com/testimonials/53d5597b-4a34-4cf0-9dd3-7a0ae9f64c29/photo'
status: pending
submitted_at: '2026-08-31T10:15:00.000000Z'
published_at: null
items:
type: object
properties:
id:
type: string
example: 53d5597b-4a34-4cf0-9dd3-7a0ae9f64c29
store_type:
type: string
example: pharmacy
feedback:
type: string
example: 'Dukanam keeps our counter records together.'
photo_url:
type: string
example: 'https://dukanam.com/testimonials/53d5597b-4a34-4cf0-9dd3-7a0ae9f64c29/photo'
status:
type: string
example: pending
submitted_at:
type: string
example: '2026-08-31T10:15:00.000000Z'
published_at:
type: string
example: null
nullable: true
links:
type: object
properties: {}
meta:
type: object
properties:
current_page:
type: integer
example: 1
per_page:
type: integer
example: 12
total:
type: integer
example: 1
tags:
- Testimonials
post:
summary: 'Submit a testimonial from a subscribed workspace.'
operationId: submitATestimonialFromASubscribedWorkspace
description: "Requires a usable non-free subscription. Name, email and shop identity are\ntaken from the authenticated user and workspace, not request fields."
parameters: []
responses:
202:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'Thank you. Your testimonial is awaiting review.'
data:
id: 53d5597b-4a34-4cf0-9dd3-7a0ae9f64c29
store_type: pharmacy
feedback: 'Dukanam keeps our counter records together.'
photo_url: 'https://dukanam.com/testimonials/53d5597b-4a34-4cf0-9dd3-7a0ae9f64c29/photo'
status: pending
submitted_at: '2026-08-31T10:15:00.000000Z'
published_at: null
properties:
message:
type: string
example: 'Thank you. Your testimonial is awaiting review.'
data:
type: object
properties:
id:
type: string
example: 53d5597b-4a34-4cf0-9dd3-7a0ae9f64c29
store_type:
type: string
example: pharmacy
feedback:
type: string
example: 'Dukanam keeps our counter records together.'
photo_url:
type: string
example: 'https://dukanam.com/testimonials/53d5597b-4a34-4cf0-9dd3-7a0ae9f64c29/photo'
status:
type: string
example: pending
submitted_at:
type: string
example: '2026-08-31T10:15:00.000000Z'
published_at:
type: string
example: null
nullable: true
tags:
- Testimonials
requestBody:
required: true
content:
multipart/form-data:
schema:
type: object
properties:
store_type:
type: string
description: 'Industry page that should receive the testimonial after approval. Accepts configured industry slugs, including electrical-store, paint-store, sanitaryware-store and pesticide-store.'
example: pharmacy
enum:
- kirana-store
- pharmacy
- supermarket
- clothing-store
- footwear-store
- hardware-store
- electronics-store
- mobile-store
- stationery-store
- book-store
- bakery
- cosmetics-store
- wholesale-business
- auto-parts-store
- home-appliance-store
- gift-shop
- electrical-store
- paint-store
- sanitaryware-store
- pesticide-store
feedback:
type: string
description: 'First-hand feedback, 40 to 1,200 characters. Must be at least 40 characters. Must not be greater than 1200 characters.'
example: 'Dukanam made our daily medicine counter billing easier to review, and the purchase and stock records now stay together for closing.'
photo:
type: string
format: binary
description: 'JPEG, PNG or WebP shop photo, 320×320 to 6000×6000 pixels, maximum 5 MB. Must be an image. Must not be greater than 5120 kilobytes.'
consent:
type: boolean
description: 'Must be true to confirm content rights, consent from people shown, and permission to moderate and publish the submitted name, shop, photo and feedback. Must be accepted.'
example: true
website:
type: string
description: 'Spam-trap field. Omit it or leave it empty.'
example: null
nullable: true
required:
- store_type
- feedback
- photo
- consent
parameters:
-
in: path
name: business_id
description: 'Workspace ID.'
example: 17
required: true
schema:
type: integer
/api/v1/testimonials:
get:
summary: 'List approved public testimonials.'
operationId: listApprovedPublicTestimonials
description: "Includes only consented stories; internal uxcrafts.com owners and businesses\nare excluded. Admin-authored stories may be text-only."
parameters:
-
in: query
name: store_type
description: 'Filter by one configured industry slug, including electrical-store, paint-store, sanitaryware-store and pesticide-store.'
example: electrical-store
required: false
schema:
type: string
description: 'Filter by one configured industry slug, including electrical-store, paint-store, sanitaryware-store and pesticide-store.'
example: electrical-store
-
in: query
name: per_page
description: 'Results per page, from 1 to 24.'
example: 12
required: false
schema:
type: integer
description: 'Results per page, from 1 to 24.'
example: 12
responses:
200:
description: ''
content:
application/json:
schema:
type: object
example:
data:
-
id: 53d5597b-4a34-4cf0-9dd3-7a0ae9f64c29
store_type: pharmacy
owner_name: 'Dr. Kavitha Reddy'
shop_name: 'Aarogya Medical & General Store'
feedback: 'Dukanam made our daily medicine counter billing easier to review.'
photo_url: 'https://dukanam.com/testimonials/53d5597b-4a34-4cf0-9dd3-7a0ae9f64c29/photo'
published_at: '2026-08-31T10:30:00.000000Z'
location: null
links:
first: 'https://dukanam.com/api/v1/testimonials?page=1'
last: 'https://dukanam.com/api/v1/testimonials?page=1'
prev: null
next: null
meta:
current_page: 1
from: 1
last_page: 1
links: []
path: 'https://dukanam.com/api/v1/testimonials'
per_page: 12
to: 1
total: 1
properties:
data:
type: array
example:
- id: 53d5597b-4a34-4cf0-9dd3-7a0ae9f64c29
store_type: pharmacy
owner_name: 'Dr. Kavitha Reddy'
shop_name: 'Aarogya Medical & General Store'
feedback: 'Dukanam made our daily medicine counter billing easier to review.'
photo_url: 'https://dukanam.com/testimonials/53d5597b-4a34-4cf0-9dd3-7a0ae9f64c29/photo'
published_at: '2026-08-31T10:30:00.000000Z'
location: null
items:
type: object
properties:
id:
type: string
example: 53d5597b-4a34-4cf0-9dd3-7a0ae9f64c29
store_type:
type: string
example: pharmacy
owner_name:
type: string
example: 'Dr. Kavitha Reddy'
shop_name:
type: string
example: 'Aarogya Medical & General Store'
feedback:
type: string
example: 'Dukanam made our daily medicine counter billing easier to review.'
photo_url:
type: string
example: 'https://dukanam.com/testimonials/53d5597b-4a34-4cf0-9dd3-7a0ae9f64c29/photo'
published_at:
type: string
example: '2026-08-31T10:30:00.000000Z'
location:
type: string
example: null
nullable: true
links:
type: object
properties:
first:
type: string
example: 'https://dukanam.com/api/v1/testimonials?page=1'
last:
type: string
example: 'https://dukanam.com/api/v1/testimonials?page=1'
prev:
type: string
example: null
nullable: true
next:
type: string
example: null
nullable: true
meta:
type: object
properties:
current_page:
type: integer
example: 1
from:
type: integer
example: 1
last_page:
type: integer
example: 1
links:
type: array
example: []
path:
type: string
example: 'https://dukanam.com/api/v1/testimonials'
per_page:
type: integer
example: 12
to:
type: integer
example: 1
total:
type: integer
example: 1
tags:
- Testimonials
security: []
/api/v1/admin/testimonials:
get:
summary: 'List testimonial submissions for moderation.'
operationId: listTestimonialSubmissionsForModeration
description: ''
parameters:
-
in: query
name: status
description: 'Filter by pending, approved or rejected.'
example: pending
required: false
schema:
type: string
description: 'Filter by pending, approved or rejected.'
example: pending
-
in: query
name: store_type
description: 'Filter by one configured industry slug, including electrical-store, paint-store, sanitaryware-store and pesticide-store.'
example: electrical-store
required: false
schema:
type: string
description: 'Filter by one configured industry slug, including electrical-store, paint-store, sanitaryware-store and pesticide-store.'
example: electrical-store
-
in: query
name: per_page
description: 'Results per page, from 1 to 50.'
example: 24
required: false
schema:
type: integer
description: 'Results per page, from 1 to 50.'
example: 24
responses:
200:
description: ''
content:
application/json:
schema:
type: object
example:
data:
-
id: 53d5597b-4a34-4cf0-9dd3-7a0ae9f64c29
store_type: pharmacy
user_id: 42
business_id: 17
owner_name: 'Dr. Kavitha Reddy'
shop_name: 'Aarogya Medical & General Store'
contact_email: kavitha@example.com
feedback: 'Dukanam made our daily medicine counter billing easier to review.'
photo_url: 'https://dukanam.com/testimonials/53d5597b-4a34-4cf0-9dd3-7a0ae9f64c29/photo'
photo_mime_type: image/webp
photo_size: 98214
status: pending
consent_at: '2026-08-31T10:15:00.000000Z'
review_note: null
reviewed_by: null
reviewed_at: null
published_at: null
submitted_at: '2026-08-31T10:15:00.000000Z'
location: null
source: customer
consent_note: null
links:
first: 'https://dukanam.com/api/v1/admin/testimonials?page=1'
last: 'https://dukanam.com/api/v1/admin/testimonials?page=1'
prev: null
next: null
meta:
current_page: 1
from: 1
last_page: 1
links: []
path: 'https://dukanam.com/api/v1/admin/testimonials'
per_page: 24
to: 1
total: 1
properties:
data:
type: array
example:
- id: 53d5597b-4a34-4cf0-9dd3-7a0ae9f64c29
store_type: pharmacy
user_id: 42
business_id: 17
owner_name: 'Dr. Kavitha Reddy'
shop_name: 'Aarogya Medical & General Store'
contact_email: kavitha@example.com
feedback: 'Dukanam made our daily medicine counter billing easier to review.'
photo_url: 'https://dukanam.com/testimonials/53d5597b-4a34-4cf0-9dd3-7a0ae9f64c29/photo'
photo_mime_type: image/webp
photo_size: 98214
status: pending
consent_at: '2026-08-31T10:15:00.000000Z'
review_note: null
reviewed_by: null
reviewed_at: null
published_at: null
submitted_at: '2026-08-31T10:15:00.000000Z'
location: null
source: customer
consent_note: null
items:
type: object
properties:
id:
type: string
example: 53d5597b-4a34-4cf0-9dd3-7a0ae9f64c29
store_type:
type: string
example: pharmacy
user_id:
type: integer
example: 42
business_id:
type: integer
example: 17
owner_name:
type: string
example: 'Dr. Kavitha Reddy'
shop_name:
type: string
example: 'Aarogya Medical & General Store'
contact_email:
type: string
example: kavitha@example.com
feedback:
type: string
example: 'Dukanam made our daily medicine counter billing easier to review.'
photo_url:
type: string
example: 'https://dukanam.com/testimonials/53d5597b-4a34-4cf0-9dd3-7a0ae9f64c29/photo'
photo_mime_type:
type: string
example: image/webp
photo_size:
type: integer
example: 98214
status:
type: string
example: pending
consent_at:
type: string
example: '2026-08-31T10:15:00.000000Z'
review_note:
type: string
example: null
nullable: true
reviewed_by:
type: string
example: null
nullable: true
reviewed_at:
type: string
example: null
nullable: true
published_at:
type: string
example: null
nullable: true
submitted_at:
type: string
example: '2026-08-31T10:15:00.000000Z'
location:
type: string
example: null
nullable: true
source:
type: string
example: customer
consent_note:
type: string
example: null
nullable: true
links:
type: object
properties:
first:
type: string
example: 'https://dukanam.com/api/v1/admin/testimonials?page=1'
last:
type: string
example: 'https://dukanam.com/api/v1/admin/testimonials?page=1'
prev:
type: string
example: null
nullable: true
next:
type: string
example: null
nullable: true
meta:
type: object
properties:
current_page:
type: integer
example: 1
from:
type: integer
example: 1
last_page:
type: integer
example: 1
links:
type: array
example: []
path:
type: string
example: 'https://dukanam.com/api/v1/admin/testimonials'
per_page:
type: integer
example: 24
to:
type: integer
example: 1
total:
type: integer
example: 1
tags:
- 'Testimonial moderation'
post:
summary: 'Create a customer testimonial draft.'
operationId: createACustomerTestimonialDraft
description: "Super admins may draft on behalf of a customer on any plan. Identity and\nprivate contact email come from the selected business and its owner;\ndisplay-name corrections are allowed. uxcrafts.com and its subdomains are\nexcluded for both the owner and business email. Drafts always start pending.\nPhotos are optional. Permission for the exact wording and attribution must\nbe recorded with consent=true and a private consent_note before approval."
parameters: []
responses:
201:
description: ''
content:
application/json:
schema:
type: object
example:
data:
id: 53d5597b-4a34-4cf0-9dd3-7a0ae9f64c29
business_id: 17
user_id: 42
store_type: pharmacy
owner_name: 'Kavitha Reddy'
shop_name: 'Aarogya Medical Store'
location: Hyderabad
feedback: 'We have had a positive experience using Dukanam for our pharmacy in Hyderabad.'
source: admin
status: pending
photo_url: null
photo_mime_type: null
photo_size: null
consent_at: null
consent_note: null
published_at: null
properties:
data:
type: object
properties:
id:
type: string
example: 53d5597b-4a34-4cf0-9dd3-7a0ae9f64c29
business_id:
type: integer
example: 17
user_id:
type: integer
example: 42
store_type:
type: string
example: pharmacy
description: 'Optional industry page; required before approval.'
owner_name:
type: string
example: 'Kavitha Reddy'
shop_name:
type: string
example: 'Aarogya Medical Store'
location:
type: string
example: Hyderabad
description: 'Optional public city or region.'
feedback:
type: string
example: 'We have had a positive experience using Dukanam for our pharmacy in Hyderabad.'
source:
type: string
example: admin
description: 'customer for owner submissions, admin for administrator-authored drafts.'
status:
type: string
example: pending
photo_url:
type: string
example: null
description: 'Null for text-only testimonials.'
photo_mime_type:
type: string
example: null
nullable: true
photo_size:
type: string
example: null
nullable: true
consent_at:
type: string
example: null
description: 'Null when permission has not been recorded.'
consent_note:
type: string
example: null
description: 'Private permission evidence, never returned publicly.'
published_at:
type: string
example: null
nullable: true
422:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'Choose a customer business with an owner outside uxcrafts.com and its subdomains.'
errors:
business_id:
- 'Choose a customer business with an owner outside uxcrafts.com and its subdomains.'
properties:
message:
type: string
example: 'Choose a customer business with an owner outside uxcrafts.com and its subdomains.'
errors:
type: object
properties:
business_id:
type: array
example:
- 'Choose a customer business with an owner outside uxcrafts.com and its subdomains.'
items:
type: string
tags:
- 'Testimonial moderation'
requestBody:
required: true
content:
multipart/form-data:
schema:
type: object
properties:
business_id:
type: integer
description: 'Existing customer business ID. Required on creation; prohibited on editing. Owners or businesses with uxcrafts.com or subdomain emails are excluded. Must match an existing stored value.'
example: 17
store_type:
type: string
description: 'Optional configured industry page slug. An unassigned draft can be saved but requires a page before approval.'
example: pharmacy
enum:
- kirana-store
- pharmacy
- supermarket
- clothing-store
- footwear-store
- hardware-store
- electronics-store
- mobile-store
- stationery-store
- book-store
- bakery
- cosmetics-store
- wholesale-business
- auto-parts-store
- home-appliance-store
- gift-shop
- electrical-store
- paint-store
- sanitaryware-store
- pesticide-store
nullable: true
owner_name:
type: string
description: 'Optional display-name correction. Defaults to the business owner on creation and keeps the current name on editing. Must be at least 1 character. Must not be greater than 120 characters.'
example: 'Kavitha Reddy'
nullable: true
shop_name:
type: string
description: 'Optional business display name; defaults to the registered business name. Must be at least 1 character. Must not be greater than 160 characters.'
example: 'Aarogya Medical Store'
nullable: true
location:
type: string
description: 'Optional public city/region, up to 160 characters. Defaults to the business city on creation. Must not be greater than 160 characters.'
example: 'Hyderabad, Telangana'
nullable: true
feedback:
type: string
description: 'Customer-supported wording, 40–1200 characters. Always saved pending; editing unpublishes the previous version. Must be at least 40 characters. Must not be greater than 1200 characters.'
example: 'We have had a positive experience using Dukanam for our pharmacy in Hyderabad.'
photo:
type: string
format: binary
description: 'Optional real shop photo. JPEG, PNG or WebP, 320–6000 pixels per dimension, maximum 5 MB. Omit to keep the existing photo on editing. Must be an image. Must not be greater than 5120 kilobytes.'
nullable: true
remove_photo:
type: boolean
description: 'Remove the existing photo on editing; cannot be combined with a new photo.'
example: false
consent:
type: boolean
description: 'Confirm permission for the exact wording, attribution, location and any photo. False or omitted clears consent; drafts without consent cannot be approved.'
example: true
consent_note:
type: string
description: 'Private source/evidence of permission, maximum 1000 characters. Required when consent is true; never public. This field is required when consent is 1 or true. Must not be greater than 1000 characters.'
example: 'Owner approved this wording and public attribution in our customer conversation.'
nullable: true
required:
- business_id
- feedback
'/api/v1/admin/testimonials/{public_id}':
put:
summary: 'Edit a testimonial and return it to pending review.'
operationId: editATestimonialAndReturnItToPendingReview
description: "The business reference cannot be changed. Every edit unpublishes the old\nversion from matching web pages and the public API. Omit photo to keep it,\nor set remove_photo=true to remove it. Confirm consent and supply its private\nnote again for the revised wording; omitted/false consent clears permission.\nFor multipart edits, POST this URL with _method=PUT."
parameters: []
responses:
200:
description: ''
content:
application/json:
schema:
type: object
example:
data:
id: 53d5597b-4a34-4cf0-9dd3-7a0ae9f64c29
business_id: 17
user_id: 42
store_type: pharmacy
owner_name: 'Kavitha Reddy'
shop_name: 'Aarogya Medical Store'
location: Hyderabad
feedback: 'We have had a positive experience using Dukanam for our pharmacy in Hyderabad.'
source: admin
status: pending
photo_url: null
photo_mime_type: null
photo_size: null
consent_at: null
consent_note: null
published_at: null
properties:
data:
type: object
properties:
id:
type: string
example: 53d5597b-4a34-4cf0-9dd3-7a0ae9f64c29
business_id:
type: integer
example: 17
user_id:
type: integer
example: 42
store_type:
type: string
example: pharmacy
owner_name:
type: string
example: 'Kavitha Reddy'
shop_name:
type: string
example: 'Aarogya Medical Store'
location:
type: string
example: Hyderabad
feedback:
type: string
example: 'We have had a positive experience using Dukanam for our pharmacy in Hyderabad.'
source:
type: string
example: admin
status:
type: string
example: pending
photo_url:
type: string
example: null
nullable: true
photo_mime_type:
type: string
example: null
nullable: true
photo_size:
type: string
example: null
nullable: true
consent_at:
type: string
example: null
nullable: true
consent_note:
type: string
example: null
nullable: true
published_at:
type: string
example: null
nullable: true
tags:
- 'Testimonial moderation'
requestBody:
required: true
content:
multipart/form-data:
schema:
type: object
properties:
store_type:
type: string
description: 'Optional configured industry page slug. An unassigned draft can be saved but requires a page before approval.'
example: pharmacy
enum:
- kirana-store
- pharmacy
- supermarket
- clothing-store
- footwear-store
- hardware-store
- electronics-store
- mobile-store
- stationery-store
- book-store
- bakery
- cosmetics-store
- wholesale-business
- auto-parts-store
- home-appliance-store
- gift-shop
- electrical-store
- paint-store
- sanitaryware-store
- pesticide-store
nullable: true
owner_name:
type: string
description: 'Optional display-name correction. Defaults to the business owner on creation and keeps the current name on editing. Must be at least 1 character. Must not be greater than 120 characters.'
example: 'Kavitha Reddy'
nullable: true
shop_name:
type: string
description: 'Optional business display name; defaults to the registered business name. Must be at least 1 character. Must not be greater than 160 characters.'
example: 'Aarogya Medical Store'
nullable: true
location:
type: string
description: 'Optional public city/region, up to 160 characters. Defaults to the business city on creation. Must not be greater than 160 characters.'
example: 'Hyderabad, Telangana'
nullable: true
feedback:
type: string
description: 'Customer-supported wording, 40–1200 characters. Always saved pending; editing unpublishes the previous version. Must be at least 40 characters. Must not be greater than 1200 characters.'
example: 'We have had a positive experience using Dukanam for our pharmacy in Hyderabad.'
photo:
type: string
format: binary
description: 'Optional real shop photo. JPEG, PNG or WebP, 320–6000 pixels per dimension, maximum 5 MB. Omit to keep the existing photo on editing. Must be an image. Must not be greater than 5120 kilobytes.'
nullable: true
remove_photo:
type: boolean
description: 'Remove the existing photo on editing; cannot be combined with a new photo.'
example: false
consent:
type: boolean
description: 'Confirm permission for the exact wording, attribution, location and any photo. False or omitted clears consent; drafts without consent cannot be approved.'
example: true
consent_note:
type: string
description: 'Private source/evidence of permission, maximum 1000 characters. Required when consent is true; never public. This field is required when consent is 1 or true. Must not be greater than 1000 characters.'
example: 'Owner approved this wording and public attribution in our customer conversation.'
nullable: true
required:
- feedback
patch:
summary: 'Approve or reject a testimonial.'
operationId: approveOrRejectATestimonial
description: "Approval publishes the story on its matching industry page.\nRejection removes it from public API and web responses while preserving the\nprivate moderation record.\nApproval returns 422 without recorded permission or for internal uxcrafts.com\nbusinesses/owners. Rejection is the reversible Unpublish action."
parameters: []
responses:
200:
description: ''
content:
application/json:
schema:
type: object
example:
data:
id: 53d5597b-4a34-4cf0-9dd3-7a0ae9f64c29
store_type: pharmacy
user_id: 42
business_id: 17
owner_name: 'Dr. Kavitha Reddy'
shop_name: 'Aarogya Medical & General Store'
contact_email: kavitha@example.com
feedback: 'Dukanam made our daily medicine counter billing easier to review.'
photo_url: 'https://dukanam.com/testimonials/53d5597b-4a34-4cf0-9dd3-7a0ae9f64c29/photo'
photo_mime_type: image/webp
photo_size: 98214
status: approved
consent_at: '2026-08-31T10:15:00.000000Z'
review_note: Verified
reviewed_by:
id: 1
name: 'Platform Admin'
reviewed_at: '2026-08-31T10:30:00.000000Z'
published_at: '2026-08-31T10:30:00.000000Z'
submitted_at: '2026-08-31T10:15:00.000000Z'
location: null
source: customer
consent_note: null
properties:
data:
type: object
properties:
id:
type: string
example: 53d5597b-4a34-4cf0-9dd3-7a0ae9f64c29
store_type:
type: string
example: pharmacy
user_id:
type: integer
example: 42
business_id:
type: integer
example: 17
owner_name:
type: string
example: 'Dr. Kavitha Reddy'
shop_name:
type: string
example: 'Aarogya Medical & General Store'
contact_email:
type: string
example: kavitha@example.com
feedback:
type: string
example: 'Dukanam made our daily medicine counter billing easier to review.'
photo_url:
type: string
example: 'https://dukanam.com/testimonials/53d5597b-4a34-4cf0-9dd3-7a0ae9f64c29/photo'
photo_mime_type:
type: string
example: image/webp
photo_size:
type: integer
example: 98214
status:
type: string
example: approved
consent_at:
type: string
example: '2026-08-31T10:15:00.000000Z'
review_note:
type: string
example: Verified
reviewed_by:
type: object
properties:
id:
type: integer
example: 1
name:
type: string
example: 'Platform Admin'
reviewed_at:
type: string
example: '2026-08-31T10:30:00.000000Z'
published_at:
type: string
example: '2026-08-31T10:30:00.000000Z'
submitted_at:
type: string
example: '2026-08-31T10:15:00.000000Z'
location:
type: string
example: null
nullable: true
source:
type: string
example: customer
consent_note:
type: string
example: null
nullable: true
tags:
- 'Testimonial moderation'
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
status:
type: string
description: 'Moderation decision. Must be approved or rejected.'
example: approved
enum:
- approved
- rejected
review_note:
type: string
description: 'Private moderation note. Must not be greater than 1000 characters.'
example: 'Photo and first-hand statement verified.'
nullable: true
required:
- status
parameters:
-
in: path
name: public_id
description: 'Public testimonial UUID.'
example: 53d5597b-4a34-4cf0-9dd3-7a0ae9f64c29
required: true
schema:
type: string
'/api/v1/businesses/{business_id}/feedback':
get:
summary: 'List my private feedback.'
operationId: listMyPrivateFeedback
description: ''
parameters:
-
in: query
name: per_page
description: 'Results per page, from 1 to 24.'
example: 12
required: false
schema:
type: integer
description: 'Results per page, from 1 to 24.'
example: 12
responses:
200:
description: ''
content:
application/json:
schema:
type: object
example:
data:
-
id: 82fdb616-df56-4547-9e09-5f58f4740acd
feedback_type: suggestion
message: 'Please add a daily counter export to the mobile dashboard.'
photo_url: null
status: pending
response: null
submitted_at: '2026-08-31T10:15:00.000000Z'
reviewed_at: null
links: {}
meta:
current_page: 1
per_page: 12
total: 1
properties:
data:
type: array
example:
- id: 82fdb616-df56-4547-9e09-5f58f4740acd
feedback_type: suggestion
message: 'Please add a daily counter export to the mobile dashboard.'
photo_url: null
status: pending
response: null
submitted_at: '2026-08-31T10:15:00.000000Z'
reviewed_at: null
items:
type: object
properties:
id:
type: string
example: 82fdb616-df56-4547-9e09-5f58f4740acd
feedback_type:
type: string
example: suggestion
message:
type: string
example: 'Please add a daily counter export to the mobile dashboard.'
photo_url:
type: string
example: null
nullable: true
status:
type: string
example: pending
response:
type: string
example: null
nullable: true
submitted_at:
type: string
example: '2026-08-31T10:15:00.000000Z'
reviewed_at:
type: string
example: null
nullable: true
links:
type: object
properties: {}
meta:
type: object
properties:
current_page:
type: integer
example: 1
per_page:
type: integer
example: 12
total:
type: integer
example: 1
tags:
- Feedback
post:
summary: 'Submit private feedback.'
operationId: submitPrivateFeedback
description: "Available to every authenticated workspace user, including free plans.\nFeedback is never added to public testimonial pages."
parameters: []
responses:
202:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'Thank you. Your feedback has been sent to the Dukanam team.'
data:
id: 82fdb616-df56-4547-9e09-5f58f4740acd
feedback_type: suggestion
message: 'Please add a daily counter export to the mobile dashboard.'
photo_url: null
status: pending
response: null
submitted_at: '2026-08-31T10:15:00.000000Z'
reviewed_at: null
properties:
message:
type: string
example: 'Thank you. Your feedback has been sent to the Dukanam team.'
data:
type: object
properties:
id:
type: string
example: 82fdb616-df56-4547-9e09-5f58f4740acd
feedback_type:
type: string
example: suggestion
message:
type: string
example: 'Please add a daily counter export to the mobile dashboard.'
photo_url:
type: string
example: null
nullable: true
status:
type: string
example: pending
response:
type: string
example: null
nullable: true
submitted_at:
type: string
example: '2026-08-31T10:15:00.000000Z'
reviewed_at:
type: string
example: null
nullable: true
tags:
- Feedback
requestBody:
required: true
content:
multipart/form-data:
schema:
type: object
properties:
feedback_type:
type: string
description: 'Improvement, complaint or suggestion.'
example: suggestion
enum:
- improvement
- complaint
- suggestion
message:
type: string
description: 'Private product feedback, 20 to 2,000 characters. Must be at least 20 characters. Must not be greater than 2000 characters.'
example: 'Please add an option to export the daily counter summary directly from the mobile dashboard.'
photo:
type: string
format: binary
description: 'Optional JPEG, PNG or WebP screenshot or shop photo, 320×320 to 6000×6000 pixels, maximum 5 MB. Must be an image. Must not be greater than 5120 kilobytes.'
nullable: true
website:
type: string
description: 'Spam-trap field. Omit it or leave it empty.'
example: null
nullable: true
required:
- feedback_type
- message
parameters:
-
in: path
name: business_id
description: 'Workspace ID.'
example: 17
required: true
schema:
type: integer
/api/v1/admin/feedback:
get:
summary: 'List private customer feedback.'
operationId: listPrivateCustomerFeedback
description: ''
parameters:
-
in: query
name: status
description: 'Filter by pending, reviewed or resolved.'
example: pending
required: false
schema:
type: string
description: 'Filter by pending, reviewed or resolved.'
example: pending
-
in: query
name: feedback_type
description: 'Filter by improvement, complaint or suggestion.'
example: complaint
required: false
schema:
type: string
description: 'Filter by improvement, complaint or suggestion.'
example: complaint
-
in: query
name: per_page
description: 'Results per page, from 1 to 50.'
example: 24
required: false
schema:
type: integer
description: 'Results per page, from 1 to 50.'
example: 24
responses:
200:
description: ''
content:
application/json:
schema:
type: object
example:
data:
-
id: 82fdb616-df56-4547-9e09-5f58f4740acd
feedback_type: complaint
message: 'The mobile report did not load after closing the counter.'
photo_url: null
photo_mime_type: null
photo_size: null
status: pending
submitter:
id: 42
name: 'Kavitha Reddy'
email: kavitha@example.com
business:
id: 17
name: 'Aarogya Medical Store'
response: null
reviewed_by: null
submitted_at: '2026-08-31T10:15:00.000000Z'
reviewed_at: null
links: {}
meta:
current_page: 1
per_page: 24
total: 1
properties:
data:
type: array
example:
-
id: 82fdb616-df56-4547-9e09-5f58f4740acd
feedback_type: complaint
message: 'The mobile report did not load after closing the counter.'
photo_url: null
photo_mime_type: null
photo_size: null
status: pending
submitter:
id: 42
name: 'Kavitha Reddy'
email: kavitha@example.com
business:
id: 17
name: 'Aarogya Medical Store'
response: null
reviewed_by: null
submitted_at: '2026-08-31T10:15:00.000000Z'
reviewed_at: null
items:
type: object
properties:
id:
type: string
example: 82fdb616-df56-4547-9e09-5f58f4740acd
feedback_type:
type: string
example: complaint
message:
type: string
example: 'The mobile report did not load after closing the counter.'
photo_url:
type: string
example: null
nullable: true
photo_mime_type:
type: string
example: null
nullable: true
photo_size:
type: string
example: null
nullable: true
status:
type: string
example: pending
submitter:
type: object
properties:
id:
type: integer
example: 42
name:
type: string
example: 'Kavitha Reddy'
email:
type: string
example: kavitha@example.com
business:
type: object
properties:
id:
type: integer
example: 17
name:
type: string
example: 'Aarogya Medical Store'
response:
type: string
example: null
nullable: true
reviewed_by:
type: string
example: null
nullable: true
submitted_at:
type: string
example: '2026-08-31T10:15:00.000000Z'
reviewed_at:
type: string
example: null
nullable: true
links:
type: object
properties: {}
meta:
type: object
properties:
current_page:
type: integer
example: 1
per_page:
type: integer
example: 24
total:
type: integer
example: 1
tags:
- 'Feedback moderation'
requestBody:
required: false
content:
application/json:
schema:
type: object
properties:
status:
type: string
description: ''
example: null
nullable: true
feedback_type:
type: string
description: ''
example: null
nullable: true
per_page:
type: integer
description: 'Must be at least 1. Must not be greater than 50.'
example: 1
nullable: true
'/api/v1/admin/feedback/{public_id}':
patch:
summary: 'Mark customer feedback reviewed or resolved.'
operationId: markCustomerFeedbackReviewedOrResolved
description: ''
parameters: []
responses:
200:
description: ''
content:
application/json:
schema:
type: object
example:
data:
id: 82fdb616-df56-4547-9e09-5f58f4740acd
feedback_type: suggestion
message: 'Please add a mobile daily summary export.'
photo_url: null
photo_mime_type: null
photo_size: null
status: reviewed
submitter:
id: 42
name: 'Kavitha Reddy'
email: kavitha@example.com
business:
id: 17
name: 'Aarogya Medical Store'
response: 'Added to the mobile backlog.'
reviewed_by:
id: 1
name: 'Platform Admin'
submitted_at: '2026-08-31T10:15:00.000000Z'
reviewed_at: '2026-08-31T11:00:00.000000Z'
properties:
data:
type: object
properties:
id:
type: string
example: 82fdb616-df56-4547-9e09-5f58f4740acd
feedback_type:
type: string
example: suggestion
message:
type: string
example: 'Please add a mobile daily summary export.'
photo_url:
type: string
example: null
nullable: true
photo_mime_type:
type: string
example: null
nullable: true
photo_size:
type: string
example: null
nullable: true
status:
type: string
example: reviewed
submitter:
type: object
properties:
id:
type: integer
example: 42
name:
type: string
example: 'Kavitha Reddy'
email:
type: string
example: kavitha@example.com
business:
type: object
properties:
id:
type: integer
example: 17
name:
type: string
example: 'Aarogya Medical Store'
response:
type: string
example: 'Added to the mobile backlog.'
reviewed_by:
type: object
properties:
id:
type: integer
example: 1
name:
type: string
example: 'Platform Admin'
submitted_at:
type: string
example: '2026-08-31T10:15:00.000000Z'
reviewed_at:
type: string
example: '2026-08-31T11:00:00.000000Z'
tags:
- 'Feedback moderation'
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
status:
type: string
description: 'Feedback workflow status. Must be reviewed or resolved.'
example: reviewed
enum:
- reviewed
- resolved
review_note:
type: string
description: 'Private administrator response or handling note. Must not be greater than 1000 characters.'
example: 'Added to the mobile dashboard backlog.'
nullable: true
required:
- status
parameters:
-
in: path
name: public_id
description: 'Public feedback UUID.'
example: 82fdb616-df56-4547-9e09-5f58f4740acd
required: true
schema:
type: string
/api/v1/apple/subscription/verify:
post:
summary: 'Verify an App Store subscription purchase'
operationId: verifyAnAppStoreSubscriptionPurchase
description: "Send either StoreKit's signed transaction JWS or a transaction ID plus its environment. The business owner\nmust pass the `app_account_token` returned by the current-subscription endpoint to StoreKit when purchasing.\nThe plan, dates, and status always come from Apple's signed data."
parameters: []
responses:
200:
description: ''
content:
application/json:
schema:
type: object
example:
success: true
business_id: 1
app_account_token: 2ee48be5-b23e-46e8-b84d-5e21850d42d2
subscription:
provider: apple
plan: business
billing_period: yearly
product_id: com.dukanam.business.yearly
status: active
is_active: true
expires_at: '2027-09-04T10:30:00.000000Z'
auto_renew_status: true
environment: production
properties:
success:
type: boolean
example: true
business_id:
type: integer
example: 1
app_account_token:
type: string
example: 2ee48be5-b23e-46e8-b84d-5e21850d42d2
subscription:
type: object
properties:
provider:
type: string
example: apple
plan:
type: string
example: business
billing_period:
type: string
example: yearly
product_id:
type: string
example: com.dukanam.business.yearly
status:
type: string
example: active
is_active:
type: boolean
example: true
expires_at:
type: string
example: '2027-09-04T10:30:00.000000Z'
auto_renew_status:
type: boolean
example: true
environment:
type: string
example: production
403:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'Only the business owner can manage this subscription.'
properties:
message:
type: string
example: 'Only the business owner can manage this subscription.'
409:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'The Apple transaction is not linked to this Dukanam business.'
properties:
message:
type: string
example: 'The Apple transaction is not linked to this Dukanam business.'
422:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'This Apple product is not supported.'
properties:
message:
type: string
example: 'This Apple product is not supported.'
503:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'Apple subscriptions are not enabled.'
properties:
message:
type: string
example: 'Apple subscriptions are not enabled.'
tags:
- 'Apple subscriptions'
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
business_id:
type: integer
description: 'The business receiving this entitlement.'
example: 1
signedTransactionInfo:
type: string
description: 'StoreKit 2 transaction JWS. Required without transactionId.'
example: eyJhbGciOiJFUzI1NiIsIng1YyI6WyIuLi4iXX0.eyJ0cmFuc2FjdGlvbklkIjoiMjAwMDAwMTIzNDU2Nzg5MCJ9.signature
nullable: true
transactionId:
type: string
description: 'App Store transaction ID. Required without signedTransactionInfo.'
example: '2000001234567890'
nullable: true
environment:
type: string
description: 'Required with transactionId. Must be `sandbox` or `production`.'
example: sandbox
nullable: true
required:
- business_id
/api/v1/apple/subscription/sync:
post:
summary: 'Synchronize an App Store subscription'
operationId: synchronizeAnAppStoreSubscription
description: "Reconciles the linked subscription with the App Store Server API. Use after Restore Purchases or to recover\nfrom a missed server notification."
parameters: []
responses:
403:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'Only the business owner can manage this subscription.'
properties:
message:
type: string
example: 'Only the business owner can manage this subscription.'
422:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'No Apple subscription has been linked to this business yet.'
properties:
message:
type: string
example: 'No Apple subscription has been linked to this business yet.'
503:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'App Store Server API credentials are not configured.'
properties:
message:
type: string
example: 'App Store Server API credentials are not configured.'
tags:
- 'Apple subscriptions'
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
business_id:
type: integer
description: 'The current business.'
example: 1
required:
- business_id
/api/v1/me/subscription:
get:
summary: 'Get the current subscription entitlement'
operationId: getTheCurrentSubscriptionEntitlement
description: "Returns the backend-authoritative plan and the stable `app_account_token` that Flutter must attach to every\nStoreKit purchase for this business. Free Essentials is returned when there is no usable paid entitlement."
parameters:
-
in: query
name: business_id
description: 'The current business.'
example: 1
required: true
schema:
type: integer
description: 'The current business.'
example: 1
responses:
200:
description: ''
content:
application/json:
schema:
type: object
example:
success: true
business_id: 1
app_account_token: 2ee48be5-b23e-46e8-b84d-5e21850d42d2
subscription:
provider: null
plan: free_essentials
billing_period: null
product_id: null
status: active
is_active: true
expires_at: null
auto_renew_status: null
environment: null
properties:
success:
type: boolean
example: true
business_id:
type: integer
example: 1
app_account_token:
type: string
example: 2ee48be5-b23e-46e8-b84d-5e21850d42d2
subscription:
type: object
properties:
provider:
type: string
example: null
nullable: true
plan:
type: string
example: free_essentials
billing_period:
type: string
example: null
nullable: true
product_id:
type: string
example: null
nullable: true
status:
type: string
example: active
is_active:
type: boolean
example: true
expires_at:
type: string
example: null
nullable: true
auto_renew_status:
type: string
example: null
nullable: true
environment:
type: string
example: null
nullable: true
403:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'Only the business owner can manage this subscription.'
properties:
message:
type: string
example: 'Only the business owner can manage this subscription.'
tags:
- 'Apple subscriptions'
/api/v1/apple/notifications:
post:
summary: 'Receive App Store Server Notifications V2'
operationId: receiveAppStoreServerNotificationsV2
description: "Public Apple webhook. Every payload and nested transaction is cryptographically verified and processed\nidempotently by notification UUID. This endpoint does not use Dukanam bearer authentication."
parameters: []
responses:
200:
description: ''
content:
application/json:
schema:
type: object
example:
success: true
properties:
success:
type: boolean
example: true
400:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'Apple could not verify the signed notification.'
properties:
message:
type: string
example: 'Apple could not verify the signed notification.'
tags:
- 'Apple subscriptions'
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
signedPayload:
type: string
description: "Apple's App Store Server Notifications V2 JWS."
example: eyJhbGciOiJFUzI1NiIsIng1YyI6WyIuLi4iXX0.eyJub3RpZmljYXRpb25UeXBlIjoiVEVTVCJ9.signature
required:
- signedPayload
security: []
/api/v1/account/deletion:
get:
summary: 'Show the deletion status and what a deletion would destroy.'
operationId: showTheDeletionStatusAndWhatADeletionWouldDestroy
description: "`owned_workspaces` are erased entirely, including every team member's access.\n`joined_workspaces` survive; only this user's membership in them is removed.\nApple billing is not cancelled by deletion. Display the warning and management URL\nwhen present; active, trialing and billing-retry Apple records trigger the warning."
parameters: []
responses:
200:
description: ''
content:
application/json:
schema:
type: object
example:
data:
pending: false
grace_days: 7
scheduled_for: null
owned_workspaces:
-
name: 'Sri Balaji Stores'
team_members: 2
has_billing_records: true
has_apple_subscription: true
joined_workspaces:
- 'Anand Traders'
apple_subscription_warning: 'Deleting your Dukanam account does not cancel your Apple subscription. Apple may continue billing you until you cancel it in your Apple subscription settings.'
apple_subscription_management_url: 'https://apps.apple.com/account/subscriptions'
properties:
data:
type: object
properties:
pending:
type: boolean
example: false
grace_days:
type: integer
example: 7
scheduled_for:
type: string
example: null
nullable: true
owned_workspaces:
type: array
example:
- name: 'Sri Balaji Stores'
team_members: 2
has_billing_records: true
has_apple_subscription: true
items:
type: object
properties:
name:
type: string
example: 'Sri Balaji Stores'
team_members:
type: integer
example: 2
has_billing_records:
type: boolean
example: true
has_apple_subscription:
type: boolean
example: true
joined_workspaces:
type: array
example:
- 'Anand Traders'
items:
type: string
apple_subscription_warning:
type: string
example: 'Deleting your Dukanam account does not cancel your Apple subscription. Apple may continue billing you until you cancel it in your Apple subscription settings.'
apple_subscription_management_url:
type: string
example: 'https://apps.apple.com/account/subscriptions'
tags:
- 'Account deletion'
post:
summary: 'Request deletion of the account and its data.'
operationId: requestDeletionOfTheAccountAndItsData
description: "The account stays usable during the grace period so the request can be\ncancelled. Push notifications stop immediately.\nThe confirmation email warns owners of outstanding Apple subscriptions and links\nto Apple's subscription settings. Deletion never cancels Apple billing."
parameters: []
responses:
202:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'Your account is scheduled for deletion.'
data:
scheduled_for: '2026-09-06T10:15:00+00:00'
properties:
message:
type: string
example: 'Your account is scheduled for deletion.'
data:
type: object
properties:
scheduled_for:
type: string
example: '2026-09-06T10:15:00+00:00'
403:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'Administrator accounts cannot be deleted from the app. Contact support.'
properties:
message:
type: string
example: 'Administrator accounts cannot be deleted from the app. Contact support.'
422:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'The given data was invalid.'
errors:
confirmation:
- 'Type DELETE to confirm that you want the account erased.'
properties:
message:
type: string
example: 'The given data was invalid.'
errors:
type: object
properties:
confirmation:
type: array
example:
- 'Type DELETE to confirm that you want the account erased.'
items:
type: string
tags:
- 'Account deletion'
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
current_password:
type: string
description: 'The signed-in account password.'
example: correct-horse-battery
confirmation:
type: string
description: 'Must be the word DELETE.'
example: DELETE
reason:
type: string
description: 'An optional reason, kept only until the purge runs.'
example: 'Closing the shop'
nullable: true
required:
- current_password
- confirmation
delete:
summary: 'Cancel a pending deletion request.'
operationId: cancelAPendingDeletionRequest
description: ''
parameters: []
responses:
200:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'Account deletion cancelled.'
properties:
message:
type: string
example: 'Account deletion cancelled.'
tags:
- 'Account deletion'
/api/v1/admin/app-link/analytics:
get:
summary: 'Summarise app link opens.'
operationId: summariseAppLinkOpens
description: "Totals, store-page engagement, per-page rows, a zero-filled daily series, a weekday × hour\ngrid (ISO weekday 1 = Monday, hours 0–23), top-15 breakdowns by source, medium, campaign, platform, device, operating\nsystem, browser, in-app browser, referrer, country, region, language, page language,\ncall to action and screen size, the top 50\ncities with approximate coordinates, and link-preview fetches. `previous_period` covers\nthe same number of days immediately before `from`, for comparison.\n\nSource keys: whatsapp, instagram, facebook, meta (a Meta ad click whose app is unknown),\nqr, sms, google, youtube, x, linkedin, telegram, email, referral (another website),\ninternal (a link on dukanam.com) and direct (untagged with no referrer). Any other `utm_source` value is kept as sent, normalised.\nOutcomes: play_store and app_store are store redirects; landing_page means the page was shown."
parameters:
-
in: query
name: from
description: 'First calendar day, YYYY-MM-DD. Defaults to 29 days before `to`. Must be a valid date in the format Y-m-d.'
example: '2026-01-15'
required: false
schema:
type: string
description: 'First calendar day, YYYY-MM-DD. Defaults to 29 days before `to`. Must be a valid date in the format Y-m-d.'
example: '2026-01-15'
nullable: true
-
in: query
name: to
description: 'Last calendar day, YYYY-MM-DD. Defaults to today. The range may cover at most 366 days. Must be a valid date in the format Y-m-d.'
example: '2026-01-15'
required: false
schema:
type: string
description: 'Last calendar day, YYYY-MM-DD. Defaults to today. The range may cover at most 366 days. Must be a valid date in the format Y-m-d.'
example: '2026-01-15'
nullable: true
-
in: query
name: source
description: 'Only opens from this source key, such as whatsapp, instagram, qr, sms, facebook, meta, google, referral or direct. Must not be greater than 50 characters.'
example: whatsapp
required: false
schema:
type: string
description: 'Only opens from this source key, such as whatsapp, instagram, qr, sms, facebook, meta, google, referral or direct. Must not be greater than 50 characters.'
example: whatsapp
nullable: true
-
in: query
name: campaign
description: 'Only opens tagged with this campaign, as normalised (lower case, underscores). Must not be greater than 120 characters.'
example: diwali_2026
required: false
schema:
type: string
description: 'Only opens tagged with this campaign, as normalised (lower case, underscores). Must not be greater than 120 characters.'
example: diwali_2026
nullable: true
-
in: query
name: platform
description: 'Only opens from android, ios, desktop or other.'
example: android
required: false
schema:
type: string
description: 'Only opens from android, ios, desktop or other.'
example: android
enum:
- android
- ios
- desktop
- other
nullable: true
-
in: query
name: tracked_page
description: 'Only opens of this page: app (the /app download link), stores (the store-type index), or a store-type key such as pharmacy or kirana-store.'
example: app
required: false
schema:
type: string
description: 'Only opens of this page: app (the /app download link), stores (the store-type index), or a store-type key such as pharmacy or kirana-store.'
example: app
enum:
- app
- stores
- kirana-store
- pharmacy
- supermarket
- clothing-store
- footwear-store
- hardware-store
- electronics-store
- mobile-store
- stationery-store
- book-store
- bakery
- cosmetics-store
- wholesale-business
- auto-parts-store
- home-appliance-store
- gift-shop
- electrical-store
- paint-store
- sanitaryware-store
- pesticide-store
nullable: true
-
in: query
name: per_page
description: 'Visits per page, 1 to 100. Visit lists only. Defaults to 25. Must be at least 1. Must not be greater than 100.'
example: 25
required: false
schema:
type: integer
description: 'Visits per page, 1 to 100. Visit lists only. Defaults to 25. Must be at least 1. Must not be greater than 100.'
example: 25
nullable: true
-
in: query
name: page
description: 'Page number. Visit lists only. Must be at least 1.'
example: 1
required: false
schema:
type: integer
description: 'Page number. Visit lists only. Must be at least 1.'
example: 1
nullable: true
responses:
200:
description: ''
content:
application/json:
schema:
type: object
example:
data:
range:
from: '2026-09-20'
to: '2026-09-26'
days: 7
filters:
source: null
campaign: null
platform: null
tracked_page: null
totals:
opens: 5
unique_visitors: 5
new_visitors: 4
returning_visitors: 1
store_redirects: 3
play_store: 2
app_store: 1
landing_page: 2
link_previews: 1
engagement:
page_views: 2
average_seconds: 100
average_scroll_depth: 80
call_to_action_opens: 2
call_to_action_rate: 100
signup_or_app_opens: 1
previous_period:
from: '2026-09-13'
to: '2026-09-19'
opens: 0
unique_visitors: 0
store_redirects: 0
daily:
-
date: '2026-09-20'
opens: 0
unique_visitors: 0
play_store: 0
app_store: 0
landing_page: 0
-
date: '2026-09-21'
opens: 0
unique_visitors: 0
play_store: 0
app_store: 0
landing_page: 0
-
date: '2026-09-22'
opens: 0
unique_visitors: 0
play_store: 0
app_store: 0
landing_page: 0
-
date: '2026-09-23'
opens: 1
unique_visitors: 1
play_store: 1
app_store: 0
landing_page: 0
-
date: '2026-09-24'
opens: 1
unique_visitors: 1
play_store: 1
app_store: 0
landing_page: 0
-
date: '2026-09-25'
opens: 2
unique_visitors: 2
play_store: 0
app_store: 1
landing_page: 1
-
date: '2026-09-26'
opens: 1
unique_visitors: 1
play_store: 0
app_store: 0
landing_page: 1
weekday_hours:
-
weekday: 1
hours:
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
-
weekday: 2
hours:
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
-
weekday: 3
hours:
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 1
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
-
weekday: 4
hours:
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 1
- 0
- 0
- 0
- 0
-
weekday: 5
hours:
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 1
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 1
- 0
- 0
-
weekday: 6
hours:
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 1
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
-
weekday: 7
hours:
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
pages:
-
key: app
label: 'App download link (/app)'
opens: 3
unique_visitors: 3
play_store: 2
app_store: 1
landing_page: 0
average_seconds: null
average_scroll_depth: null
call_to_action_opens: 0
signup_clicks: 0
-
key: stores
label: 'All store types'
opens: 1
unique_visitors: 1
play_store: 0
app_store: 0
landing_page: 1
average_seconds: 74
average_scroll_depth: 88
call_to_action_opens: 1
signup_clicks: 0
-
key: pharmacy
label: Pharmacies
opens: 1
unique_visitors: 1
play_store: 0
app_store: 0
landing_page: 1
average_seconds: 126
average_scroll_depth: 71
call_to_action_opens: 1
signup_clicks: 1
breakdowns:
sources:
-
key: instagram
label: Instagram
opens: 2
unique_visitors: 2
play_store: 1
app_store: 0
landing_page: 1
-
key: whatsapp
label: WhatsApp
opens: 1
unique_visitors: 1
play_store: 1
app_store: 0
landing_page: 0
-
key: qr
label: 'QR code'
opens: 1
unique_visitors: 1
play_store: 0
app_store: 1
landing_page: 0
-
key: google
label: Google
opens: 1
unique_visitors: 1
play_store: 0
app_store: 0
landing_page: 1
mediums:
-
key: paid
label: paid
opens: 2
unique_visitors: 2
play_store: 1
app_store: 0
landing_page: 1
-
key: social
label: social
opens: 1
unique_visitors: 1
play_store: 1
app_store: 0
landing_page: 0
-
key: organic
label: organic
opens: 1
unique_visitors: 1
play_store: 0
app_store: 0
landing_page: 1
-
key: offline
label: offline
opens: 1
unique_visitors: 1
play_store: 0
app_store: 1
landing_page: 0
campaigns:
-
key: diwali_2026
label: diwali_2026
opens: 2
unique_visitors: 2
play_store: 2
app_store: 0
landing_page: 0
-
key: poster_vijayawada
label: poster_vijayawada
opens: 1
unique_visitors: 1
play_store: 0
app_store: 1
landing_page: 0
-
key: pharmacy_launch
label: pharmacy_launch
opens: 1
unique_visitors: 1
play_store: 0
app_store: 0
landing_page: 1
-
key: null
label: 'Not tagged'
opens: 1
unique_visitors: 1
play_store: 0
app_store: 0
landing_page: 1
platforms:
-
key: android
label: Android
opens: 3
unique_visitors: 3
play_store: 2
app_store: 0
landing_page: 1
-
key: ios
label: 'iPhone / iPad'
opens: 1
unique_visitors: 1
play_store: 0
app_store: 1
landing_page: 0
-
key: desktop
label: Desktop
opens: 1
unique_visitors: 1
play_store: 0
app_store: 0
landing_page: 1
device_types:
-
key: smartphone
label: Smartphone
opens: 4
unique_visitors: 4
play_store: 2
app_store: 1
landing_page: 1
-
key: desktop
label: Desktop
opens: 1
unique_visitors: 1
play_store: 0
app_store: 0
landing_page: 1
device_brands:
-
key: Realme
label: Realme
opens: 2
unique_visitors: 2
play_store: 1
app_store: 0
landing_page: 1
-
key: Samsung
label: Samsung
opens: 1
unique_visitors: 1
play_store: 1
app_store: 0
landing_page: 0
-
key: Apple
label: Apple
opens: 1
unique_visitors: 1
play_store: 0
app_store: 1
landing_page: 0
device_models:
-
key: 'Realme · C55'
label: 'Realme C55'
opens: 2
unique_visitors: 2
play_store: 1
app_store: 0
landing_page: 1
-
key: null
label: Unknown
opens: 1
unique_visitors: 1
play_store: 0
app_store: 0
landing_page: 1
-
key: 'Apple · iPhone'
label: 'Apple iPhone'
opens: 1
unique_visitors: 1
play_store: 0
app_store: 1
landing_page: 0
-
key: 'Samsung · Galaxy A15'
label: 'Samsung Galaxy A15'
opens: 1
unique_visitors: 1
play_store: 1
app_store: 0
landing_page: 0
operating_systems:
-
key: 'Android · 13'
label: 'Android 13'
opens: 2
unique_visitors: 2
play_store: 1
app_store: 0
landing_page: 1
-
key: 'Android · 14'
label: 'Android 14'
opens: 1
unique_visitors: 1
play_store: 1
app_store: 0
landing_page: 0
-
key: 'Windows · 10'
label: 'Windows 10'
opens: 1
unique_visitors: 1
play_store: 0
app_store: 0
landing_page: 1
-
key: 'iOS · 17.4'
label: 'iOS 17.4'
opens: 1
unique_visitors: 1
play_store: 0
app_store: 1
landing_page: 0
browsers:
-
key: 'Mobile Safari'
label: 'Mobile Safari'
opens: 1
unique_visitors: 1
play_store: 0
app_store: 1
landing_page: 0
-
key: Chromium
label: Chromium
opens: 1
unique_visitors: 1
play_store: 1
app_store: 0
landing_page: 0
-
key: 'Chrome Mobile'
label: 'Chrome Mobile'
opens: 1
unique_visitors: 1
play_store: 0
app_store: 0
landing_page: 1
-
key: Chrome
label: Chrome
opens: 1
unique_visitors: 1
play_store: 0
app_store: 0
landing_page: 1
in_app_browsers:
-
key: Instagram
label: Instagram
opens: 1
unique_visitors: 1
play_store: 1
app_store: 0
landing_page: 0
referrers:
-
key: l.instagram.com
label: l.instagram.com
opens: 1
unique_visitors: 1
play_store: 0
app_store: 0
landing_page: 1
-
key: www.google.com
label: www.google.com
opens: 1
unique_visitors: 1
play_store: 0
app_store: 0
landing_page: 1
countries:
-
key: IN
label: India
opens: 5
unique_visitors: 5
play_store: 2
app_store: 1
landing_page: 2
regions:
-
key: 'IN · Andhra Pradesh'
label: 'Andhra Pradesh, IN'
opens: 3
unique_visitors: 3
play_store: 1
app_store: 1
landing_page: 1
-
key: 'IN · Karnataka'
label: 'Karnataka, IN'
opens: 1
unique_visitors: 1
play_store: 0
app_store: 0
landing_page: 1
-
key: 'IN · Telangana'
label: 'Telangana, IN'
opens: 1
unique_visitors: 1
play_store: 1
app_store: 0
landing_page: 0
cities:
-
key: 'IN · Andhra Pradesh · Vijayawada'
label: Vijayawada
region: 'Andhra Pradesh'
country_code: IN
opens: 2
unique_visitors: 2
latitude: 16.5062
longitude: 80.648
-
key: 'IN · Karnataka · Bengaluru'
label: Bengaluru
region: Karnataka
country_code: IN
opens: 1
unique_visitors: 1
latitude: 12.9716
longitude: 77.5946
-
key: 'IN · Andhra Pradesh · Guntur'
label: Guntur
region: 'Andhra Pradesh'
country_code: IN
opens: 1
unique_visitors: 1
latitude: 16.3067
longitude: 80.4365
-
key: 'IN · Telangana · Hyderabad'
label: Hyderabad
region: Telangana
country_code: IN
opens: 1
unique_visitors: 1
latitude: 17.385
longitude: 78.4867
languages:
-
key: te-IN
label: te-IN
opens: 5
unique_visitors: 5
play_store: 2
app_store: 1
landing_page: 2
page_languages:
-
key: te
label: తెలుగు
opens: 1
unique_visitors: 1
play_store: 0
app_store: 0
landing_page: 1
calls_to_action:
-
key: signup
label: 'Start free / sign up'
opens: 1
unique_visitors: 1
play_store: 0
app_store: 0
landing_page: 1
-
key: pricing
label: Pricing
opens: 1
unique_visitors: 1
play_store: 0
app_store: 0
landing_page: 1
screen_sizes:
-
key: '412 · 915'
label: '412 × 915'
opens: 1
unique_visitors: 1
play_store: 0
app_store: 0
landing_page: 1
-
key: '1920 · 1080'
label: '1920 × 1080'
opens: 1
unique_visitors: 1
play_store: 0
app_store: 0
landing_page: 1
link_previews:
-
label: WhatsApp
fetches: 1
properties:
data:
type: object
properties:
range:
type: object
properties:
from:
type: string
example: '2026-09-20'
to:
type: string
example: '2026-09-26'
days:
type: integer
example: 7
filters:
type: object
properties:
source:
type: string
example: null
nullable: true
campaign:
type: string
example: null
nullable: true
platform:
type: string
example: null
nullable: true
tracked_page:
type: string
example: null
nullable: true
totals:
type: object
properties:
opens:
type: integer
example: 5
unique_visitors:
type: integer
example: 5
new_visitors:
type: integer
example: 4
returning_visitors:
type: integer
example: 1
store_redirects:
type: integer
example: 3
play_store:
type: integer
example: 2
app_store:
type: integer
example: 1
landing_page:
type: integer
example: 2
link_previews:
type: integer
example: 1
engagement:
type: object
properties:
page_views:
type: integer
example: 2
average_seconds:
type: integer
example: 100
average_scroll_depth:
type: integer
example: 80
call_to_action_opens:
type: integer
example: 2
call_to_action_rate:
type: integer
example: 100
signup_or_app_opens:
type: integer
example: 1
previous_period:
type: object
properties:
from:
type: string
example: '2026-09-13'
to:
type: string
example: '2026-09-19'
opens:
type: integer
example: 0
unique_visitors:
type: integer
example: 0
store_redirects:
type: integer
example: 0
daily:
type: array
example:
- date: '2026-09-20'
opens: 0
unique_visitors: 0
play_store: 0
app_store: 0
landing_page: 0
- date: '2026-09-21'
opens: 0
unique_visitors: 0
play_store: 0
app_store: 0
landing_page: 0
- date: '2026-09-22'
opens: 0
unique_visitors: 0
play_store: 0
app_store: 0
landing_page: 0
- date: '2026-09-23'
opens: 1
unique_visitors: 1
play_store: 1
app_store: 0
landing_page: 0
- date: '2026-09-24'
opens: 1
unique_visitors: 1
play_store: 1
app_store: 0
landing_page: 0
- date: '2026-09-25'
opens: 2
unique_visitors: 2
play_store: 0
app_store: 1
landing_page: 1
- date: '2026-09-26'
opens: 1
unique_visitors: 1
play_store: 0
app_store: 0
landing_page: 1
items:
type: object
properties:
date:
type: string
example: '2026-09-20'
opens:
type: integer
example: 0
unique_visitors:
type: integer
example: 0
play_store:
type: integer
example: 0
app_store:
type: integer
example: 0
landing_page:
type: integer
example: 0
weekday_hours:
type: array
example:
-
weekday: 1
hours:
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
-
weekday: 2
hours:
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
-
weekday: 3
hours:
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 1
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
-
weekday: 4
hours:
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 1
- 0
- 0
- 0
- 0
-
weekday: 5
hours:
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 1
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 1
- 0
- 0
-
weekday: 6
hours:
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 1
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
-
weekday: 7
hours:
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
items:
type: object
properties:
weekday:
type: integer
example: 1
hours:
type: array
example:
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
items:
type: integer
pages:
type: array
example:
- key: app
label: 'App download link (/app)'
opens: 3
unique_visitors: 3
play_store: 2
app_store: 1
landing_page: 0
average_seconds: null
average_scroll_depth: null
call_to_action_opens: 0
signup_clicks: 0
- key: stores
label: 'All store types'
opens: 1
unique_visitors: 1
play_store: 0
app_store: 0
landing_page: 1
average_seconds: 74
average_scroll_depth: 88
call_to_action_opens: 1
signup_clicks: 0
- key: pharmacy
label: Pharmacies
opens: 1
unique_visitors: 1
play_store: 0
app_store: 0
landing_page: 1
average_seconds: 126
average_scroll_depth: 71
call_to_action_opens: 1
signup_clicks: 1
items:
type: object
properties:
key:
type: string
example: app
label:
type: string
example: 'App download link (/app)'
opens:
type: integer
example: 3
unique_visitors:
type: integer
example: 3
play_store:
type: integer
example: 2
app_store:
type: integer
example: 1
landing_page:
type: integer
example: 0
average_seconds:
type: string
example: null
nullable: true
average_scroll_depth:
type: string
example: null
nullable: true
call_to_action_opens:
type: integer
example: 0
signup_clicks:
type: integer
example: 0
breakdowns:
type: object
properties:
sources:
type: array
example:
- key: instagram
label: Instagram
opens: 2
unique_visitors: 2
play_store: 1
app_store: 0
landing_page: 1
- key: whatsapp
label: WhatsApp
opens: 1
unique_visitors: 1
play_store: 1
app_store: 0
landing_page: 0
- key: qr
label: 'QR code'
opens: 1
unique_visitors: 1
play_store: 0
app_store: 1
landing_page: 0
- key: google
label: Google
opens: 1
unique_visitors: 1
play_store: 0
app_store: 0
landing_page: 1
items:
type: object
properties:
key:
type: string
example: instagram
label:
type: string
example: Instagram
opens:
type: integer
example: 2
unique_visitors:
type: integer
example: 2
play_store:
type: integer
example: 1
app_store:
type: integer
example: 0
landing_page:
type: integer
example: 1
mediums:
type: array
example:
- key: paid
label: paid
opens: 2
unique_visitors: 2
play_store: 1
app_store: 0
landing_page: 1
- key: social
label: social
opens: 1
unique_visitors: 1
play_store: 1
app_store: 0
landing_page: 0
- key: organic
label: organic
opens: 1
unique_visitors: 1
play_store: 0
app_store: 0
landing_page: 1
- key: offline
label: offline
opens: 1
unique_visitors: 1
play_store: 0
app_store: 1
landing_page: 0
items:
type: object
properties:
key:
type: string
example: paid
label:
type: string
example: paid
opens:
type: integer
example: 2
unique_visitors:
type: integer
example: 2
play_store:
type: integer
example: 1
app_store:
type: integer
example: 0
landing_page:
type: integer
example: 1
campaigns:
type: array
example:
- key: diwali_2026
label: diwali_2026
opens: 2
unique_visitors: 2
play_store: 2
app_store: 0
landing_page: 0
- key: poster_vijayawada
label: poster_vijayawada
opens: 1
unique_visitors: 1
play_store: 0
app_store: 1
landing_page: 0
- key: pharmacy_launch
label: pharmacy_launch
opens: 1
unique_visitors: 1
play_store: 0
app_store: 0
landing_page: 1
- key: null
label: 'Not tagged'
opens: 1
unique_visitors: 1
play_store: 0
app_store: 0
landing_page: 1
items:
type: object
properties:
key:
type: string
example: diwali_2026
label:
type: string
example: diwali_2026
opens:
type: integer
example: 2
unique_visitors:
type: integer
example: 2
play_store:
type: integer
example: 2
app_store:
type: integer
example: 0
landing_page:
type: integer
example: 0
platforms:
type: array
example:
- key: android
label: Android
opens: 3
unique_visitors: 3
play_store: 2
app_store: 0
landing_page: 1
- key: ios
label: 'iPhone / iPad'
opens: 1
unique_visitors: 1
play_store: 0
app_store: 1
landing_page: 0
- key: desktop
label: Desktop
opens: 1
unique_visitors: 1
play_store: 0
app_store: 0
landing_page: 1
items:
type: object
properties:
key:
type: string
example: android
label:
type: string
example: Android
opens:
type: integer
example: 3
unique_visitors:
type: integer
example: 3
play_store:
type: integer
example: 2
app_store:
type: integer
example: 0
landing_page:
type: integer
example: 1
device_types:
type: array
example:
- key: smartphone
label: Smartphone
opens: 4
unique_visitors: 4
play_store: 2
app_store: 1
landing_page: 1
- key: desktop
label: Desktop
opens: 1
unique_visitors: 1
play_store: 0
app_store: 0
landing_page: 1
items:
type: object
properties:
key:
type: string
example: smartphone
label:
type: string
example: Smartphone
opens:
type: integer
example: 4
unique_visitors:
type: integer
example: 4
play_store:
type: integer
example: 2
app_store:
type: integer
example: 1
landing_page:
type: integer
example: 1
device_brands:
type: array
example:
- key: Realme
label: Realme
opens: 2
unique_visitors: 2
play_store: 1
app_store: 0
landing_page: 1
- key: Samsung
label: Samsung
opens: 1
unique_visitors: 1
play_store: 1
app_store: 0
landing_page: 0
- key: Apple
label: Apple
opens: 1
unique_visitors: 1
play_store: 0
app_store: 1
landing_page: 0
items:
type: object
properties:
key:
type: string
example: Realme
label:
type: string
example: Realme
opens:
type: integer
example: 2
unique_visitors:
type: integer
example: 2
play_store:
type: integer
example: 1
app_store:
type: integer
example: 0
landing_page:
type: integer
example: 1
device_models:
type: array
example:
- key: 'Realme · C55'
label: 'Realme C55'
opens: 2
unique_visitors: 2
play_store: 1
app_store: 0
landing_page: 1
- key: null
label: Unknown
opens: 1
unique_visitors: 1
play_store: 0
app_store: 0
landing_page: 1
- key: 'Apple · iPhone'
label: 'Apple iPhone'
opens: 1
unique_visitors: 1
play_store: 0
app_store: 1
landing_page: 0
- key: 'Samsung · Galaxy A15'
label: 'Samsung Galaxy A15'
opens: 1
unique_visitors: 1
play_store: 1
app_store: 0
landing_page: 0
items:
type: object
properties:
key:
type: string
example: 'Realme · C55'
label:
type: string
example: 'Realme C55'
opens:
type: integer
example: 2
unique_visitors:
type: integer
example: 2
play_store:
type: integer
example: 1
app_store:
type: integer
example: 0
landing_page:
type: integer
example: 1
operating_systems:
type: array
example:
- key: 'Android · 13'
label: 'Android 13'
opens: 2
unique_visitors: 2
play_store: 1
app_store: 0
landing_page: 1
- key: 'Android · 14'
label: 'Android 14'
opens: 1
unique_visitors: 1
play_store: 1
app_store: 0
landing_page: 0
- key: 'Windows · 10'
label: 'Windows 10'
opens: 1
unique_visitors: 1
play_store: 0
app_store: 0
landing_page: 1
- key: 'iOS · 17.4'
label: 'iOS 17.4'
opens: 1
unique_visitors: 1
play_store: 0
app_store: 1
landing_page: 0
items:
type: object
properties:
key:
type: string
example: 'Android · 13'
label:
type: string
example: 'Android 13'
opens:
type: integer
example: 2
unique_visitors:
type: integer
example: 2
play_store:
type: integer
example: 1
app_store:
type: integer
example: 0
landing_page:
type: integer
example: 1
browsers:
type: array
example:
- key: 'Mobile Safari'
label: 'Mobile Safari'
opens: 1
unique_visitors: 1
play_store: 0
app_store: 1
landing_page: 0
- key: Chromium
label: Chromium
opens: 1
unique_visitors: 1
play_store: 1
app_store: 0
landing_page: 0
- key: 'Chrome Mobile'
label: 'Chrome Mobile'
opens: 1
unique_visitors: 1
play_store: 0
app_store: 0
landing_page: 1
- key: Chrome
label: Chrome
opens: 1
unique_visitors: 1
play_store: 0
app_store: 0
landing_page: 1
items:
type: object
properties:
key:
type: string
example: 'Mobile Safari'
label:
type: string
example: 'Mobile Safari'
opens:
type: integer
example: 1
unique_visitors:
type: integer
example: 1
play_store:
type: integer
example: 0
app_store:
type: integer
example: 1
landing_page:
type: integer
example: 0
in_app_browsers:
type: array
example:
- key: Instagram
label: Instagram
opens: 1
unique_visitors: 1
play_store: 1
app_store: 0
landing_page: 0
items:
type: object
properties:
key:
type: string
example: Instagram
label:
type: string
example: Instagram
opens:
type: integer
example: 1
unique_visitors:
type: integer
example: 1
play_store:
type: integer
example: 1
app_store:
type: integer
example: 0
landing_page:
type: integer
example: 0
referrers:
type: array
example:
- key: l.instagram.com
label: l.instagram.com
opens: 1
unique_visitors: 1
play_store: 0
app_store: 0
landing_page: 1
- key: www.google.com
label: www.google.com
opens: 1
unique_visitors: 1
play_store: 0
app_store: 0
landing_page: 1
items:
type: object
properties:
key:
type: string
example: l.instagram.com
label:
type: string
example: l.instagram.com
opens:
type: integer
example: 1
unique_visitors:
type: integer
example: 1
play_store:
type: integer
example: 0
app_store:
type: integer
example: 0
landing_page:
type: integer
example: 1
countries:
type: array
example:
- key: IN
label: India
opens: 5
unique_visitors: 5
play_store: 2
app_store: 1
landing_page: 2
items:
type: object
properties:
key:
type: string
example: IN
label:
type: string
example: India
opens:
type: integer
example: 5
unique_visitors:
type: integer
example: 5
play_store:
type: integer
example: 2
app_store:
type: integer
example: 1
landing_page:
type: integer
example: 2
regions:
type: array
example:
- key: 'IN · Andhra Pradesh'
label: 'Andhra Pradesh, IN'
opens: 3
unique_visitors: 3
play_store: 1
app_store: 1
landing_page: 1
- key: 'IN · Karnataka'
label: 'Karnataka, IN'
opens: 1
unique_visitors: 1
play_store: 0
app_store: 0
landing_page: 1
- key: 'IN · Telangana'
label: 'Telangana, IN'
opens: 1
unique_visitors: 1
play_store: 1
app_store: 0
landing_page: 0
items:
type: object
properties:
key:
type: string
example: 'IN · Andhra Pradesh'
label:
type: string
example: 'Andhra Pradesh, IN'
opens:
type: integer
example: 3
unique_visitors:
type: integer
example: 3
play_store:
type: integer
example: 1
app_store:
type: integer
example: 1
landing_page:
type: integer
example: 1
cities:
type: array
example:
- key: 'IN · Andhra Pradesh · Vijayawada'
label: Vijayawada
region: 'Andhra Pradesh'
country_code: IN
opens: 2
unique_visitors: 2
latitude: 16.5062
longitude: 80.648
- key: 'IN · Karnataka · Bengaluru'
label: Bengaluru
region: Karnataka
country_code: IN
opens: 1
unique_visitors: 1
latitude: 12.9716
longitude: 77.5946
- key: 'IN · Andhra Pradesh · Guntur'
label: Guntur
region: 'Andhra Pradesh'
country_code: IN
opens: 1
unique_visitors: 1
latitude: 16.3067
longitude: 80.4365
- key: 'IN · Telangana · Hyderabad'
label: Hyderabad
region: Telangana
country_code: IN
opens: 1
unique_visitors: 1
latitude: 17.385
longitude: 78.4867
items:
type: object
properties:
key:
type: string
example: 'IN · Andhra Pradesh · Vijayawada'
label:
type: string
example: Vijayawada
region:
type: string
example: 'Andhra Pradesh'
country_code:
type: string
example: IN
opens:
type: integer
example: 2
unique_visitors:
type: integer
example: 2
latitude:
type: number
example: 16.5062
longitude:
type: number
example: 80.648
languages:
type: array
example:
- key: te-IN
label: te-IN
opens: 5
unique_visitors: 5
play_store: 2
app_store: 1
landing_page: 2
items:
type: object
properties:
key:
type: string
example: te-IN
label:
type: string
example: te-IN
opens:
type: integer
example: 5
unique_visitors:
type: integer
example: 5
play_store:
type: integer
example: 2
app_store:
type: integer
example: 1
landing_page:
type: integer
example: 2
page_languages:
type: array
example:
- key: te
label: తెలుగు
opens: 1
unique_visitors: 1
play_store: 0
app_store: 0
landing_page: 1
items:
type: object
properties:
key:
type: string
example: te
label:
type: string
example: తెలుగు
opens:
type: integer
example: 1
unique_visitors:
type: integer
example: 1
play_store:
type: integer
example: 0
app_store:
type: integer
example: 0
landing_page:
type: integer
example: 1
calls_to_action:
type: array
example:
- key: signup
label: 'Start free / sign up'
opens: 1
unique_visitors: 1
play_store: 0
app_store: 0
landing_page: 1
- key: pricing
label: Pricing
opens: 1
unique_visitors: 1
play_store: 0
app_store: 0
landing_page: 1
items:
type: object
properties:
key:
type: string
example: signup
label:
type: string
example: 'Start free / sign up'
opens:
type: integer
example: 1
unique_visitors:
type: integer
example: 1
play_store:
type: integer
example: 0
app_store:
type: integer
example: 0
landing_page:
type: integer
example: 1
screen_sizes:
type: array
example:
- key: '412 · 915'
label: '412 × 915'
opens: 1
unique_visitors: 1
play_store: 0
app_store: 0
landing_page: 1
- key: '1920 · 1080'
label: '1920 × 1080'
opens: 1
unique_visitors: 1
play_store: 0
app_store: 0
landing_page: 1
items:
type: object
properties:
key:
type: string
example: '412 · 915'
label:
type: string
example: '412 × 915'
opens:
type: integer
example: 1
unique_visitors:
type: integer
example: 1
play_store:
type: integer
example: 0
app_store:
type: integer
example: 0
landing_page:
type: integer
example: 1
link_previews:
type: array
example:
- label: WhatsApp
fetches: 1
items:
type: object
properties:
label:
type: string
example: WhatsApp
fetches:
type: integer
example: 1
401:
description: ''
content:
application/json:
schema:
type: object
example:
message: Unauthenticated.
properties:
message:
type: string
example: Unauthenticated.
403:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'This action is unauthorized.'
properties:
message:
type: string
example: 'This action is unauthorized.'
422:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'Choose a range of at most 366 days.'
errors:
from:
- 'Choose a range of at most 366 days.'
properties:
message:
type: string
example: 'Choose a range of at most 366 days.'
errors:
type: object
properties:
from:
type: array
example:
- 'Choose a range of at most 366 days.'
items:
type: string
tags:
- 'App link analytics'
/api/v1/admin/app-link/visits:
get:
summary: 'List individual app link opens.'
operationId: listIndividualAppLinkOpens
description: "Newest first, including link previews (`is_link_preview: true`). No raw IP address, name,\nphone number or email is stored or returned. Location is approximate and may be null."
parameters:
-
in: query
name: from
description: 'First calendar day, YYYY-MM-DD. Defaults to 29 days before `to`. Must be a valid date in the format Y-m-d.'
example: '2026-01-15'
required: false
schema:
type: string
description: 'First calendar day, YYYY-MM-DD. Defaults to 29 days before `to`. Must be a valid date in the format Y-m-d.'
example: '2026-01-15'
nullable: true
-
in: query
name: to
description: 'Last calendar day, YYYY-MM-DD. Defaults to today. The range may cover at most 366 days. Must be a valid date in the format Y-m-d.'
example: '2026-01-15'
required: false
schema:
type: string
description: 'Last calendar day, YYYY-MM-DD. Defaults to today. The range may cover at most 366 days. Must be a valid date in the format Y-m-d.'
example: '2026-01-15'
nullable: true
-
in: query
name: source
description: 'Only opens from this source key, such as whatsapp, instagram, qr, sms, facebook, meta, google, referral or direct. Must not be greater than 50 characters.'
example: whatsapp
required: false
schema:
type: string
description: 'Only opens from this source key, such as whatsapp, instagram, qr, sms, facebook, meta, google, referral or direct. Must not be greater than 50 characters.'
example: whatsapp
nullable: true
-
in: query
name: campaign
description: 'Only opens tagged with this campaign, as normalised (lower case, underscores). Must not be greater than 120 characters.'
example: diwali_2026
required: false
schema:
type: string
description: 'Only opens tagged with this campaign, as normalised (lower case, underscores). Must not be greater than 120 characters.'
example: diwali_2026
nullable: true
-
in: query
name: platform
description: 'Only opens from android, ios, desktop or other.'
example: android
required: false
schema:
type: string
description: 'Only opens from android, ios, desktop or other.'
example: android
enum:
- android
- ios
- desktop
- other
nullable: true
-
in: query
name: tracked_page
description: 'Only opens of this page: app (the /app download link), stores (the store-type index), or a store-type key such as pharmacy or kirana-store.'
example: app
required: false
schema:
type: string
description: 'Only opens of this page: app (the /app download link), stores (the store-type index), or a store-type key such as pharmacy or kirana-store.'
example: app
enum:
- app
- stores
- kirana-store
- pharmacy
- supermarket
- clothing-store
- footwear-store
- hardware-store
- electronics-store
- mobile-store
- stationery-store
- book-store
- bakery
- cosmetics-store
- wholesale-business
- auto-parts-store
- home-appliance-store
- gift-shop
- electrical-store
- paint-store
- sanitaryware-store
- pesticide-store
nullable: true
-
in: query
name: per_page
description: 'Visits per page, 1 to 100. Visit lists only. Defaults to 25. Must be at least 1. Must not be greater than 100.'
example: 25
required: false
schema:
type: integer
description: 'Visits per page, 1 to 100. Visit lists only. Defaults to 25. Must be at least 1. Must not be greater than 100.'
example: 25
nullable: true
-
in: query
name: page
description: 'Page number. Visit lists only. Must be at least 1.'
example: 1
required: false
schema:
type: integer
description: 'Page number. Visit lists only. Must be at least 1.'
example: 1
nullable: true
responses:
200:
description: ''
content:
application/json:
schema:
type: object
example:
data:
-
id: 6
visited_at: '2026-09-26T11:20:00+05:30'
page: app
page_label: 'App download link (/app)'
page_locale: null
outcome: landing_page
is_link_preview: true
preview_agent: WhatsApp
is_new_visitor: true
source: whatsapp
source_label: WhatsApp
medium: social
campaign: diwali_2026
utm:
source: whatsapp
medium: null
campaign: diwali_2026
content: null
term: null
ad_click_id: null
referrer_host: null
referrer_app: null
platform: other
device:
type: null
brand: null
model: null
os:
name: Android
version: null
browser:
name: WhatsApp
version: '2.23'
in_app: null
location:
country_code: IN
region: Maharashtra
city: Mumbai
postal_code: '400001'
latitude: 19.076
longitude: 72.8777
timezone: Asia/Kolkata
language: te-IN
screen: null
engagement:
seconds: null
scroll_depth: null
call_to_action: null
-
id: 5
visited_at: '2026-09-26T10:05:00+05:30'
page: pharmacy
page_label: Pharmacies
page_locale: te
outcome: landing_page
is_link_preview: false
preview_agent: null
is_new_visitor: true
source: instagram
source_label: Instagram
medium: paid
campaign: pharmacy_launch
utm:
source: instagram
medium: paid
campaign: pharmacy_launch
content: null
term: null
ad_click_id: null
referrer_host: l.instagram.com
referrer_app: null
platform: android
device:
type: smartphone
brand: Realme
model: C55
os:
name: Android
version: '13'
browser:
name: 'Chrome Mobile'
version: '124'
in_app: null
location:
country_code: IN
region: 'Andhra Pradesh'
city: Guntur
postal_code: '522001'
latitude: 16.3067
longitude: 80.4365
timezone: Asia/Kolkata
language: te-IN
screen:
width: 412
height: 915
engagement:
seconds: 126
scroll_depth: 71
call_to_action: signup
meta:
current_page: 1
last_page: 3
per_page: 2
total: 6
properties:
data:
type: array
example:
-
id: 6
visited_at: '2026-09-26T11:20:00+05:30'
page: app
page_label: 'App download link (/app)'
page_locale: null
outcome: landing_page
is_link_preview: true
preview_agent: WhatsApp
is_new_visitor: true
source: whatsapp
source_label: WhatsApp
medium: social
campaign: diwali_2026
utm:
source: whatsapp
medium: null
campaign: diwali_2026
content: null
term: null
ad_click_id: null
referrer_host: null
referrer_app: null
platform: other
device:
type: null
brand: null
model: null
os:
name: Android
version: null
browser:
name: WhatsApp
version: '2.23'
in_app: null
location:
country_code: IN
region: Maharashtra
city: Mumbai
postal_code: '400001'
latitude: 19.076
longitude: 72.8777
timezone: Asia/Kolkata
language: te-IN
screen: null
engagement:
seconds: null
scroll_depth: null
call_to_action: null
-
id: 5
visited_at: '2026-09-26T10:05:00+05:30'
page: pharmacy
page_label: Pharmacies
page_locale: te
outcome: landing_page
is_link_preview: false
preview_agent: null
is_new_visitor: true
source: instagram
source_label: Instagram
medium: paid
campaign: pharmacy_launch
utm:
source: instagram
medium: paid
campaign: pharmacy_launch
content: null
term: null
ad_click_id: null
referrer_host: l.instagram.com
referrer_app: null
platform: android
device:
type: smartphone
brand: Realme
model: C55
os:
name: Android
version: '13'
browser:
name: 'Chrome Mobile'
version: '124'
in_app: null
location:
country_code: IN
region: 'Andhra Pradesh'
city: Guntur
postal_code: '522001'
latitude: 16.3067
longitude: 80.4365
timezone: Asia/Kolkata
language: te-IN
screen:
width: 412
height: 915
engagement:
seconds: 126
scroll_depth: 71
call_to_action: signup
items:
type: object
properties:
id:
type: integer
example: 6
visited_at:
type: string
example: '2026-09-26T11:20:00+05:30'
page:
type: string
example: app
page_label:
type: string
example: 'App download link (/app)'
page_locale:
type: string
example: null
nullable: true
outcome:
type: string
example: landing_page
is_link_preview:
type: boolean
example: true
preview_agent:
type: string
example: WhatsApp
is_new_visitor:
type: boolean
example: true
source:
type: string
example: whatsapp
source_label:
type: string
example: WhatsApp
medium:
type: string
example: social
campaign:
type: string
example: diwali_2026
utm:
type: object
properties:
source:
type: string
example: whatsapp
medium:
type: string
example: null
nullable: true
campaign:
type: string
example: diwali_2026
content:
type: string
example: null
nullable: true
term:
type: string
example: null
nullable: true
ad_click_id:
type: string
example: null
nullable: true
referrer_host:
type: string
example: null
nullable: true
referrer_app:
type: string
example: null
nullable: true
platform:
type: string
example: other
device:
type: object
properties:
type:
type: string
example: null
nullable: true
brand:
type: string
example: null
nullable: true
model:
type: string
example: null
nullable: true
os:
type: object
properties:
name:
type: string
example: Android
version:
type: string
example: null
nullable: true
browser:
type: object
properties:
name:
type: string
example: WhatsApp
version:
type: string
example: '2.23'
in_app:
type: string
example: null
nullable: true
location:
type: object
properties:
country_code:
type: string
example: IN
region:
type: string
example: Maharashtra
city:
type: string
example: Mumbai
postal_code:
type: string
example: '400001'
latitude:
type: number
example: 19.076
longitude:
type: number
example: 72.8777
timezone:
type: string
example: Asia/Kolkata
language:
type: string
example: te-IN
screen:
type: string
example: null
nullable: true
engagement:
type: object
properties:
seconds:
type: string
example: null
nullable: true
scroll_depth:
type: string
example: null
nullable: true
call_to_action:
type: string
example: null
nullable: true
meta:
type: object
properties:
current_page:
type: integer
example: 1
last_page:
type: integer
example: 3
per_page:
type: integer
example: 2
total:
type: integer
example: 6
401:
description: ''
content:
application/json:
schema:
type: object
example:
message: Unauthenticated.
properties:
message:
type: string
example: Unauthenticated.
403:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'This action is unauthorized.'
properties:
message:
type: string
example: 'This action is unauthorized.'
422:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'The selected platform is invalid.'
errors:
platform:
- 'The selected platform is invalid.'
properties:
message:
type: string
example: 'The selected platform is invalid.'
errors:
type: object
properties:
platform:
type: array
example:
- 'The selected platform is invalid.'
items:
type: string
tags:
- 'App link analytics'
/api/v1/webhooks/razorpay:
post:
summary: 'Receive Razorpay subscription events'
operationId: receiveRazorpaySubscriptionEvents
description: "This public provider callback requires a valid `X-Razorpay-Signature` HMAC header and is idempotent.\nWorkers fetch current Razorpay state. Cancelled, completed, or expired subscriptions end paid access\nand restore Free Essentials only when no newer subscription has replaced the ended subscription."
parameters:
-
in: header
name: X-Razorpay-Signature
description: ''
example: 'string required Razorpay webhook HMAC signature. Example: 0123456789abcdef'
schema:
type: string
responses:
200:
description: ''
content:
application/json:
schema:
type: object
example:
received: true
properties:
received:
type: boolean
example: true
401:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'Invalid webhook signature.'
properties:
message:
type: string
example: 'Invalid webhook signature.'
tags:
- 'Billing webhooks'
security: []
'/api/v1/businesses/{business}/settings/payment-details':
get:
summary: 'The shareable business and bank details.'
operationId: theShareableBusinessAndBankDetails
description: "With no `payment_account_id`, the account returned is the one the shop\nalready prints on its bills, else its default receiving bank, else any\nactive bank account it has. A workspace that has not saved a bank account\nyet gets `bank_account: null` — show the \"add bank details\" path rather\nthan an empty card.\n\n`share_message` is the same details as plain text, ready for WhatsApp or a\nshare sheet. Render the card from the fields; do not parse this string.\n\nNot available offline: bank credentials are fetched fresh each time and\nmust not be cached."
parameters:
-
in: query
name: payment_account_id
description: 'An active payment account of this workspace to show instead of the default one.'
example: 8
required: false
schema:
type: integer
description: 'An active payment account of this workspace to show instead of the default one.'
example: 8
responses:
200:
description: ''
content:
application/json:
schema:
type: object
example:
data:
business:
id: 17
name: Haniot
legal_name: 'HANIOT PRIVATE LIMITED'
gstin: 37AAFCH7061N1Z5
gst_registration_status: active
is_gst_registered: true
phone: '9876543210'
email: accounts@haniot.in
logo_url: null
address:
line_1: '12 MG Road'
line_2: null
city: Visakhapatnam
state_code: '37'
pincode: '530001'
bank_account:
payment_account_id: 8
name: 'ICICI Current'
type: bank
bank_name: 'ICICI Bank'
account_name: 'Haniot Private Limited'
account_number: '236001505028'
account_last_four: '5028'
ifsc: ICIC0012360
branch: 'Dwaraka Nagar'
upi_id: null
upi_id: haniot@icici
share_message: "HANIOT PRIVATE LIMITED\nGSTIN: 37AAFCH7061N1Z5\n\nAccount name: Haniot Private Limited\nAccount number: 236001505028\nBank: ICICI Bank\nIFSC: ICIC0012360\nBranch: Dwaraka Nagar\nUPI: haniot@icici"
properties:
data:
type: object
properties:
business:
type: object
properties:
id:
type: integer
example: 17
name:
type: string
example: Haniot
legal_name:
type: string
example: 'HANIOT PRIVATE LIMITED'
description: 'The registered name to print on the card. Falls back to the workspace name when no legal name is saved.'
gstin:
type: string
example: 37AAFCH7061N1Z5
description: "The primary GST registration's GSTIN, else the one saved on the workspace."
gst_registration_status:
type: string
example: active
description: 'One of `not_assessed`, `not_registered`, `pending`, `active`, `suspended`, `cancelled`.'
is_gst_registered:
type: boolean
example: true
description: 'Whether to show the verified tick: an active registration with a GSTIN on record. It does not re-check the GST portal.'
phone:
type: string
example: '9876543210'
email:
type: string
example: accounts@haniot.in
logo_url:
type: string
example: null
nullable: true
address:
type: object
properties:
line_1:
type: string
example: '12 MG Road'
line_2:
type: string
example: null
nullable: true
city:
type: string
example: Visakhapatnam
state_code:
type: string
example: '37'
pincode:
type: string
example: '530001'
bank_account:
type: object
properties:
payment_account_id:
type: integer
example: 8
name:
type: string
example: 'ICICI Current'
type:
type: string
example: bank
bank_name:
type: string
example: 'ICICI Bank'
account_name:
type: string
example: 'Haniot Private Limited'
account_number:
type: string
example: '236001505028'
description: 'The full account number. Available here and in business receiving_bank_account for accounting members; fetch fresh rather than persisting it on the device.'
account_last_four:
type: string
example: '5028'
ifsc:
type: string
example: ICIC0012360
branch:
type: string
example: 'Dwaraka Nagar'
upi_id:
type: string
example: null
nullable: true
description: 'The account to transfer into, or null when the workspace has saved none.'
upi_id:
type: string
example: haniot@icici
description: "The account's UPI ID, else the workspace's."
share_message:
type: string
example: "HANIOT PRIVATE LIMITED\nGSTIN: 37AAFCH7061N1Z5\n\nAccount name: Haniot Private Limited\nAccount number: 236001505028\nBank: ICICI Bank\nIFSC: ICIC0012360\nBranch: Dwaraka Nagar\nUPI: haniot@icici"
description: 'The same details as plain text for a share sheet.'
401:
description: ''
content:
application/json:
schema:
type: object
example:
message: Unauthenticated.
properties:
message:
type: string
example: Unauthenticated.
403:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'This action is unauthorized.'
properties:
message:
type: string
example: 'This action is unauthorized.'
422:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'The selected payment account id is invalid.'
errors:
payment_account_id:
- 'The selected payment account id is invalid.'
properties:
message:
type: string
example: 'The selected payment account id is invalid.'
errors:
type: object
properties:
payment_account_id:
type: array
example:
- 'The selected payment account id is invalid.'
items:
type: string
tags:
- 'Business details'
requestBody:
required: false
content:
application/json:
schema:
type: object
properties:
payment_account_id:
type: integer
description: ''
example: 16
nullable: true
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
-
in: path
name: business_id
description: 'Workspace ID.'
example: 17
required: true
schema:
type: integer
/api/v1/plugin/session:
get:
summary: 'Inspect the connected plugin account and shop.'
operationId: inspectTheConnectedPluginAccountAndShop
description: "Requires a plugin OAuth token with `dukanam:read`; ordinary mobile tokens return 403.\nMembership and plan/seat eligibility are rechecked on every request."
parameters: []
responses:
200:
description: ''
content:
application/json:
schema:
type: object
example:
data:
user:
id: 1
name: Priya
business:
id: 1
name: 'Priya Store'
scope: 'dukanam:read'
resource: 'https://mcp.example.com/mcp'
timezone: Asia/Kolkata
properties:
data:
type: object
properties:
user:
type: object
properties:
id:
type: integer
example: 1
name:
type: string
example: Priya
business:
type: object
properties:
id:
type: integer
example: 1
name:
type: string
example: 'Priya Store'
scope:
type: string
example: 'dukanam:read'
resource:
type: string
example: 'https://mcp.example.com/mcp'
timezone:
type: string
example: Asia/Kolkata
403:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'A plugin OAuth token is required.'
properties:
message:
type: string
example: 'A plugin OAuth token is required.'
tags:
- 'ChatGPT plugin'
'/api/v1/businesses/{business}/invoices/{invoice}/expenses':
get:
summary: 'List expenses on an invoice.'
operationId: listExpensesOnAnInvoice
description: ''
parameters:
-
in: query
name: per_page
description: 'From 1 to 100.'
example: 20
required: false
schema:
type: integer
description: 'From 1 to 100.'
example: 20
responses:
401:
description: ''
content:
application/json:
schema:
type: object
example:
message: Unauthenticated.
properties:
message:
type: string
example: Unauthenticated.
tags:
- 'Invoice-linked expenses'
requestBody:
required: false
content:
application/json:
schema:
type: object
properties:
per_page:
type: integer
description: 'Must be between 1 and 100.'
example: 2
nullable: true
post:
summary: 'Record a sales or purchase invoice expense.'
operationId: recordASalesOrPurchaseInvoiceExpense
description: ''
parameters: []
responses: {}
tags:
- 'Invoice-linked expenses'
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
category:
type: string
description: 'Category, up to 64 characters.'
example: Transport
payee:
type: string
description: 'Payee, up to 128 characters.'
example: 'Local transport'
nullable: true
amount_paise:
type: integer
description: 'Positive expense amount in paise.'
example: 30000
occurred_on:
type: string
description: 'Expense date, Y-m-d.'
example: '2026-10-04'
note:
type: string
description: 'Description, up to 1000 characters.'
example: 'Transport for this invoice'
paid:
type: boolean
description: 'Whether paid at creation; defaults true. False accrues an expense payable without moving cash.'
example: true
payment_method:
type: string
description: 'Required if paid.'
example: bank
enum:
- cash
- bank
- upi
- card
- other
nullable: true
payment_account_id:
type: integer
description: 'Active compatible payment account.'
example: null
nullable: true
idempotency_key:
type: string
description: 'Stable UUID; retain for retries.'
example: fdf5dfdf-aead-473b-8c98-dcfbbda6c02a
required:
- category
- amount_paise
- occurred_on
- note
- idempotency_key
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
-
in: path
name: invoice
description: 'The invoice.'
example: 1
required: true
schema:
type: integer
'/api/v1/businesses/{business}/documents/{document}/expenses':
get:
summary: 'List expenses on an invoice.'
operationId: listExpensesOnAnInvoice
description: ''
parameters:
-
in: query
name: per_page
description: 'From 1 to 100.'
example: 20
required: false
schema:
type: integer
description: 'From 1 to 100.'
example: 20
responses:
401:
description: ''
content:
application/json:
schema:
type: object
example:
message: Unauthenticated.
properties:
message:
type: string
example: Unauthenticated.
tags:
- 'Invoice-linked expenses'
requestBody:
required: false
content:
application/json:
schema:
type: object
properties:
per_page:
type: integer
description: 'Must be between 1 and 100.'
example: 2
nullable: true
post:
summary: 'Record a sales or purchase invoice expense.'
operationId: recordASalesOrPurchaseInvoiceExpense
description: ''
parameters: []
responses: {}
tags:
- 'Invoice-linked expenses'
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
category:
type: string
description: 'Category, up to 64 characters.'
example: Transport
payee:
type: string
description: 'Payee, up to 128 characters.'
example: 'Local transport'
nullable: true
amount_paise:
type: integer
description: 'Positive expense amount in paise.'
example: 30000
occurred_on:
type: string
description: 'Expense date, Y-m-d.'
example: '2026-10-04'
note:
type: string
description: 'Description, up to 1000 characters.'
example: 'Transport for this invoice'
paid:
type: boolean
description: 'Whether paid at creation; defaults true. False accrues an expense payable without moving cash.'
example: true
payment_method:
type: string
description: 'Required if paid.'
example: bank
enum:
- cash
- bank
- upi
- card
- other
nullable: true
payment_account_id:
type: integer
description: 'Active compatible payment account.'
example: null
nullable: true
idempotency_key:
type: string
description: 'Stable UUID; retain for retries.'
example: fdf5dfdf-aead-473b-8c98-dcfbbda6c02a
required:
- category
- amount_paise
- occurred_on
- note
- idempotency_key
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
-
in: path
name: document
description: 'The document.'
example: architecto
required: true
schema:
type: string
'/api/v1/businesses/{business}/expenses/{expense}/payments':
post:
summary: 'Pay an outstanding invoice expense.'
operationId: payAnOutstandingInvoiceExpense
description: "Recognized expense stays on its original date. Payment reduces the expense payable and\ncash/bank only. Partial payments are allowed; amount cannot exceed outstanding."
parameters: []
responses: {}
tags:
- 'Invoice-linked expenses'
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
amount_paise:
type: integer
description: 'Positive paise within the outstanding amount.'
example: 10000
paid_on:
type: string
description: 'Payment date, Y-m-d.'
example: '2026-10-04'
payment_method:
type: string
description: ''
example: bank
enum:
- cash
- bank
- upi
- card
- other
payment_account_id:
type: integer
description: 'Active compatible account.'
example: null
nullable: true
reference:
type: string
description: 'Payment reference, up to 128 characters.'
example: null
nullable: true
idempotency_key:
type: string
description: 'Stable UUID for retries.'
example: fdf5dfdf-aead-473b-8c98-dcfbbda6c02b
required:
- amount_paise
- paid_on
- payment_method
- idempotency_key
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
-
in: path
name: expense
description: 'The expense.'
example: architecto
required: true
schema:
type: string
'/api/v1/businesses/{business}/expenses/{expense}/payments/{payment}/void':
post:
summary: 'Void an invoice expense payment.'
operationId: voidAnInvoiceExpensePayment
description: 'Keeps the expense and restores its payable; reverses the bank/cash movement once.'
parameters: []
responses: {}
tags:
- 'Invoice-linked expenses'
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
reason:
type: string
description: 'Correction reason, up to 1000 characters.'
example: 'Wrong transfer'
required:
- reason
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
-
in: path
name: expense
description: 'The expense.'
example: architecto
required: true
schema:
type: string
-
in: path
name: payment
description: 'The payment.'
example: architecto
required: true
schema:
type: string
'/api/v1/businesses/{business_id}/settings/invoice-branding':
get:
summary: 'Current invoice branding and the choices available.'
operationId: currentInvoiceBrandingAndTheChoicesAvailable
description: "`templates`, `layout_choices` and `accents` are the pickers — render them rather\nthan hard-coding values, because they can grow. `accents` is a set of suggested\nswatches to offer beside a colour input, not the set of allowed values: any\n`#RRGGBB` is accepted. `invoice_accent_palette` carries the colours that hex\nactually prints as, so a client can show the result without redoing the maths.\n\nAvailable offline: cached. Branding changes rarely and the counter needs it\nto print without signal."
parameters: []
responses:
200:
description: ''
content:
application/json:
schema:
type: object
example:
data:
invoice_template: classic
invoice_accent_colour: '#10505E'
invoice_footer_note: 'Goods once sold are not returnable.'
bank_details: 'HDFC Bank 5010 1234 5678, IFSC HDFC0000123'
authorized_signatory: 'Priya Sharma'
show_upi_qr_on_invoice: false
show_logo_on_invoice: true
upi_id: priyatextiles@hdfcbank
logo_url: 'https://cdn.dukanam.com/business-logos/17/logo.png'
signature_url: null
templates:
-
value: classic
label: Classic
summary: 'Boxed party details under a ruled header. The layout Dukanam has always printed.'
-
value: compact
label: Compact
summary: 'Tighter type and flat rows, so a long bill fits on fewer pages.'
-
value: accent
label: Accent
summary: 'A filled colour band across the head, for a shop that wants its colour seen.'
accents:
-
value: '#10505E'
label: 'Dukanam teal'
max_image_kilobytes: 1024
properties:
data:
type: object
properties:
invoice_template:
type: string
example: classic
invoice_accent_colour:
type: string
example: '#10505E'
invoice_footer_note:
type: string
example: 'Goods once sold are not returnable.'
bank_details:
type: string
example: 'HDFC Bank 5010 1234 5678, IFSC HDFC0000123'
authorized_signatory:
type: string
example: 'Priya Sharma'
show_upi_qr_on_invoice:
type: boolean
example: false
show_logo_on_invoice:
type: boolean
example: true
upi_id:
type: string
example: priyatextiles@hdfcbank
logo_url:
type: string
example: 'https://cdn.dukanam.com/business-logos/17/logo.png'
description: 'Temporary signed object-storage URL when the logo is stored on S3. Refresh the resource after it expires.'
signature_url:
type: string
example: null
description: 'As logo_url, for the authorised signature image.'
templates:
type: array
example:
- value: classic
label: Classic
summary: 'Boxed party details under a ruled header. The layout Dukanam has always printed.'
- value: compact
label: Compact
summary: 'Tighter type and flat rows, so a long bill fits on fewer pages.'
- value: accent
label: Accent
summary: 'A filled colour band across the head, for a shop that wants its colour seen.'
items:
type: object
properties:
value:
type: string
example: classic
label:
type: string
example: Classic
summary:
type: string
example: 'Boxed party details under a ruled header. The layout Dukanam has always printed.'
accents:
type: array
example:
- value: '#10505E'
label: 'Dukanam teal'
items:
type: object
properties:
value:
type: string
example: '#10505E'
label:
type: string
example: 'Dukanam teal'
max_image_kilobytes:
type: integer
example: 1024
401:
description: ''
content:
application/json:
schema:
type: object
example:
message: Unauthenticated.
properties:
message:
type: string
example: Unauthenticated.
403:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'This action is unauthorized.'
properties:
message:
type: string
example: 'This action is unauthorized.'
tags:
- 'Invoice branding'
put:
summary: 'Update invoice branding.'
operationId: updateInvoiceBranding
description: "Requires the `accounting` permission, the same as the rest of business\nsettings. Send only the fields you are changing. Saved documents are\nuntouched: each froze its own branding when it was raised.\n\nOnline only."
parameters: []
responses:
200:
description: ''
content:
application/json:
schema:
type: object
example:
data:
invoice_template: accent
invoice_accent_colour: '#047857'
show_upi_qr_on_invoice: true
properties:
data:
type: object
properties:
invoice_template:
type: string
example: accent
invoice_accent_colour:
type: string
example: '#047857'
show_upi_qr_on_invoice:
type: boolean
example: true
403:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'Your role does not allow this action.'
properties:
message:
type: string
example: 'Your role does not allow this action.'
422:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'Add a UPI ID in business settings before printing a UPI QR on invoices.'
errors:
show_upi_qr_on_invoice:
- 'Add a UPI ID in business settings before printing a UPI QR on invoices.'
properties:
message:
type: string
example: 'Add a UPI ID in business settings before printing a UPI QR on invoices.'
errors:
type: object
properties:
show_upi_qr_on_invoice:
type: array
example:
- 'Add a UPI ID in business settings before printing a UPI QR on invoices.'
items:
type: string
tags:
- 'Invoice branding'
requestBody:
required: false
content:
application/json:
schema:
type: object
properties:
invoice_template:
type: string
description: 'One of `classic`, `compact`, `accent`, or `custom`.'
example: accent
invoice_accent_colour:
type: string
description: 'Any seven-character hex, `#RRGGBB`. The `accents` list returned by the GET is a set of suggestions, not the limit. Send `null` to return to the default teal.'
example: '#047857'
nullable: true
invoice_layout:
type: object
description: 'The knobs behind the `custom` template. Every key is optional and merges over what is saved, so one switch can be moved on its own. Send `null` to reset the whole layout to its defaults.'
example: []
properties:
header:
type: string
description: '`ruled`, `band`, or `minimal`.'
example: band
density:
type: string
description: '`roomy`, `normal`, or `compact`.'
example: compact
table:
type: string
description: '`ruled`, `flat`, or `zebra`.'
example: flat
logo:
type: object
description: ''
example: null
properties:
height:
type: integer
description: 'Logo height on A4 in pixels, 24-96. An 80mm roll keeps its own size.'
example: 64
align:
type: string
description: '`left`, `center`, or `right`.'
example: center
columns:
type: object
description: ''
example: null
properties:
hsn:
type: boolean
description: 'Print the HSN/SAC code under each line. A GST tax invoice must carry it above the turnover threshold.'
example: true
uqc:
type: boolean
description: 'Print the unit beside the quantity.'
example: true
discount:
type: boolean
description: 'Print the per-line discount column. The discount total still prints either way.'
example: true
nullable: true
invoice_footer_note:
type: string
description: 'Terms, a returns policy, or a thank-you line, up to 1000 characters.'
example: 'Goods once sold are not returnable.'
nullable: true
bank_details:
type: string
description: 'Payment instructions printed above the signature, up to 1000 characters.'
example: 'HDFC Bank 5010 1234 5678, IFSC HDFC0000123'
nullable: true
authorized_signatory:
type: string
description: 'The name printed under the signature, up to 128 characters.'
example: 'Priya Sharma'
nullable: true
show_upi_qr_on_invoice:
type: boolean
description: 'Print an exact-amount UPI QR on the bill. Requires a UPI ID on the workspace.'
example: true
show_logo_on_invoice:
type: boolean
description: 'Print the shop logo on invoices and documents. On by default. The image is the workspace logo captured during onboarding, so this only controls whether it appears on the paper.'
example: true
parameters:
-
in: path
name: business_id
description: 'Workspace ID.'
example: 17
required: true
schema:
type: integer
'/api/v1/businesses/{business_id}/settings/invoice-branding/preview':
get:
summary: 'Preview a sample bill under branding that has not been saved.'
operationId: previewASampleBillUnderBrandingThatHasNotBeenSaved
description: "Renders a plausible invoice — never created, never numbered, never stored —\nso the settings screen can show the real document while the shop is still\nchoosing. Returns a PDF by default; pass `as=html` for a WebView.\n\nOnline only."
parameters:
-
in: query
name: template
description: 'Preview this template instead of the saved one.'
example: accent
required: false
schema:
type: string
description: 'Preview this template instead of the saved one.'
example: accent
-
in: query
name: accent
description: 'Preview this accent instead of the saved one, as any seven-character hex.'
example: '#047857'
required: false
schema:
type: string
description: 'Preview this accent instead of the saved one, as any seven-character hex.'
example: '#047857'
-
in: query
name: layout
description: 'Preview these layout knobs instead of the saved ones, as `layout[header]=band&layout[density]=compact`. Merged over what is saved.'
example: []
required: false
schema:
type: object
description: 'Preview these layout knobs instead of the saved ones, as `layout[header]=band&layout[density]=compact`. Merged over what is saved.'
example: []
properties: {}
-
in: query
name: format
description: 'Paper size: `a4` (default) or `80mm`.'
example: 80mm
required: false
schema:
type: string
description: 'Paper size: `a4` (default) or `80mm`.'
example: 80mm
-
in: query
name: as
description: 'Response body: `pdf` (default) or `html`.'
example: html
required: false
schema:
type: string
description: 'Response body: `pdf` (default) or `html`.'
example: html
responses:
200:
description: ''
content:
text/plain:
schema:
type: string
example: 'A PDF or HTML document, not JSON.'
422:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'The selected template is invalid.'
errors:
template:
- 'The selected template is invalid.'
properties:
message:
type: string
example: 'The selected template is invalid.'
errors:
type: object
properties:
template:
type: array
example:
- 'The selected template is invalid.'
items:
type: string
tags:
- 'Invoice branding'
parameters:
-
in: path
name: business_id
description: 'Workspace ID.'
example: 17
required: true
schema:
type: integer
'/api/v1/businesses/{business_id}/settings/invoice-branding/{kind}':
post:
summary: 'Upload the shop logo or the authorised signature.'
operationId: uploadTheShopLogoOrTheAuthorisedSignature
description: "Multipart. PNG or JPEG, up to 1 MB. Replaces whatever is stored under that\nname; the previous file is deleted once the new one is committed.\n\nOnline only."
parameters: []
responses:
200:
description: ''
content:
application/json:
schema:
type: object
example:
data:
invoice_template: classic
logo_url: 'https://cdn.dukanam.com/business-logos/17/logo.png'
signature_url: null
properties:
data:
type: object
properties:
invoice_template:
type: string
example: classic
logo_url:
type: string
example: 'https://cdn.dukanam.com/business-logos/17/logo.png'
signature_url:
type: string
example: null
nullable: true
422:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'The image must not be greater than 1024 kilobytes.'
errors:
image:
- 'The image must not be greater than 1024 kilobytes.'
properties:
message:
type: string
example: 'The image must not be greater than 1024 kilobytes.'
errors:
type: object
properties:
image:
type: array
example:
- 'The image must not be greater than 1024 kilobytes.'
items:
type: string
tags:
- 'Invoice branding'
requestBody:
required: true
content:
multipart/form-data:
schema:
type: object
properties:
image:
type: string
format: binary
description: 'The PNG or JPEG to store, up to 1 MB.'
required:
- image
delete:
summary: 'Remove the shop logo or the authorised signature.'
operationId: removeTheShopLogoOrTheAuthorisedSignature
description: "Documents already saved keep the image they were issued with; only future\ndocuments print without it.\n\nOnline only."
parameters: []
responses:
200:
description: ''
content:
application/json:
schema:
type: object
example:
data:
invoice_template: classic
logo_url: null
signature_url: null
properties:
data:
type: object
properties:
invoice_template:
type: string
example: classic
logo_url:
type: string
example: null
nullable: true
signature_url:
type: string
example: null
nullable: true
tags:
- 'Invoice branding'
parameters:
-
in: path
name: business_id
description: 'Workspace ID.'
example: 17
required: true
schema:
type: integer
-
in: path
name: kind
description: 'Either `logo` or `signature`.'
example: logo
required: true
schema:
type: string
'/api/v1/businesses/{business_id}/invoices/{invoice_id}/preview':
get:
summary: 'Preview a real invoice under branding that has not been saved.'
operationId: previewARealInvoiceUnderBrandingThatHasNotBeenSaved
description: "Renders a saved invoice with the requested template and accent so the shop\ncan judge the choice against its own bill. Nothing is written: the invoice\nkeeps the branding it was saved with, and printing it still produces that.\n\nOnline only."
parameters:
-
in: query
name: template
description: "Render this template instead of the invoice's own."
example: compact
required: false
schema:
type: string
description: "Render this template instead of the invoice's own."
example: compact
-
in: query
name: accent
description: "Render this accent instead of the invoice's own, as any seven-character hex."
example: '#9A3412'
required: false
schema:
type: string
description: "Render this accent instead of the invoice's own, as any seven-character hex."
example: '#9A3412'
-
in: query
name: layout
description: "Render these layout knobs instead of the invoice's own, as `layout[table]=zebra`. Merged over what the workspace has saved."
example: []
required: false
schema:
type: object
description: "Render these layout knobs instead of the invoice's own, as `layout[table]=zebra`. Merged over what the workspace has saved."
example: []
properties: {}
-
in: query
name: format
description: 'Paper size: `a4` (default) or `80mm`.'
example: 80mm
required: false
schema:
type: string
description: 'Paper size: `a4` (default) or `80mm`.'
example: 80mm
-
in: query
name: as
description: 'Response body: `pdf` (default) or `html`.'
example: html
required: false
schema:
type: string
description: 'Response body: `pdf` (default) or `html`.'
example: html
responses:
200:
description: ''
content:
text/plain:
schema:
type: string
example: 'A PDF or HTML document, not JSON.'
404:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'Not Found'
properties:
message:
type: string
example: 'Not Found'
tags:
- 'Invoice branding'
parameters:
-
in: path
name: business_id
description: 'Workspace ID.'
example: 17
required: true
schema:
type: integer
-
in: path
name: invoice_id
description: 'Invoice ID.'
example: 482
required: true
schema:
type: integer
'/api/v1/businesses/{business}/loan-accounts':
get:
summary: 'List loans and credit cards.'
operationId: listLoansAndCreditCards
description: ''
parameters:
-
in: query
name: status
description: 'Filter by record status.'
example: active
required: false
schema:
type: string
description: 'Filter by record status.'
example: active
enum:
- active
- closed
-
in: query
name: type
description: 'Filter by kind.'
example: loan
required: false
schema:
type: string
description: 'Filter by kind.'
example: loan
enum:
- loan
- credit_card
-
in: query
name: per_page
description: 'Results per page, from 1 to 100.'
example: 20
required: false
schema:
type: integer
description: 'Results per page, from 1 to 100.'
example: 20
responses:
401:
description: ''
content:
application/json:
schema:
type: object
example:
message: Unauthenticated.
properties:
message:
type: string
example: Unauthenticated.
tags:
- 'Loans and EMI'
requestBody:
required: false
content:
application/json:
schema:
type: object
properties:
status:
type: string
description: ''
example: active
enum:
- active
- closed
nullable: true
type:
type: string
description: ''
example: loan
enum:
- loan
- credit_card
nullable: true
per_page:
type: integer
description: 'Must be at least 1. Must not be greater than 100.'
example: 1
nullable: true
post:
summary: 'Open a loan.'
operationId: openALoan
description: "Naming `disbursed_to_payment_account_id` says the money actually landed in that account, so\nthe opening journal is a disbursement — the bank balance rises and the liability rises with\nit. Leave it out for a loan taken before the shop kept books here: there is no receipt to\nrecord, so the borrowing opens against equity, the way a party's opening balance does.\n\nA `loan` with a `tenure_months` gets its amortisation worked out on the spot. A\n`credit_card` never does — it revolves, and an invented schedule would be a fiction."
parameters: []
responses:
422:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'This EMI does not cover the interest on the outstanding balance, so the loan would never be repaid. Raise the EMI or lower the interest rate.'
errors:
emi_amount:
- 'This EMI does not cover the interest on the outstanding balance, so the loan would never be repaid. Raise the EMI or lower the interest rate.'
properties:
message:
type: string
example: 'This EMI does not cover the interest on the outstanding balance, so the loan would never be repaid. Raise the EMI or lower the interest rate.'
errors:
type: object
properties:
emi_amount:
type: array
example:
- 'This EMI does not cover the interest on the outstanding balance, so the loan would never be repaid. Raise the EMI or lower the interest rate.'
items:
type: string
tags:
- 'Loans and EMI'
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
name:
type: string
description: 'Must not be greater than 120 characters.'
example: b
type:
type: string
description: ''
example: loan
enum:
- loan
- credit_card
nullable: true
lender_name:
type: string
description: 'Must not be greater than 120 characters.'
example: 'n'
nullable: true
account_last_four:
type: string
description: 'Must not be greater than 8 characters.'
example: gzmiyvdl
nullable: true
principal:
type: number
description: 'Must be at least 0. Must not be greater than 999999999.'
example: 19
interest_rate:
type: number
description: 'Must be at least 0. Must not be greater than 100.'
example: 17
nullable: true
emi_amount:
type: number
description: 'Must not be greater than 999999999.'
example: 5
nullable: true
emi_day_of_month:
type: integer
description: 'Must be between 1 and 31.'
example: 1
nullable: true
tenure_months:
type: integer
description: 'Must be between 1 and 360.'
example: 2
nullable: true
started_on:
type: string
description: 'Must be a valid date.'
example: '2026-01-15'
first_emi_on:
type: string
description: 'Must be a valid date. Must be a date after or equal to started_on.'
example: '2026-01-15'
nullable: true
disbursed_to_payment_account_id:
type: integer
description: ''
example: 16
nullable: true
notes:
type: string
description: 'Must not be greater than 1000 characters.'
example: 'n'
nullable: true
required:
- name
- principal
- started_on
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
'/api/v1/businesses/{business}/loan-accounts/{loanAccount_id}':
get:
summary: 'Show one loan with its schedule and repayment history.'
operationId: showOneLoanWithItsScheduleAndRepaymentHistory
description: ''
parameters: []
responses:
401:
description: ''
content:
application/json:
schema:
type: object
example:
message: Unauthenticated.
properties:
message:
type: string
example: Unauthenticated.
tags:
- 'Loans and EMI'
put:
summary: "Correct a loan's description and terms."
operationId: correctALoansDescriptionAndTerms
description: "This is a **whole-record write**: a field left out is cleared, exactly as contact updates\nbehave. The borrowing itself is saved, so the principal and the start date are not\neditable here. Revising the terms rebuilds the instalments still to come; instalments\nalready paid are left exactly as they were paid."
parameters: []
responses: {}
tags:
- 'Loans and EMI'
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
name:
type: string
description: 'Must not be greater than 120 characters.'
example: b
lender_name:
type: string
description: 'Must not be greater than 120 characters.'
example: 'n'
nullable: true
account_last_four:
type: string
description: 'Must not be greater than 8 characters.'
example: gzmiyvdl
nullable: true
interest_rate:
type: number
description: 'Must be at least 0. Must not be greater than 100.'
example: 19
nullable: true
emi_amount:
type: number
description: 'Must not be greater than 999999999.'
example: 17
nullable: true
emi_day_of_month:
type: integer
description: 'Must be between 1 and 31.'
example: 1
nullable: true
tenure_months:
type: integer
description: 'Must be between 1 and 360.'
example: 1
nullable: true
first_emi_on:
type: string
description: 'Must be a valid date.'
example: '2026-01-15'
nullable: true
status:
type: string
description: ''
example: active
enum:
- active
- closed
nullable: true
notes:
type: string
description: 'Must not be greater than 1000 characters.'
example: h
nullable: true
required:
- name
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
-
in: path
name: loanAccount_id
description: 'The ID of the loanAccount.'
example: 1
required: true
schema:
type: integer
'/api/v1/businesses/{business}/loan-accounts/{loanAccount_id}/schedule':
get:
summary: 'The instalments worked out for this loan.'
operationId: theInstalmentsWorkedOutForThisLoan
description: ''
parameters:
-
in: query
name: status
description: 'Filter by instalment status.'
example: pending
required: false
schema:
type: string
description: 'Filter by instalment status.'
example: pending
enum:
- pending
- paid
responses:
401:
description: ''
content:
application/json:
schema:
type: object
example:
message: Unauthenticated.
properties:
message:
type: string
example: Unauthenticated.
tags:
- 'Loans and EMI'
requestBody:
required: false
content:
application/json:
schema:
type: object
properties:
status:
type: string
description: ''
example: pending
enum:
- pending
- paid
nullable: true
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
-
in: path
name: loanAccount_id
description: 'The ID of the loanAccount.'
example: 1
required: true
schema:
type: integer
'/api/v1/businesses/{business}/loan-accounts/{loanAccount_id}/payments':
get:
summary: 'List repayments made against a loan.'
operationId: listRepaymentsMadeAgainstALoan
description: ''
parameters:
-
in: query
name: per_page
description: 'Results per page, from 1 to 100.'
example: 20
required: false
schema:
type: integer
description: 'Results per page, from 1 to 100.'
example: 20
responses:
401:
description: ''
content:
application/json:
schema:
type: object
example:
message: Unauthenticated.
properties:
message:
type: string
example: Unauthenticated.
tags:
- 'Loans and EMI'
requestBody:
required: false
content:
application/json:
schema:
type: object
properties:
per_page:
type: integer
description: 'Must be at least 1. Must not be greater than 100.'
example: 1
nullable: true
post:
summary: 'Record a repayment.'
operationId: recordARepayment
description: "Name `loan_emi_schedule_id` and the lender's own split for that instalment is used, which is\nwhat a shop paying this month's EMI wants. State `principal` and `interest` instead for a\npart payment, a prepayment, or a credit-card payment with no schedule behind it. Sending\n`amount` as well is a check, not an override: parts that do not add up are refused.\n\nThe journal is split — principal off the liability, interest to expense, any lender charges\nto operating expenses — because a lump sum booked as one expense misstates both the balance\nsheet and the profit and loss."
parameters: []
responses:
422:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'Only ₹12,000.00 of principal is still outstanding on Vehicle loan. Reduce the principal portion, or record the excess as interest or charges.'
errors:
principal:
- 'Only ₹12,000.00 of principal is still outstanding on Vehicle loan. Reduce the principal portion, or record the excess as interest or charges.'
properties:
message:
type: string
example: 'Only ₹12,000.00 of principal is still outstanding on Vehicle loan. Reduce the principal portion, or record the excess as interest or charges.'
errors:
type: object
properties:
principal:
type: array
example:
- 'Only ₹12,000.00 of principal is still outstanding on Vehicle loan. Reduce the principal portion, or record the excess as interest or charges.'
items:
type: string
tags:
- 'Loans and EMI'
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
loan_emi_schedule_id:
type: integer
description: ''
example: 16
nullable: true
paid_on:
type: string
description: 'Must be a valid date.'
example: '2026-01-15'
amount:
type: number
description: 'Must not be greater than 999999999.'
example: 22
nullable: true
principal:
type: number
description: 'Must be at least 0. Must not be greater than 999999999.'
example: 7
nullable: true
interest:
type: number
description: 'Must be at least 0. Must not be greater than 999999999.'
example: 16
nullable: true
charges:
type: number
description: 'Must be at least 0. Must not be greater than 999999999.'
example: 17
nullable: true
payment_method:
type: string
description: ''
example: cash
enum:
- cash
- bank
- upi
- card
- cheque
- other
payment_account_id:
type: integer
description: ''
example: 16
nullable: true
reference:
type: string
description: 'Must not be greater than 64 characters.'
example: 'n'
nullable: true
note:
type: string
description: 'Must not be greater than 255 characters.'
example: g
nullable: true
idempotency_key:
type: string
description: 'Stable UUID that makes a retry safe.'
example: 6d61f406-f07d-482d-a284-3e06edfd7f55
nullable: true
required:
- paid_on
- payment_method
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
-
in: path
name: loanAccount_id
description: 'The ID of the loanAccount.'
example: 1
required: true
schema:
type: integer
'/api/v1/businesses/{business}/loan-accounts/{loanAccount_id}/payments/{loanPayment_id}/void':
post:
summary: 'Void a repayment.'
operationId: voidARepayment
description: "Saved transactions keep their history. The journal is reversed, the instalment goes back to\npending, and the reason stays in the audit trail."
parameters: []
responses: {}
tags:
- 'Loans and EMI'
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
reason:
type: string
description: 'Must be at least 3 characters. Must not be greater than 255 characters.'
example: b
required:
- reason
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
-
in: path
name: loanAccount_id
description: 'The ID of the loanAccount.'
example: 1
required: true
schema:
type: integer
-
in: path
name: loanPayment_id
description: 'The ID of the loanPayment.'
example: 1
required: true
schema:
type: integer
'/api/v1/businesses/{business}/other-incomes':
get:
summary: 'List other-income receipts.'
operationId: listOtherIncomeReceipts
description: ''
parameters:
-
in: query
name: q
description: 'Case-insensitive search across category, payer, or description.'
example: rent
required: false
schema:
type: string
description: 'Case-insensitive search across category, payer, or description.'
example: rent
-
in: query
name: category
description: 'Filter by an exact category.'
example: 'Rent received'
required: false
schema:
type: string
description: 'Filter by an exact category.'
example: 'Rent received'
-
in: query
name: payment_account_id
description: 'Filter by the account the money landed in.'
example: 9
required: false
schema:
type: integer
description: 'Filter by the account the money landed in.'
example: 9
-
in: query
name: from
description: 'Include receipts on or after this date (`YYYY-MM-DD`).'
example: '2026-08-01'
required: false
schema:
type: string
description: 'Include receipts on or after this date (`YYYY-MM-DD`).'
example: '2026-08-01'
-
in: query
name: to
description: 'Include receipts on or before this date (`YYYY-MM-DD`).'
example: '2026-08-31'
required: false
schema:
type: string
description: 'Include receipts on or before this date (`YYYY-MM-DD`).'
example: '2026-08-31'
-
in: query
name: status
description: 'Filter by record status.'
example: active
required: false
schema:
type: string
description: 'Filter by record status.'
example: active
enum:
- active
- voided
-
in: query
name: per_page
description: 'Results per page, from 1 to 100.'
example: 20
required: false
schema:
type: integer
description: 'Results per page, from 1 to 100.'
example: 20
responses:
401:
description: ''
content:
application/json:
schema:
type: object
example:
message: Unauthenticated.
properties:
message:
type: string
example: Unauthenticated.
tags:
- 'Loans and EMI'
requestBody:
required: false
content:
application/json:
schema:
type: object
properties:
q:
type: string
description: 'Must not be greater than 100 characters.'
example: b
nullable: true
category:
type: string
description: 'Must not be greater than 64 characters.'
example: 'n'
nullable: true
payment_account_id:
type: integer
description: ''
example: 16
nullable: true
from:
type: string
description: 'Must be a valid date.'
example: '2026-01-15'
nullable: true
to:
type: string
description: 'Must be a valid date. Must be a date after or equal to from.'
example: '2026-01-15'
nullable: true
status:
type: string
description: ''
example: active
enum:
- active
- voided
nullable: true
per_page:
type: integer
description: 'Must be at least 1. Must not be greater than 100.'
example: 22
nullable: true
post:
summary: 'Record a receipt that is not a sale.'
operationId: recordAReceiptThatIsNotASale
description: "The receipt, its journal, the cash-drawer movement, and the audit record either all commit\nor all roll back."
parameters: []
responses: {}
tags:
- 'Loans and EMI'
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
category:
type: string
description: 'Must not be greater than 64 characters.'
example: b
payer:
type: string
description: 'Must not be greater than 128 characters.'
example: 'n'
nullable: true
amount:
type: number
description: 'Must not be greater than 999999999.'
example: 7
occurred_on:
type: string
description: 'Must be a valid date.'
example: '2026-01-15'
payment_method:
type: string
description: ''
example: cash
enum:
- cash
- bank
- upi
- card
- cheque
- other
payment_account_id:
type: integer
description: ''
example: 16
nullable: true
note:
type: string
description: 'Must not be greater than 1000 characters.'
example: 'n'
idempotency_key:
type: string
description: 'Stable UUID that makes a retry safe.'
example: 6d61f406-f07d-482d-a284-3e06edfd7f55
nullable: true
required:
- category
- amount
- occurred_on
- payment_method
- note
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
'/api/v1/businesses/{business}/other-incomes/{otherIncome_id}':
get:
summary: ''
operationId: getApiV1BusinessesBusinessOtherIncomesOtherIncome_id
description: ''
parameters: []
responses:
401:
description: ''
content:
application/json:
schema:
type: object
example:
message: Unauthenticated.
properties:
message:
type: string
example: Unauthenticated.
tags:
- 'Loans and EMI'
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
-
in: path
name: otherIncome_id
description: 'The ID of the otherIncome.'
example: 1
required: true
schema:
type: integer
'/api/v1/businesses/{business}/other-incomes/{otherIncome_id}/void':
post:
summary: 'Void a receipt.'
operationId: voidAReceipt
description: "The void marker, the journal reversal, the cash-drawer reversal, and the audit record\neither all commit or all roll back."
parameters: []
responses: {}
tags:
- 'Loans and EMI'
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
reason:
type: string
description: 'Must be at least 3 characters. Must not be greater than 255 characters.'
example: b
required:
- reason
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
-
in: path
name: otherIncome_id
description: 'The ID of the otherIncome.'
example: 1
required: true
schema:
type: integer
'/api/v1/businesses/{business}/lookups':
get:
summary: 'Search lookup values.'
operationId: searchLookupValues
description: ''
parameters:
-
in: query
name: source
description: 'Lookup source. The `items`, `contacts`, `customers`, and `suppliers` sources return only records that are still active, since a lookup is someone choosing what to put on a new document.'
example: customers
required: true
schema:
type: string
description: 'Lookup source. The `items`, `contacts`, `customers`, and `suppliers` sources return only records that are still active, since a lookup is someone choosing what to put on a new document.'
example: customers
-
in: query
name: q
description: 'Case-insensitive search text, up to 100 characters.'
example: priya
required: false
schema:
type: string
description: 'Case-insensitive search text, up to 100 characters.'
example: priya
nullable: true
-
in: query
name: method
description: 'Payment method used to filter compatible payment accounts.'
example: upi
required: false
schema:
type: string
description: 'Payment method used to filter compatible payment accounts.'
example: upi
nullable: true
-
in: query
name: direction
description: 'Document or account direction.'
example: received
required: false
schema:
type: string
description: 'Document or account direction.'
example: received
nullable: true
-
in: query
name: contact
description: "Contact id. Narrows outstanding documents to a single customer or supplier, prices item results with that customer's rate card, and on contact sources keeps that party in the results even when it is no longer active, so a form can still show the party it already holds."
example: 42
required: false
schema:
type: integer
description: "Contact id. Narrows outstanding documents to a single customer or supplier, prices item results with that customer's rate card, and on contact sources keeps that party in the results even when it is no longer active, so a form can still show the party it already holds."
example: 42
nullable: true
-
in: query
name: item
description: "Item id. Required for the `item_batches` source; returns that item's sellable lots, first-expiry-first-out."
example: 12
required: false
schema:
type: integer
description: "Item id. Required for the `item_batches` source; returns that item's sellable lots, first-expiry-first-out."
example: 12
nullable: true
-
in: query
name: supplier
description: "Supplier contact id, or `preferred`. On the `items` source, adds supplier_price, supplier_price_source, and supplier_name: that supplier's agreed rate or, failing that, the rate on their last purchase invoice. `preferred` uses each item's preferred supplier. Only for callers who may see purchase prices."
example: '17'
required: false
schema:
type: string
description: "Supplier contact id, or `preferred`. On the `items` source, adds supplier_price, supplier_price_source, and supplier_name: that supplier's agreed rate or, failing that, the rate on their last purchase invoice. `preferred` uses each item's preferred supplier. Only for callers who may see purchase prices."
example: '17'
nullable: true
-
in: query
name: warehouse
description: 'Warehouse id. On the `items` source, the pre-selected lot is the earliest-expiring one with stock in that warehouse; on `item_batches`, only lots with stock there are listed, with the quantity held there.'
example: 1
required: false
schema:
type: integer
description: 'Warehouse id. On the `items` source, the pre-selected lot is the earliest-expiring one with stock in that warehouse; on `item_batches`, only lots with stock there are listed, with the quantity held there.'
example: 1
nullable: true
-
in: query
name: ids
description: 'Item ids to price or re-read, up to 200. Every named item is returned, including items that are no longer active, so a form can reprice the lines it already holds in one request.'
example:
- 12
- 18
required: false
schema:
type: array
description: 'Item ids to price or re-read, up to 200. Every named item is returned, including items that are no longer active, so a form can reprice the lines it already holds in one request.'
example:
- 12
- 18
items:
type: integer
-
in: query
name: category_id
description: 'Tenant-owned item category.'
example: 1
required: false
schema:
type: integer
description: 'Tenant-owned item category.'
example: 1
-
in: query
name: subcategory_id
description: 'Tenant-owned subcategory; combine with category_id.'
example: 2
required: false
schema:
type: integer
description: 'Tenant-owned subcategory; combine with category_id.'
example: 2
-
in: query
name: brand_id
description: 'Tenant-owned item brand.'
example: 3
required: false
schema:
type: integer
description: 'Tenant-owned item brand.'
example: 3
-
in: query
name: manufacturer_id
description: 'Tenant-owned manufacturer.'
example: 4
required: false
schema:
type: integer
description: 'Tenant-owned manufacturer.'
example: 4
responses:
200:
description: ''
content:
application/json:
schema:
type: object
example:
data:
-
value: '42'
label: 'Priya Sharma'
meta: 'Customer · 9876543210'
attributes:
type: customer
phone: '9876543210'
meta:
source: customers
query: priya
has_more: false
properties:
data:
type: array
example:
-
value: '42'
label: 'Priya Sharma'
meta: 'Customer · 9876543210'
attributes:
type: customer
phone: '9876543210'
items:
type: object
properties:
value:
type: string
example: '42'
label:
type: string
example: 'Priya Sharma'
meta:
type: string
example: 'Customer · 9876543210'
attributes:
type: object
properties:
type:
type: string
example: customer
phone:
type: string
example: '9876543210'
meta:
type: object
properties:
source:
type: string
example: customers
description: 'Lookup source used for the response.'
query:
type: string
example: priya
description: 'Normalized search query.'
has_more:
type: boolean
example: false
description: 'Whether more matching values exist beyond this response.'
tags:
- Lookups
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
/api/v1/partners/team:
get:
summary: 'Get my team performance.'
operationId: getMyTeamPerformance
description: "Aggregate funnel, per-member clicks/sign-ups/paid accounts, platform earnings,\nteam rewards and retained amount. No referred-shop identity or contact details."
parameters:
-
in: query
name: period
description: '7d, 30d, 90d or all.'
example: 30d
required: false
schema:
type: string
description: '7d, 30d, 90d or all.'
example: 30d
-
in: header
name: Cookie
description: ''
example: 'dukanam-session={PARTNER_SESSION}'
schema:
type: string
responses:
200:
description: ''
content:
application/json:
schema:
type: object
example:
data:
member_count: 1
funnel:
clicks: 12
signups: 2
paid: 1
platform_earnings:
lifetime_paise: 39900
team_rewards:
lifetime_paise: 20000
retained_paise: 19900
members:
-
id: 8
name: 'Anita Rao'
status: active
team_role: associate
phone: '+919123456789'
email: null
funnel:
clicks: 12
signups: 2
paid: 1
earnings:
lifetime_paise: 20000
properties:
data:
type: object
properties:
member_count:
type: integer
example: 1
funnel:
type: object
properties:
clicks:
type: integer
example: 12
signups:
type: integer
example: 2
paid:
type: integer
example: 1
platform_earnings:
type: object
properties:
lifetime_paise:
type: integer
example: 39900
team_rewards:
type: object
properties:
lifetime_paise:
type: integer
example: 20000
retained_paise:
type: integer
example: 19900
members:
type: array
example:
-
id: 8
name: 'Anita Rao'
status: active
team_role: associate
phone: '+919123456789'
email: null
funnel:
clicks: 12
signups: 2
paid: 1
earnings:
lifetime_paise: 20000
items:
type: object
properties:
id:
type: integer
example: 8
name:
type: string
example: 'Anita Rao'
status:
type: string
example: active
team_role:
type: string
example: associate
phone:
type: string
example: '+919123456789'
email:
type: string
example: null
nullable: true
funnel:
type: object
properties:
clicks:
type: integer
example: 12
signups:
type: integer
example: 2
paid:
type: integer
example: 1
earnings:
type: object
properties:
lifetime_paise:
type: integer
example: 20000
403:
description: ''
content:
application/json:
schema:
type: object
example:
message: Forbidden
properties:
message:
type: string
example: Forbidden
tags:
- 'Master partner teams'
security:
-
partnerSession: []
/api/v1/partners/team/members:
post:
summary: 'Create my team member.'
operationId: createMyTeamMember
description: "Creates a new account belonging only to this master, with WhatsApp OTP login. It does\nnot send an invitation message; share /partners/login with the person. Existing\npartner numbers cannot be claimed. Members cannot create their own teams."
parameters:
-
in: header
name: Cookie
description: ''
example: 'dukanam-session={PARTNER_SESSION}'
schema:
type: string
-
name: X-CSRF-TOKEN
in: header
required: true
description: 'Session CSRF token, or send the X-XSRF-TOKEN equivalent.'
schema:
type: string
responses:
201:
description: ''
content:
application/json:
schema:
type: object
example:
data:
id: 8
name: 'Anita Rao'
phone: '+919123456789'
email: null
status: active
account_type: member
team_role: associate
master_partner_id: 3
properties:
data:
type: object
properties:
id:
type: integer
example: 8
name:
type: string
example: 'Anita Rao'
phone:
type: string
example: '+919123456789'
email:
type: string
example: null
nullable: true
status:
type: string
example: active
account_type:
type: string
example: member
team_role:
type: string
example: associate
master_partner_id:
type: integer
example: 3
422:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'Another partner already uses this number.'
errors:
phone:
- 'Another partner already uses this number.'
properties:
message:
type: string
example: 'Another partner already uses this number.'
errors:
type: object
properties:
phone:
type: array
example:
- 'Another partner already uses this number.'
items:
type: string
tags:
- 'Master partner teams'
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
name:
type: string
description: ''
example: 'Anita Rao'
phone:
type: string
description: 'Indian WhatsApp mobile.'
example: '9123456789'
email:
type: string
description: ''
example: anita@example.com
nullable: true
status:
type: string
description: 'active or suspended.'
example: active
team_role:
type: string
description: 'associate or employee.'
example: associate
account_type:
type: string
description: ''
example: null
master_partner_id:
type: string
description: ''
example: null
commission_type:
type: string
description: ''
example: null
commission_flat_amount:
type: string
description: ''
example: null
commission_percent:
type: string
description: ''
example: null
required:
- name
- phone
- team_role
security:
-
partnerSession: []
'/api/v1/partners/team/members/{member_id}':
get:
summary: 'Get my team member and link performance.'
operationId: getMyTeamMemberAndLinkPerformance
description: 'Includes eligible programs and reward balances, never platform admin notes or PAN.'
parameters:
-
in: header
name: Cookie
description: ''
example: 'dukanam-session={PARTNER_SESSION}'
schema:
type: string
responses:
200:
description: ''
content:
application/json:
schema:
type: object
example:
data:
member:
id: 8
name: 'Anita Rao'
master_partner_id: 3
funnel:
clicks: 12
signups: 2
paid: 1
earnings:
lifetime_paise: 20000
available_programs: []
links:
-
id: 4
code: ANITA4K
program_id: 2
label: WhatsApp
url: 'https://dukanam.com/p/ANITA4K'
destination: register
is_active: true
stats:
clicks: 12
signups: 2
paid: 1
properties:
data:
type: object
properties:
member:
type: object
properties:
id:
type: integer
example: 8
name:
type: string
example: 'Anita Rao'
master_partner_id:
type: integer
example: 3
funnel:
type: object
properties:
clicks:
type: integer
example: 12
signups:
type: integer
example: 2
paid:
type: integer
example: 1
earnings:
type: object
properties:
lifetime_paise:
type: integer
example: 20000
available_programs:
type: array
example: []
links:
type: array
example:
-
id: 4
code: ANITA4K
program_id: 2
label: WhatsApp
url: 'https://dukanam.com/p/ANITA4K'
destination: register
is_active: true
stats:
clicks: 12
signups: 2
paid: 1
items:
type: object
properties:
id:
type: integer
example: 4
code:
type: string
example: ANITA4K
program_id:
type: integer
example: 2
label:
type: string
example: WhatsApp
url:
type: string
example: 'https://dukanam.com/p/ANITA4K'
destination:
type: string
example: register
is_active:
type: boolean
example: true
stats:
type: object
properties:
clicks:
type: integer
example: 12
signups:
type: integer
example: 2
paid:
type: integer
example: 1
404:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'Not found.'
properties:
message:
type: string
example: 'Not found.'
tags:
- 'Master partner teams'
security:
-
partnerSession: []
put:
summary: 'Update my team member.'
operationId: updateMyTeamMember
description: "Only profile, phone, status and team_role may change. Parent, account type and\nplatform commission fields are prohibited. Suspending stops login and new attribution."
parameters:
-
in: header
name: Cookie
description: ''
example: 'dukanam-session={PARTNER_SESSION}'
schema:
type: string
-
name: X-CSRF-TOKEN
in: header
required: true
description: 'Session CSRF token, or send the X-XSRF-TOKEN equivalent.'
schema:
type: string
responses:
200:
description: ''
content:
application/json:
schema:
type: object
example:
data:
id: 8
name: 'Anita Rao'
phone: '+919123456789'
email: null
status: suspended
account_type: member
team_role: employee
master_partner_id: 3
properties:
data:
type: object
properties:
id:
type: integer
example: 8
name:
type: string
example: 'Anita Rao'
phone:
type: string
example: '+919123456789'
email:
type: string
example: null
nullable: true
status:
type: string
example: suspended
account_type:
type: string
example: member
team_role:
type: string
example: employee
master_partner_id:
type: integer
example: 3
tags:
- 'Master partner teams'
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
name:
type: string
description: ''
example: 'Anita Rao'
phone:
type: string
description: ''
example: '9123456789'
email:
type: string
description: ''
example: anita@example.com
nullable: true
status:
type: string
description: 'active or suspended.'
example: suspended
team_role:
type: string
description: 'associate or employee.'
example: employee
account_type:
type: string
description: ''
example: null
master_partner_id:
type: string
description: ''
example: null
commission_type:
type: string
description: ''
example: null
commission_flat_amount:
type: string
description: ''
example: null
commission_percent:
type: string
description: ''
example: null
required:
- team_role
security:
-
partnerSession: []
parameters:
-
in: path
name: member_id
description: 'The ID of the member.'
example: 1
required: true
schema:
type: integer
'/api/v1/partners/team/members/{member_id}/links':
post:
summary: 'Create a link for my team member.'
operationId: createALinkForMyTeamMember
description: "A team program eligible for this member is required. Default platform programs\nand other masters' programs cannot be used. Shared link limit is 25 per member."
parameters:
-
in: header
name: Cookie
description: ''
example: 'dukanam-session={PARTNER_SESSION}'
schema:
type: string
-
name: X-CSRF-TOKEN
in: header
required: true
description: 'Session CSRF token, or send the X-XSRF-TOKEN equivalent.'
schema:
type: string
responses:
201:
description: ''
content:
application/json:
schema:
type: object
example:
data:
id: 4
code: ANITA4K
program_id: 2
label: WhatsApp
url: 'https://dukanam.com/p/ANITA4K'
destination: register
is_active: true
created_at: '2026-10-06T12:00:00Z'
properties:
data:
type: object
properties:
id:
type: integer
example: 4
code:
type: string
example: ANITA4K
program_id:
type: integer
example: 2
label:
type: string
example: WhatsApp
url:
type: string
example: 'https://dukanam.com/p/ANITA4K'
destination:
type: string
example: register
is_active:
type: boolean
example: true
created_at:
type: string
example: '2026-10-06T12:00:00Z'
tags:
- 'Master partner teams'
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
program_id:
type: integer
description: ''
example: 2
nullable: true
label:
type: string
description: ''
example: WhatsApp
code:
type: string
description: 'Optional unique code, 4–16 letters/numbers.'
example: ANITA4K
nullable: true
destination:
type: string
description: 'register, home or pricing.'
example: register
nullable: true
required:
- program_id
- label
security:
-
partnerSession: []
parameters:
-
in: path
name: member_id
description: 'The ID of the member.'
example: 1
required: true
schema:
type: integer
'/api/v1/partners/team/members/{member_id}/links/{link_id}':
patch:
summary: "Pause or resume my member's link."
operationId: pauseOrResumeMyMembersLink
description: "Both member and link must belong to this master's team. Paused links stop new attribution."
parameters:
-
in: header
name: Cookie
description: ''
example: 'dukanam-session={PARTNER_SESSION}'
schema:
type: string
-
name: X-CSRF-TOKEN
in: header
required: true
description: 'Session CSRF token, or send the X-XSRF-TOKEN equivalent.'
schema:
type: string
responses:
200:
description: ''
content:
application/json:
schema:
type: object
example:
data:
id: 4
program_id: 2
code: ANITA4K
is_active: false
properties:
data:
type: object
properties:
id:
type: integer
example: 4
program_id:
type: integer
example: 2
code:
type: string
example: ANITA4K
is_active:
type: boolean
example: false
tags:
- 'Master partner teams'
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
is_active:
type: boolean
description: ''
example: false
required:
- is_active
security:
-
partnerSession: []
parameters:
-
in: path
name: member_id
description: 'The ID of the member.'
example: 1
required: true
schema:
type: integer
-
in: path
name: link_id
description: 'The ID of the link.'
example: 1
required: true
schema:
type: integer
/api/v1/partners/team/programs:
get:
summary: 'List my team programs.'
operationId: listMyTeamPrograms
description: "All/selected audiences apply only within this master's team. Includes paused and scheduled programs."
parameters:
-
in: header
name: Cookie
description: ''
example: 'dukanam-session={PARTNER_SESSION}'
schema:
type: string
responses:
200:
description: ''
content:
application/json:
schema:
type: object
example:
data:
-
id: 2
owner_master_partner_id: 3
name: 'Shop referrals'
audience: all
partner_ids: []
is_active: true
commission:
type: flat
flat_amount_paise: 20000
percent_basis_points: null
custom: false
description: '₹200.00 per paid account'
hold_days: 7
properties:
data:
type: array
example:
-
id: 2
owner_master_partner_id: 3
name: 'Shop referrals'
audience: all
partner_ids: []
is_active: true
commission:
type: flat
flat_amount_paise: 20000
percent_basis_points: null
custom: false
description: '₹200.00 per paid account'
hold_days: 7
items:
type: object
properties:
id:
type: integer
example: 2
owner_master_partner_id:
type: integer
example: 3
name:
type: string
example: 'Shop referrals'
audience:
type: string
example: all
partner_ids:
type: array
example: []
is_active:
type: boolean
example: true
commission:
type: object
properties:
type:
type: string
example: flat
flat_amount_paise:
type: integer
example: 20000
percent_basis_points:
type: string
example: null
nullable: true
custom:
type: boolean
example: false
description:
type: string
example: '₹200.00 per paid account'
hold_days:
type: integer
example: 7
tags:
- 'Master partner teams'
security:
-
partnerSession: []
post:
summary: 'Create my team program.'
operationId: createMyTeamProgram
description: "Fixed rewards must be below the master's fixed platform rate. Percent rewards\nuse the master's platform commission, not the shop payment, up to 99.99%.\nA rounding safeguard retains at least one paise for the master per conversion.\nTeam hold is the longer of program and master platform holds. Terms are saved at sign-up."
parameters:
-
in: header
name: Cookie
description: ''
example: 'dukanam-session={PARTNER_SESSION}'
schema:
type: string
-
name: X-CSRF-TOKEN
in: header
required: true
description: 'Session CSRF token, or send the X-XSRF-TOKEN equivalent.'
schema:
type: string
responses:
201:
description: ''
content:
application/json:
schema:
type: object
example:
data:
id: 2
owner_master_partner_id: 3
name: 'Shop referrals'
audience: all
partner_ids: []
is_active: true
commission:
type: flat
flat_amount_paise: 20000
percent_basis_points: null
custom: false
description: '₹200.00 per paid account'
hold_days: 7
properties:
data:
type: object
properties:
id:
type: integer
example: 2
owner_master_partner_id:
type: integer
example: 3
name:
type: string
example: 'Shop referrals'
audience:
type: string
example: all
partner_ids:
type: array
example: []
is_active:
type: boolean
example: true
commission:
type: object
properties:
type:
type: string
example: flat
flat_amount_paise:
type: integer
example: 20000
percent_basis_points:
type: string
example: null
nullable: true
custom:
type: boolean
example: false
description:
type: string
example: '₹200.00 per paid account'
hold_days:
type: integer
example: 7
422:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'Fixed team rewards must be lower than your fixed platform commission.'
errors:
flat_amount:
- 'Fixed team rewards must be lower than your fixed platform commission.'
properties:
message:
type: string
example: 'Fixed team rewards must be lower than your fixed platform commission.'
errors:
type: object
properties:
flat_amount:
type: array
example:
- 'Fixed team rewards must be lower than your fixed platform commission.'
items:
type: string
tags:
- 'Master partner teams'
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
name:
type: string
description: ''
example: 'Shop referrals'
description:
type: string
description: ''
example: 'Refer shops and earn rewards.'
is_active:
type: boolean
description: ''
example: true
audience:
type: string
description: 'all or selected within my team.'
example: all
partner_ids:
type: array
description: 'Required for selected; only my member IDs.'
example:
- 8
items:
type: integer
starts_at:
type: string
description: 'Nullable inclusive start.'
example: null
nullable: true
ends_at:
type: string
description: 'Nullable exclusive end, after start if supplied.'
example: null
nullable: true
commission_type:
type: string
description: 'flat or percent of master commission.'
example: flat
flat_amount:
type: number
description: 'Positive rupees, required for flat.'
example: 200.0
nullable: true
percent:
type: number
description: 'Positive percent below 100, required for percent.'
example: 50.0
nullable: true
hold_days:
type: integer
description: 0–180.
example: 7
owner_master_partner_id:
type: string
description: ''
example: null
required:
- name
- description
- is_active
- audience
- commission_type
- hold_days
security:
-
partnerSession: []
'/api/v1/partners/team/programs/{program_id}':
put:
summary: 'Replace my team program.'
operationId: replaceMyTeamProgram
description: "Uses the same full configuration and validation as creation. Prior referrals\nretain saved master and member terms. Omitted optional timestamps clear the window."
parameters:
-
in: header
name: Cookie
description: ''
example: 'dukanam-session={PARTNER_SESSION}'
schema:
type: string
-
name: X-CSRF-TOKEN
in: header
required: true
description: 'Session CSRF token, or send the X-XSRF-TOKEN equivalent.'
schema:
type: string
responses:
200:
description: ''
content:
application/json:
schema:
type: object
example:
data:
id: 2
owner_master_partner_id: 3
name: 'Shop referrals'
is_active: false
audience: all
partner_ids: []
commission:
type: flat
flat_amount_paise: 20000
percent_basis_points: null
custom: false
description: '₹200.00 per paid account'
hold_days: 7
properties:
data:
type: object
properties:
id:
type: integer
example: 2
owner_master_partner_id:
type: integer
example: 3
name:
type: string
example: 'Shop referrals'
is_active:
type: boolean
example: false
audience:
type: string
example: all
partner_ids:
type: array
example: []
commission:
type: object
properties:
type:
type: string
example: flat
flat_amount_paise:
type: integer
example: 20000
percent_basis_points:
type: string
example: null
nullable: true
custom:
type: boolean
example: false
description:
type: string
example: '₹200.00 per paid account'
hold_days:
type: integer
example: 7
tags:
- 'Master partner teams'
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
name:
type: string
description: 'Must not be greater than 80 characters.'
example: b
description:
type: string
description: 'Must not be greater than 2000 characters.'
example: 'Et animi quos velit et fugiat.'
is_active:
type: boolean
description: ''
example: false
audience:
type: string
description: ''
example: all
enum:
- all
- selected
partner_ids:
type: array
description: 'Must match an existing stored value.'
example:
- 16
items:
type: integer
starts_at:
type: string
description: 'Must be a valid date.'
example: '2026-01-15'
nullable: true
ends_at:
type: string
description: 'Must be a valid date.'
example: '2026-01-15'
nullable: true
commission_type:
type: string
description: ''
example: flat
enum:
- flat
- percent
flat_amount:
type: number
description: 'This field is required when commission_type is flat. Must be at least 0.01. Must not be greater than 1000000.'
example: 22
nullable: true
percent:
type: number
description: 'This field is required when commission_type is percent. Must be at least 0.01. Must not be greater than 99.99.'
example: 7
nullable: true
hold_days:
type: integer
description: 'Must be at least 0. Must not be greater than 180.'
example: 16
owner_master_partner_id:
type: string
description: ''
example: null
required:
- name
- description
- is_active
- audience
- partner_ids
- commission_type
- hold_days
security:
-
partnerSession: []
parameters:
-
in: path
name: program_id
description: 'The ID of the program.'
example: 1
required: true
schema:
type: integer
/api/v1/partners/team/settlements:
get:
summary: 'List my team settlements.'
operationId: listMyTeamSettlements
description: 'Processing, paid and cancelled manual transfers, separate from Dukanam payouts.'
parameters:
-
in: header
name: Cookie
description: ''
example: 'dukanam-session={PARTNER_SESSION}'
schema:
type: string
responses:
200:
description: ''
content:
application/json:
schema:
type: object
example:
data:
-
id: 1
master_partner_id: 3
partner_id: 8
status: processing
gross_paise: 20000
commissions_count: 1
account_label: 'UPI · an••@okhdfcbank'
reference: null
notes: null
created_by_partner_id: 3
paid_by_partner_id: null
created_at: '2026-10-06T12:00:00Z'
paid_at: null
cancelled_at: null
properties:
data:
type: array
example:
- id: 1
master_partner_id: 3
partner_id: 8
status: processing
gross_paise: 20000
commissions_count: 1
account_label: 'UPI · an••@okhdfcbank'
reference: null
notes: null
created_by_partner_id: 3
paid_by_partner_id: null
created_at: '2026-10-06T12:00:00Z'
paid_at: null
cancelled_at: null
items:
type: object
properties:
id:
type: integer
example: 1
master_partner_id:
type: integer
example: 3
partner_id:
type: integer
example: 8
status:
type: string
example: processing
gross_paise:
type: integer
example: 20000
commissions_count:
type: integer
example: 1
account_label:
type: string
example: 'UPI · an••@okhdfcbank'
reference:
type: string
example: null
nullable: true
notes:
type: string
example: null
nullable: true
created_by_partner_id:
type: integer
example: 3
paid_by_partner_id:
type: string
example: null
nullable: true
created_at:
type: string
example: '2026-10-06T12:00:00Z'
paid_at:
type: string
example: null
nullable: true
cancelled_at:
type: string
example: null
nullable: true
tags:
- 'Master partner teams'
security:
-
partnerSession: []
'/api/v1/partners/team/settlements/{settlement_id}':
get:
summary: "Get my settlement's payment details."
operationId: getMySettlementsPaymentDetails
description: "Full saved bank/UPI destination, only for the owning master, to make the external\ntransfer. List responses are masked. Later member edits never change this destination."
parameters:
-
in: header
name: Cookie
description: ''
example: 'dukanam-session={PARTNER_SESSION}'
schema:
type: string
responses:
200:
description: ''
content:
application/json:
schema:
type: object
example:
data:
id: 1
status: processing
gross_paise: 20000
account:
method: upi
account_holder_name: 'Anita Rao'
upi_id: anita@okhdfcbank
properties:
data:
type: object
properties:
id:
type: integer
example: 1
status:
type: string
example: processing
gross_paise:
type: integer
example: 20000
account:
type: object
properties:
method:
type: string
example: upi
account_holder_name:
type: string
example: 'Anita Rao'
upi_id:
type: string
example: anita@okhdfcbank
tags:
- 'Master partner teams'
security:
-
partnerSession: []
parameters:
-
in: path
name: settlement_id
description: 'The ID of the settlement.'
example: 1
required: true
schema:
type: integer
'/api/v1/partners/team/members/{member_id}/settlements':
post:
summary: "Prepare my member's settlement."
operationId: prepareMyMembersSettlement
description: "Reserves all approved unsettled rewards for this member. Requires completed profile\nand primary payout account; keeps an encrypted destination snapshot. Duplicate\npreparation without new rewards returns 422. Does not transfer money or reserve\nany Dukanam payout. Total is the full reward amount with no automatic withholding."
parameters:
-
in: header
name: Cookie
description: ''
example: 'dukanam-session={PARTNER_SESSION}'
schema:
type: string
-
name: X-CSRF-TOKEN
in: header
required: true
description: 'Session CSRF token, or send the X-XSRF-TOKEN equivalent.'
schema:
type: string
responses:
201:
description: ''
content:
application/json:
schema:
type: object
example:
data:
id: 1
master_partner_id: 3
partner_id: 8
status: processing
gross_paise: 20000
commissions_count: 1
account_label: 'UPI · an••@okhdfcbank'
reference: null
notes: null
created_by_partner_id: 3
paid_by_partner_id: null
created_at: '2026-10-06T12:00:00Z'
paid_at: null
cancelled_at: null
properties:
data:
type: object
properties:
id:
type: integer
example: 1
master_partner_id:
type: integer
example: 3
partner_id:
type: integer
example: 8
status:
type: string
example: processing
gross_paise:
type: integer
example: 20000
commissions_count:
type: integer
example: 1
account_label:
type: string
example: 'UPI · an••@okhdfcbank'
reference:
type: string
example: null
nullable: true
notes:
type: string
example: null
nullable: true
created_by_partner_id:
type: integer
example: 3
paid_by_partner_id:
type: string
example: null
nullable: true
created_at:
type: string
example: '2026-10-06T12:00:00Z'
paid_at:
type: string
example: null
nullable: true
cancelled_at:
type: string
example: null
nullable: true
422:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'No eligible rewards or missing payment details.'
errors:
settlement:
- 'No eligible rewards or missing payment details.'
properties:
message:
type: string
example: 'No eligible rewards or missing payment details.'
errors:
type: object
properties:
settlement:
type: array
example:
- 'No eligible rewards or missing payment details.'
items:
type: string
tags:
- 'Master partner teams'
security:
-
partnerSession: []
parameters:
-
in: path
name: member_id
description: 'The ID of the member.'
example: 1
required: true
schema:
type: integer
'/api/v1/partners/team/settlements/{settlement_id}/mark-paid':
post:
summary: 'Mark my settlement paid.'
operationId: markMySettlementPaid
description: "Only processing records. Stores reference, date and acting master; marks reserved\nmember rewards paid. Repeated payment returns 422. Transfer happens outside Dukanam."
parameters:
-
in: header
name: Cookie
description: ''
example: 'dukanam-session={PARTNER_SESSION}'
schema:
type: string
-
name: X-CSRF-TOKEN
in: header
required: true
description: 'Session CSRF token, or send the X-XSRF-TOKEN equivalent.'
schema:
type: string
responses:
200:
description: ''
content:
application/json:
schema:
type: object
example:
data:
id: 1
master_partner_id: 3
partner_id: 8
status: paid
gross_paise: 20000
reference: UTR123456
paid_by_partner_id: 3
paid_at: '2026-10-06T12:00:00Z'
properties:
data:
type: object
properties:
id:
type: integer
example: 1
master_partner_id:
type: integer
example: 3
partner_id:
type: integer
example: 8
status:
type: string
example: paid
gross_paise:
type: integer
example: 20000
reference:
type: string
example: UTR123456
paid_by_partner_id:
type: integer
example: 3
paid_at:
type: string
example: '2026-10-06T12:00:00Z'
tags:
- 'Master partner teams'
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
reference:
type: string
description: 'Bank/UPI transfer reference, max 100.'
example: UTR123456
paid_at:
type: string
description: 'Optional payment date/time, cannot be future.'
example: null
notes:
type: string
description: 'Optional notes, max 2000.'
example: 'Paid by UPI'
required:
- reference
security:
-
partnerSession: []
parameters:
-
in: path
name: settlement_id
description: 'The ID of the settlement.'
example: 1
required: true
schema:
type: integer
'/api/v1/partners/team/settlements/{settlement_id}/cancel':
post:
summary: 'Cancel my processing settlement.'
operationId: cancelMyProcessingSettlement
description: 'Frees its rewards for a future settlement. A paid record cannot be cancelled.'
parameters:
-
in: header
name: Cookie
description: ''
example: 'dukanam-session={PARTNER_SESSION}'
schema:
type: string
-
name: X-CSRF-TOKEN
in: header
required: true
description: 'Session CSRF token, or send the X-XSRF-TOKEN equivalent.'
schema:
type: string
responses:
200:
description: ''
content:
application/json:
schema:
type: object
example:
data:
id: 1
master_partner_id: 3
partner_id: 8
status: cancelled
gross_paise: 20000
cancelled_at: '2026-10-06T12:00:00Z'
properties:
data:
type: object
properties:
id:
type: integer
example: 1
master_partner_id:
type: integer
example: 3
partner_id:
type: integer
example: 8
status:
type: string
example: cancelled
gross_paise:
type: integer
example: 20000
cancelled_at:
type: string
example: '2026-10-06T12:00:00Z'
tags:
- 'Master partner teams'
requestBody:
required: false
content:
application/json:
schema:
type: object
properties:
notes:
type: string
description: 'Optional reason, max 2000.'
example: 'Wrong account; prepare again'
security:
-
partnerSession: []
parameters:
-
in: path
name: settlement_id
description: 'The ID of the settlement.'
example: 1
required: true
schema:
type: integer
/api/v1/partners/me:
get:
summary: 'Get my partner dashboard.'
operationId: getMyPartnerDashboard
description: "Shop identities and internal notes are excluded. Earnings are lifetime; funnel\nuses the requested period. Team members never see other members' balances."
parameters:
-
in: query
name: period
description: '7d, 30d, 90d or all.'
example: 30d
required: false
schema:
type: string
description: '7d, 30d, 90d or all.'
example: 30d
-
in: header
name: Cookie
description: ''
example: 'dukanam-session={PARTNER_SESSION}'
schema:
type: string
responses:
200:
description: ''
content:
application/json:
schema:
type: object
example:
data:
partner:
id: 8
name: 'Anita Rao'
account_type: member
team_role: associate
master_partner_id: 3
available_programs:
-
id: 2
name: 'Shop referrals'
commission:
type: flat
flat_amount_paise: 20000
funnel:
clicks: 12
signups: 2
paid: 1
earnings:
on_hold_paise: 0
available_paise: 20000
processing_paise: 0
paid_paise: 0
lifetime_paise: 20000
properties:
data:
type: object
properties:
partner:
type: object
properties:
id:
type: integer
example: 8
name:
type: string
example: 'Anita Rao'
account_type:
type: string
example: member
team_role:
type: string
example: associate
master_partner_id:
type: integer
example: 3
available_programs:
type: array
example:
-
id: 2
name: 'Shop referrals'
commission:
type: flat
flat_amount_paise: 20000
items:
type: object
properties:
id:
type: integer
example: 2
name:
type: string
example: 'Shop referrals'
commission:
type: object
properties:
type:
type: string
example: flat
flat_amount_paise:
type: integer
example: 20000
funnel:
type: object
properties:
clicks:
type: integer
example: 12
signups:
type: integer
example: 2
paid:
type: integer
example: 1
earnings:
type: object
properties:
on_hold_paise:
type: integer
example: 0
available_paise:
type: integer
example: 20000
processing_paise:
type: integer
example: 0
paid_paise:
type: integer
example: 0
lifetime_paise:
type: integer
example: 20000
tags:
- 'Partner portal'
security:
-
partnerSession: []
/api/v1/partners/me/links:
get:
summary: 'List my links and performance.'
operationId: listMyLinksAndPerformance
description: ''
parameters:
-
in: header
name: Cookie
description: ''
example: 'dukanam-session={PARTNER_SESSION}'
schema:
type: string
responses:
200:
description: ''
content:
application/json:
schema:
type: object
example:
data:
-
id: 4
program_id: 2
code: ANITA4K
label: WhatsApp
url: 'https://dukanam.com/p/ANITA4K'
destination: register
is_active: true
stats:
clicks: 12
signups: 2
paid: 1
share:
code: ANITA4K
link: 'https://dukanam.com/p/ANITA4K'
message: 'Try Dukanam'
whatsapp_url: 'https://wa.me/?text=Try%20Dukanam'
properties:
data:
type: array
example:
-
id: 4
program_id: 2
code: ANITA4K
label: WhatsApp
url: 'https://dukanam.com/p/ANITA4K'
destination: register
is_active: true
stats:
clicks: 12
signups: 2
paid: 1
share:
code: ANITA4K
link: 'https://dukanam.com/p/ANITA4K'
message: 'Try Dukanam'
whatsapp_url: 'https://wa.me/?text=Try%20Dukanam'
items:
type: object
properties:
id:
type: integer
example: 4
program_id:
type: integer
example: 2
code:
type: string
example: ANITA4K
label:
type: string
example: WhatsApp
url:
type: string
example: 'https://dukanam.com/p/ANITA4K'
destination:
type: string
example: register
is_active:
type: boolean
example: true
stats:
type: object
properties:
clicks:
type: integer
example: 12
signups:
type: integer
example: 2
paid:
type: integer
example: 1
share:
type: object
properties:
code:
type: string
example: ANITA4K
link:
type: string
example: 'https://dukanam.com/p/ANITA4K'
message:
type: string
example: 'Try Dukanam'
whatsapp_url:
type: string
example: 'https://wa.me/?text=Try%20Dukanam'
tags:
- 'Partner portal'
security:
-
partnerSession: []
post:
summary: 'Create my program link.'
operationId: createMyProgramLink
description: "Team members must choose an active eligible team program. Individual/master\npartners may omit program_id for their default platform deal. Maximum 25 links."
parameters:
-
in: header
name: Cookie
description: ''
example: 'dukanam-session={PARTNER_SESSION}'
schema:
type: string
-
name: X-CSRF-TOKEN
in: header
required: true
description: 'Session CSRF token, or send the X-XSRF-TOKEN equivalent.'
schema:
type: string
responses:
201:
description: ''
content:
application/json:
schema:
type: object
example:
data:
id: 4
program_id: 2
code: ANITA4K
label: WhatsApp
url: 'https://dukanam.com/p/ANITA4K'
destination: register
is_active: true
properties:
data:
type: object
properties:
id:
type: integer
example: 4
program_id:
type: integer
example: 2
code:
type: string
example: ANITA4K
label:
type: string
example: WhatsApp
url:
type: string
example: 'https://dukanam.com/p/ANITA4K'
destination:
type: string
example: register
is_active:
type: boolean
example: true
tags:
- 'Partner portal'
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
program_id:
type: integer
description: 'Eligible program; required for team members.'
example: 2
nullable: true
label:
type: string
description: ''
example: WhatsApp
code:
type: string
description: 'Optional unique letters/numbers, 4–16 characters.'
example: ANITA4K
nullable: true
destination:
type: string
description: 'register, home or pricing.'
example: register
nullable: true
required:
- label
security:
-
partnerSession: []
'/api/v1/partners/me/links/{link_id}':
patch:
summary: 'Update my link.'
operationId: updateMyLink
description: "Only your links; code, program and ownership cannot be changed. A paused link\nstill redirects, but stops attribution of new clicks and sign-ups."
parameters:
-
in: header
name: Cookie
description: ''
example: 'dukanam-session={PARTNER_SESSION}'
schema:
type: string
-
name: X-CSRF-TOKEN
in: header
required: true
description: 'Session CSRF token, or send the X-XSRF-TOKEN equivalent.'
schema:
type: string
responses:
200:
description: ''
content:
application/json:
schema:
type: object
example:
data:
id: 4
program_id: 2
code: ANITA4K
label: Instagram
destination: register
is_active: false
properties:
data:
type: object
properties:
id:
type: integer
example: 4
program_id:
type: integer
example: 2
code:
type: string
example: ANITA4K
label:
type: string
example: Instagram
destination:
type: string
example: register
is_active:
type: boolean
example: false
404:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'Not found.'
properties:
message:
type: string
example: 'Not found.'
tags:
- 'Partner portal'
requestBody:
required: false
content:
application/json:
schema:
type: object
properties:
label:
type: string
description: 'Optional new label, max 80.'
example: Instagram
destination:
type: string
description: 'register, home or pricing.'
example: register
is_active:
type: boolean
description: 'Pause or resume.'
example: false
security:
-
partnerSession: []
parameters:
-
in: path
name: link_id
description: 'The ID of the link.'
example: 1
required: true
schema:
type: integer
/api/v1/partners/me/commissions:
get:
summary: 'List my earnings history.'
operationId: listMyEarningsHistory
description: "Paginated commissions, using member rewards for a team account. Excludes shop\nidentities and the master's gross platform commission. All amounts are paise.\npartner_referral_id is null if the referred account has been deleted."
parameters:
-
in: header
name: Cookie
description: ''
example: 'dukanam-session={PARTNER_SESSION}'
schema:
type: string
responses:
200:
description: ''
content:
application/json:
schema:
type: object
example:
data:
-
id: 1
partner_referral_id: 9
amount_paise: 20000
status: approved
program_name: 'Shop referrals'
earned_at: '2026-10-06T12:00:00Z'
hold_until: '2026-10-06T12:00:00Z'
paid_at: null
rejection_reason: null
current_page: 1
per_page: 25
total: 1
properties:
data:
type: array
example:
- id: 1
partner_referral_id: 9
amount_paise: 20000
status: approved
program_name: 'Shop referrals'
earned_at: '2026-10-06T12:00:00Z'
hold_until: '2026-10-06T12:00:00Z'
paid_at: null
rejection_reason: null
items:
type: object
properties:
id:
type: integer
example: 1
partner_referral_id:
type: integer
example: 9
amount_paise:
type: integer
example: 20000
status:
type: string
example: approved
program_name:
type: string
example: 'Shop referrals'
earned_at:
type: string
example: '2026-10-06T12:00:00Z'
hold_until:
type: string
example: '2026-10-06T12:00:00Z'
paid_at:
type: string
example: null
nullable: true
rejection_reason:
type: string
example: null
nullable: true
current_page:
type: integer
example: 1
per_page:
type: integer
example: 25
total:
type: integer
example: 1
tags:
- 'Partner portal'
security:
-
partnerSession: []
/api/v1/partners/me/payments:
get:
summary: 'List my payments.'
operationId: listMyPayments
description: "Team accounts receive master-funded manual settlement records; other partners\nreceive platform payout records. Saved payment destinations are masked."
parameters:
-
in: header
name: Cookie
description: ''
example: 'dukanam-session={PARTNER_SESSION}'
schema:
type: string
responses:
200:
description: ''
content:
application/json:
schema:
type: object
example:
data:
-
id: 1
master_partner_id: 3
partner_id: 8
status: paid
gross_paise: 20000
account_label: 'UPI · an••@ybl'
reference: UTR123456
paid_at: '2026-10-06T12:00:00Z'
properties:
data:
type: array
example:
- id: 1
master_partner_id: 3
partner_id: 8
status: paid
gross_paise: 20000
account_label: 'UPI · an••@ybl'
reference: UTR123456
paid_at: '2026-10-06T12:00:00Z'
items:
type: object
properties:
id:
type: integer
example: 1
master_partner_id:
type: integer
example: 3
partner_id:
type: integer
example: 8
status:
type: string
example: paid
gross_paise:
type: integer
example: 20000
account_label:
type: string
example: 'UPI · an••@ybl'
reference:
type: string
example: UTR123456
paid_at:
type: string
example: '2026-10-06T12:00:00Z'
tags:
- 'Partner portal'
security:
-
partnerSession: []
/api/v1/admin/partner-programs:
get:
summary: 'List named partner programs.'
operationId: listNamedPartnerPrograms
description: "Includes platform-owned drafts, paused, future and expired programs for administration.\nMaster-owned programs are managed only through the owning master’s team API.\nThe default program is managed by /admin/partner-program and has no database ID."
parameters:
-
in: query
name: per_page
description: 'Page size, from 1 to 50.'
example: 25
required: false
schema:
type: integer
description: 'Page size, from 1 to 50.'
example: 25
responses:
200:
description: ''
content:
application/json:
schema:
type: object
example:
data:
-
id: 1
owner_master_partner_id: null
name: 'Creator campaign'
description: 'Help shops discover Dukanam.'
is_active: true
audience: selected
partner_ids:
- 3
starts_at: null
ends_at: null
commission:
type: percent
flat_amount_paise: null
percent_basis_points: 2500
custom: false
description: '25% of the first payment'
hold_days: 15
created_at: '2026-10-06T12:00:00.000000Z'
properties:
data:
type: array
example:
-
id: 1
owner_master_partner_id: null
name: 'Creator campaign'
description: 'Help shops discover Dukanam.'
is_active: true
audience: selected
partner_ids:
- 3
starts_at: null
ends_at: null
commission:
type: percent
flat_amount_paise: null
percent_basis_points: 2500
custom: false
description: '25% of the first payment'
hold_days: 15
created_at: '2026-10-06T12:00:00.000000Z'
items:
type: object
properties:
id:
type: integer
example: 1
owner_master_partner_id:
type: string
example: null
nullable: true
name:
type: string
example: 'Creator campaign'
description:
type: string
example: 'Help shops discover Dukanam.'
is_active:
type: boolean
example: true
audience:
type: string
example: selected
partner_ids:
type: array
example:
- 3
items:
type: integer
starts_at:
type: string
example: null
nullable: true
ends_at:
type: string
example: null
nullable: true
commission:
type: object
properties:
type:
type: string
example: percent
flat_amount_paise:
type: string
example: null
nullable: true
percent_basis_points:
type: integer
example: 2500
custom:
type: boolean
example: false
description:
type: string
example: '25% of the first payment'
hold_days:
type: integer
example: 15
created_at:
type: string
example: '2026-10-06T12:00:00.000000Z'
tags:
- 'Partner program administration'
requestBody:
required: false
content:
application/json:
schema:
type: object
properties:
per_page:
type: integer
description: 'Must be at least 1. Must not be greater than 50.'
example: 1
nullable: true
post:
summary: 'Create a named partner program.'
operationId: createANamedPartnerProgram
description: ''
parameters: []
responses:
201:
description: ''
content:
application/json:
schema:
type: object
example:
data:
id: 1
owner_master_partner_id: null
name: 'Creator campaign'
description: 'Help shops discover Dukanam.'
is_active: true
audience: selected
partner_ids:
- 3
starts_at: null
ends_at: null
commission:
type: percent
flat_amount_paise: null
percent_basis_points: 2500
custom: false
description: '25% of the first payment'
hold_days: 15
created_at: '2026-10-06T12:00:00.000000Z'
properties:
data:
type: object
properties:
id:
type: integer
example: 1
owner_master_partner_id:
type: string
example: null
nullable: true
name:
type: string
example: 'Creator campaign'
description:
type: string
example: 'Help shops discover Dukanam.'
is_active:
type: boolean
example: true
audience:
type: string
example: selected
partner_ids:
type: array
example:
- 3
items:
type: integer
starts_at:
type: string
example: null
nullable: true
ends_at:
type: string
example: null
nullable: true
commission:
type: object
properties:
type:
type: string
example: percent
flat_amount_paise:
type: string
example: null
nullable: true
percent_basis_points:
type: integer
example: 2500
custom:
type: boolean
example: false
description:
type: string
example: '25% of the first payment'
hold_days:
type: integer
example: 15
created_at:
type: string
example: '2026-10-06T12:00:00.000000Z'
422:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'The selected audience is invalid.'
errors:
audience:
- 'The selected audience is invalid.'
properties:
message:
type: string
example: 'The selected audience is invalid.'
errors:
type: object
properties:
audience:
type: array
example:
- 'The selected audience is invalid.'
items:
type: string
tags:
- 'Partner program administration'
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
name:
type: string
description: 'Program name (up to 80 characters).'
example: 'Creator campaign'
description:
type: string
description: 'Partner-facing details (up to 2000 characters).'
example: 'Help shops discover Dukanam.'
is_active:
type: boolean
description: 'Enable availability during the scheduled window.'
example: true
audience:
type: string
description: 'all or selected.'
example: selected
partner_ids:
type: array
description: 'Required and nonempty for selected; existing individual/master IDs, no team members or duplicates.'
example:
- 3
items:
type: integer
starts_at:
type: string
description: 'Optional start timestamp, inclusive; null for immediate availability.'
example: null
nullable: true
ends_at:
type: string
description: 'Optional end timestamp, exclusive; must follow starts_at when both are supplied.'
example: null
nullable: true
commission_type:
type: string
description: "flat, percent or first_month. Named program terms override the partner's personal deal for program links."
example: percent
flat_amount:
type: number
description: 'Required for flat, positive rupees up to 1000000.'
example: 250.0
nullable: true
percent:
type: number
description: 'Required for percent, positive percentage up to 100.'
example: 25.0
nullable: true
hold_days:
type: integer
description: 'Refund hold in days, from 0 to 180.'
example: 15
required:
- name
- description
- is_active
- audience
- commission_type
- hold_days
'/api/v1/admin/partner-programs/{program_id}':
get:
summary: 'Get a named partner program.'
operationId: getANamedPartnerProgram
description: ''
parameters: []
responses:
200:
description: ''
content:
application/json:
schema:
type: object
example:
data:
id: 1
owner_master_partner_id: null
name: 'Creator campaign'
description: 'Help shops discover Dukanam.'
is_active: true
audience: selected
partner_ids:
- 3
starts_at: null
ends_at: null
commission:
type: percent
flat_amount_paise: null
percent_basis_points: 2500
custom: false
description: '25% of the first payment'
hold_days: 15
created_at: '2026-10-06T12:00:00.000000Z'
properties:
data:
type: object
properties:
id:
type: integer
example: 1
owner_master_partner_id:
type: string
example: null
nullable: true
name:
type: string
example: 'Creator campaign'
description:
type: string
example: 'Help shops discover Dukanam.'
is_active:
type: boolean
example: true
audience:
type: string
example: selected
partner_ids:
type: array
example:
- 3
items:
type: integer
starts_at:
type: string
example: null
nullable: true
ends_at:
type: string
example: null
nullable: true
commission:
type: object
properties:
type:
type: string
example: percent
flat_amount_paise:
type: string
example: null
nullable: true
percent_basis_points:
type: integer
example: 2500
custom:
type: boolean
example: false
description:
type: string
example: '25% of the first payment'
hold_days:
type: integer
example: 15
created_at:
type: string
example: '2026-10-06T12:00:00.000000Z'
404:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'Not found.'
properties:
message:
type: string
example: 'Not found.'
tags:
- 'Partner program administration'
put:
summary: 'Replace a named partner program.'
operationId: replaceANamedPartnerProgram
description: "Send the full configuration. Pausing, expiry or removing an eligible partner\nstops new attribution. Already attributed referrals retain a snapshot of the\nprogram reward and hold period from sign-up, even if the program changes later.\nSuspending the partner still blocks commissions. Omitted timestamps clear the window."
parameters: []
responses:
200:
description: ''
content:
application/json:
schema:
type: object
example:
data:
id: 1
owner_master_partner_id: null
name: 'Creator campaign'
description: 'Help shops discover Dukanam.'
is_active: true
audience: selected
partner_ids:
- 3
starts_at: null
ends_at: null
commission:
type: percent
flat_amount_paise: null
percent_basis_points: 2500
custom: false
description: '25% of the first payment'
hold_days: 15
created_at: '2026-10-06T12:00:00.000000Z'
properties:
data:
type: object
properties:
id:
type: integer
example: 1
owner_master_partner_id:
type: string
example: null
nullable: true
name:
type: string
example: 'Creator campaign'
description:
type: string
example: 'Help shops discover Dukanam.'
is_active:
type: boolean
example: true
audience:
type: string
example: selected
partner_ids:
type: array
example:
- 3
items:
type: integer
starts_at:
type: string
example: null
nullable: true
ends_at:
type: string
example: null
nullable: true
commission:
type: object
properties:
type:
type: string
example: percent
flat_amount_paise:
type: string
example: null
nullable: true
percent_basis_points:
type: integer
example: 2500
custom:
type: boolean
example: false
description:
type: string
example: '25% of the first payment'
hold_days:
type: integer
example: 15
created_at:
type: string
example: '2026-10-06T12:00:00.000000Z'
422:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'The ends at field must be a date after starts at.'
errors:
ends_at:
- 'The ends at field must be a date after starts at.'
properties:
message:
type: string
example: 'The ends at field must be a date after starts at.'
errors:
type: object
properties:
ends_at:
type: array
example:
- 'The ends at field must be a date after starts at.'
items:
type: string
tags:
- 'Partner program administration'
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
name:
type: string
description: 'Program name (up to 80 characters).'
example: 'Creator campaign'
description:
type: string
description: 'Partner-facing details (up to 2000 characters).'
example: 'Help shops discover Dukanam.'
is_active:
type: boolean
description: 'Enable availability during the scheduled window.'
example: true
audience:
type: string
description: 'all or selected.'
example: selected
partner_ids:
type: array
description: 'Required and nonempty for selected; existing individual/master IDs, no team members or duplicates.'
example:
- 3
items:
type: integer
starts_at:
type: string
description: 'Optional start timestamp, inclusive; null for immediate availability.'
example: null
nullable: true
ends_at:
type: string
description: 'Optional end timestamp, exclusive; must follow starts_at when both are supplied.'
example: null
nullable: true
commission_type:
type: string
description: "flat, percent or first_month. Named program terms override the partner's personal deal for program links."
example: percent
flat_amount:
type: number
description: 'Required for flat, positive rupees up to 1000000.'
example: 250.0
nullable: true
percent:
type: number
description: 'Required for percent, positive percentage up to 100.'
example: 25.0
nullable: true
hold_days:
type: integer
description: 'Refund hold in days, from 0 to 180.'
example: 15
required:
- name
- description
- is_active
- audience
- commission_type
- hold_days
parameters:
-
in: path
name: program_id
description: 'The ID of the program.'
example: 1
required: true
schema:
type: integer
/api/v1/admin/partner-program:
get:
summary: 'Get partner program settings.'
operationId: getPartnerProgramSettings
description: "The default commission deal, the refund hold period, the minimum payout, the TDS\nrate applied to payouts and the share message, with program-wide totals."
parameters: []
responses:
200:
description: ''
content:
application/json:
schema:
type: object
example:
data:
enabled: true
commission_type: flat
flat_amount_paise: 20000
percent_basis_points: 2000
hold_days: 30
minimum_payout_paise: 50000
tds_basis_points: 0
share_message: 'I use Dukanam for billing, stock and khata. Try it free: {link}'
placeholders:
- '{link}'
- '{code}'
- '{name}'
totals:
partners: 4
clicks: 1820
signups: 96
paid: 14
on_hold_paise: 120000
available_paise: 80000
processing_paise: 0
paid_out_net_paise: 160000
properties:
data:
type: object
properties:
enabled:
type: boolean
example: true
commission_type:
type: string
example: flat
flat_amount_paise:
type: integer
example: 20000
percent_basis_points:
type: integer
example: 2000
hold_days:
type: integer
example: 30
minimum_payout_paise:
type: integer
example: 50000
tds_basis_points:
type: integer
example: 0
share_message:
type: string
example: 'I use Dukanam for billing, stock and khata. Try it free: {link}'
placeholders:
type: array
example:
- '{link}'
- '{code}'
- '{name}'
items:
type: string
totals:
type: object
properties:
partners:
type: integer
example: 4
clicks:
type: integer
example: 1820
signups:
type: integer
example: 96
paid:
type: integer
example: 14
on_hold_paise:
type: integer
example: 120000
available_paise:
type: integer
example: 80000
processing_paise:
type: integer
example: 0
paid_out_net_paise:
type: integer
example: 160000
tags:
- 'Partner program administration'
put:
summary: 'Update partner program settings.'
operationId: updatePartnerProgramSettings
description: "Amounts in rupees and rates in percent, as the admin console takes them. Applies to\ncommissions earned and payouts prepared from now on; each existing commission keeps\nthe rule it was earned under. Turning the program off stops new clicks and sign-ups\nbeing credited; accounts already credited still earn when they pay."
parameters: []
responses:
200:
description: ''
content:
application/json:
schema:
type: object
example:
data:
enabled: true
commission_type: percent
flat_amount_paise: 20000
percent_basis_points: 2500
hold_days: 15
minimum_payout_paise: 50000
tds_basis_points: 200
share_message: 'Try Dukanam free: {link}'
placeholders:
- '{link}'
- '{code}'
- '{name}'
totals:
partners: 4
clicks: 1820
signups: 96
paid: 14
on_hold_paise: 120000
available_paise: 80000
processing_paise: 0
paid_out_net_paise: 160000
properties:
data:
type: object
properties:
enabled:
type: boolean
example: true
commission_type:
type: string
example: percent
flat_amount_paise:
type: integer
example: 20000
percent_basis_points:
type: integer
example: 2500
hold_days:
type: integer
example: 15
minimum_payout_paise:
type: integer
example: 50000
tds_basis_points:
type: integer
example: 200
share_message:
type: string
example: 'Try Dukanam free: {link}'
placeholders:
type: array
example:
- '{link}'
- '{code}'
- '{name}'
items:
type: string
totals:
type: object
properties:
partners:
type: integer
example: 4
clicks:
type: integer
example: 1820
signups:
type: integer
example: 96
paid:
type: integer
example: 14
on_hold_paise:
type: integer
example: 120000
available_paise:
type: integer
example: 80000
processing_paise:
type: integer
example: 0
paid_out_net_paise:
type: integer
example: 160000
422:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'The commission type field is required.'
errors:
commission_type:
- 'The commission type field is required.'
properties:
message:
type: string
example: 'The commission type field is required.'
errors:
type: object
properties:
commission_type:
type: array
example:
- 'The commission type field is required.'
items:
type: string
tags:
- 'Partner program administration'
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
enabled:
type: boolean
description: ''
example: true
commission_type:
type: string
description: "flat, percent or first_month (one month of the bought plan's current monthly price, however the shop pays)."
example: flat
flat_amount:
type: number
description: 'Rupees per paid account for flat deals.'
example: 200.0
percent:
type: number
description: 'Percent of the first payment (before GST) for percent deals.'
example: 20.0
hold_days:
type: integer
description: 'Days a commission is held before it can be paid, 0–180.'
example: 30
minimum_payout:
type: number
description: 'Rupees a partner must have approved before a payout is prepared.'
example: 500.0
tds_percent:
type: number
description: 'Tax deducted at source on each payout, 0–30.'
example: 2.0
share_message:
type: string
description: 'Up to 1000 characters; {link}, {code} and {name} are filled in.'
example: 'Try Dukanam free: {link}'
required:
- enabled
- commission_type
- flat_amount
- percent
- hold_days
- minimum_payout
- tds_percent
- share_message
/api/v1/admin/partners:
get:
summary: 'List partners.'
operationId: listPartners
description: "Every partner with their deal and a leaderboard row for the period: human clicks,\nsign-ups and paid accounts in the period, and lifetime money earned (not rejected),\nowed (on hold or approved, not yet paid) and paid out net of TDS."
parameters:
-
in: query
name: search
description: 'Match the name, email or a link code.'
example: ravi
required: false
schema:
type: string
description: 'Match the name, email or a link code.'
example: ravi
-
in: query
name: status
description: 'active or suspended.'
example: active
required: false
schema:
type: string
description: 'active or suspended.'
example: active
-
in: query
name: period
description: '7d, 30d, 90d or all. Defaults to 30d.'
example: 30d
required: false
schema:
type: string
description: '7d, 30d, 90d or all. Defaults to 30d.'
example: 30d
-
in: query
name: per_page
description: 'Results per page, from 1 to 50.'
example: 25
required: false
schema:
type: integer
description: 'Results per page, from 1 to 50.'
example: 25
responses:
200:
description: ''
content:
application/json:
schema:
type: object
example:
data:
-
id: 3
name: 'Ravi Kumar'
email: ravi@example.com
phone: '+919876543210'
pan_on_file: true
pan_masked: ••••••234F
status: active
signup_source: invited
profile_completed_at: '2026-09-29T10:00:00.000000Z'
commission:
type: flat
flat_amount_paise: 25000
percent_basis_points: null
custom: true
description: '₹250.00 per paid account'
notes: 'YouTube, 80k subscribers'
links_count: 3
has_payout_account: true
last_login_at: '2026-09-28T10:00:00.000000Z'
created_at: '2026-09-01T10:00:00.000000Z'
stats:
clicks: 640
signups: 31
paid: 6
earned_paise: 150000
owed_paise: 50000
paid_out_net_paise: 100000
account_type: individual
master_partner_id: null
team_role: null
payment_responsibility: platform
platform_payout_terms:
hold_days: 30
minimum_payout_paise: 50000
tds_basis_points: 0
links: {}
meta:
current_page: 1
per_page: 25
total: 1
properties:
data:
type: array
example:
-
id: 3
name: 'Ravi Kumar'
email: ravi@example.com
phone: '+919876543210'
pan_on_file: true
pan_masked: ••••••234F
status: active
signup_source: invited
profile_completed_at: '2026-09-29T10:00:00.000000Z'
commission:
type: flat
flat_amount_paise: 25000
percent_basis_points: null
custom: true
description: '₹250.00 per paid account'
notes: 'YouTube, 80k subscribers'
links_count: 3
has_payout_account: true
last_login_at: '2026-09-28T10:00:00.000000Z'
created_at: '2026-09-01T10:00:00.000000Z'
stats:
clicks: 640
signups: 31
paid: 6
earned_paise: 150000
owed_paise: 50000
paid_out_net_paise: 100000
account_type: individual
master_partner_id: null
team_role: null
payment_responsibility: platform
platform_payout_terms:
hold_days: 30
minimum_payout_paise: 50000
tds_basis_points: 0
items:
type: object
properties:
id:
type: integer
example: 3
name:
type: string
example: 'Ravi Kumar'
email:
type: string
example: ravi@example.com
phone:
type: string
example: '+919876543210'
pan_on_file:
type: boolean
example: true
pan_masked:
type: string
example: ••••••234F
status:
type: string
example: active
signup_source:
type: string
example: invited
profile_completed_at:
type: string
example: '2026-09-29T10:00:00.000000Z'
commission:
type: object
properties:
type:
type: string
example: flat
flat_amount_paise:
type: integer
example: 25000
percent_basis_points:
type: string
example: null
nullable: true
custom:
type: boolean
example: true
description:
type: string
example: '₹250.00 per paid account'
notes:
type: string
example: 'YouTube, 80k subscribers'
links_count:
type: integer
example: 3
has_payout_account:
type: boolean
example: true
last_login_at:
type: string
example: '2026-09-28T10:00:00.000000Z'
created_at:
type: string
example: '2026-09-01T10:00:00.000000Z'
stats:
type: object
properties:
clicks:
type: integer
example: 640
signups:
type: integer
example: 31
paid:
type: integer
example: 6
earned_paise:
type: integer
example: 150000
owed_paise:
type: integer
example: 50000
paid_out_net_paise:
type: integer
example: 100000
account_type:
type: string
example: individual
master_partner_id:
type: string
example: null
nullable: true
team_role:
type: string
example: null
nullable: true
payment_responsibility:
type: string
example: platform
platform_payout_terms:
type: object
properties:
hold_days:
type: integer
example: 30
minimum_payout_paise:
type: integer
example: 50000
tds_basis_points:
type: integer
example: 0
links:
type: object
properties: {}
meta:
type: object
properties:
current_page:
type: integer
example: 1
per_page:
type: integer
example: 25
total:
type: integer
example: 1
tags:
- 'Partner program administration'
requestBody:
required: false
content:
application/json:
schema:
type: object
properties:
search:
type: string
description: 'Must not be greater than 100 characters.'
example: b
nullable: true
status:
type: string
description: ''
example: null
nullable: true
period:
type: string
description: ''
example: null
nullable: true
per_page:
type: integer
description: 'Must be at least 1. Must not be greater than 50.'
example: 22
nullable: true
post:
summary: 'Invite a partner.'
operationId: inviteAPartner
description: "Creates the partner and a first link. They can sign in at /partners/login straight\naway with a WhatsApp code to this number. Leave `commission_type` empty for the\nprogram default."
parameters: []
responses:
201:
description: ''
content:
application/json:
schema:
type: object
example:
data:
id: 3
name: 'Ravi Kumar'
email: ravi@example.com
phone: '+919876543210'
pan_on_file: false
pan_masked: null
status: active
signup_source: invited
profile_completed_at: '2026-09-29T10:00:00.000000Z'
commission:
type: flat
flat_amount_paise: 25000
percent_basis_points: null
custom: true
description: '₹250.00 per paid account'
notes: 'YouTube, 80k subscribers'
last_login_at: null
created_at: '2026-09-29T10:00:00.000000Z'
links:
-
id: 7
code: RAVI7K
label: 'Main link'
program_id: null
url: 'https://dukanam.com/p/RAVI7K'
destination: register
is_active: true
created_at: '2026-09-29T10:00:00.000000Z'
account_type: individual
master_partner_id: null
team_role: null
payment_responsibility: platform
platform_payout_terms:
hold_days: 30
minimum_payout_paise: 50000
tds_basis_points: 0
properties:
data:
type: object
properties:
id:
type: integer
example: 3
name:
type: string
example: 'Ravi Kumar'
email:
type: string
example: ravi@example.com
phone:
type: string
example: '+919876543210'
pan_on_file:
type: boolean
example: false
pan_masked:
type: string
example: null
nullable: true
status:
type: string
example: active
signup_source:
type: string
example: invited
profile_completed_at:
type: string
example: '2026-09-29T10:00:00.000000Z'
commission:
type: object
properties:
type:
type: string
example: flat
flat_amount_paise:
type: integer
example: 25000
percent_basis_points:
type: string
example: null
nullable: true
custom:
type: boolean
example: true
description:
type: string
example: '₹250.00 per paid account'
notes:
type: string
example: 'YouTube, 80k subscribers'
last_login_at:
type: string
example: null
nullable: true
created_at:
type: string
example: '2026-09-29T10:00:00.000000Z'
links:
type: array
example:
- id: 7
code: RAVI7K
label: 'Main link'
program_id: null
url: 'https://dukanam.com/p/RAVI7K'
destination: register
is_active: true
created_at: '2026-09-29T10:00:00.000000Z'
items:
type: object
properties:
id:
type: integer
example: 7
code:
type: string
example: RAVI7K
label:
type: string
example: 'Main link'
program_id:
type: string
example: null
nullable: true
url:
type: string
example: 'https://dukanam.com/p/RAVI7K'
destination:
type: string
example: register
is_active:
type: boolean
example: true
created_at:
type: string
example: '2026-09-29T10:00:00.000000Z'
account_type:
type: string
example: individual
master_partner_id:
type: string
example: null
nullable: true
team_role:
type: string
example: null
nullable: true
payment_responsibility:
type: string
example: platform
platform_payout_terms:
type: object
properties:
hold_days:
type: integer
example: 30
minimum_payout_paise:
type: integer
example: 50000
tds_basis_points:
type: integer
example: 0
422:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'Another partner already uses this number.'
errors:
phone:
- 'Another partner already uses this number.'
properties:
message:
type: string
example: 'Another partner already uses this number.'
errors:
type: object
properties:
phone:
type: array
example:
- 'Another partner already uses this number.'
items:
type: string
tags:
- 'Partner program administration'
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
account_type:
type: string
description: 'individual or master. Masters need a separate commission deal.'
example: master
master_partner_id:
type: string
description: ''
example: null
commission_hold_days:
type: integer
description: 'Nullable master hold override, 0–180.'
example: 7
nullable: true
payout_minimum_amount:
type: number
description: 'Nullable master minimum payout in rupees, 0–1000000.'
example: 1000.0
nullable: true
payout_tds_percent:
type: number
description: 'Nullable master TDS override, 0–30.'
example: 2.0
nullable: true
name:
type: string
description: ''
example: 'Ravi Kumar'
phone:
type: string
description: 'Indian mobile number they sign in with.'
example: '9876543210'
email:
type: string
description: ''
example: ravi@example.com
nullable: true
status:
type: string
description: ''
example: active
enum:
- active
- suspended
commission_type:
type: string
description: 'Their own deal: flat, percent or first_month. Omit or null for the program default.'
example: flat
nullable: true
commission_flat_amount:
type: number
description: 'Rupees per paid account, required for flat.'
example: 250.0
nullable: true
commission_percent:
type: number
description: 'Percent of the first payment, required for percent.'
example: 25.0
nullable: true
notes:
type: string
description: 'Internal notes.'
example: 'YouTube, 80k subscribers'
nullable: true
required:
- name
- phone
'/api/v1/admin/partners/{id}':
get:
summary: "Get a partner's performance."
operationId: getAPartnersPerformance
description: "The partner, their funnel for the period, lifetime earnings, every link with its own\nfunnel, click sources and a zero-filled 30-day daily series. `available_programs`\nuses the same active, scheduled and audience filtering as the partner portal;\nid null denotes the default program with the partner’s personal deal applied.\nSuspended partners and global program pause return an empty available_programs list.\nActive masters include team_overview with team funnel, per-member performance,\nplatform earnings, team obligations and retained amount. Member available_programs\ncontains only their master's eligible programs; earnings use the team reward ledger."
parameters:
-
in: query
name: period
description: '7d, 30d, 90d or all. Defaults to 30d.'
example: 30d
required: false
schema:
type: string
description: '7d, 30d, 90d or all. Defaults to 30d.'
example: 30d
responses:
200:
description: ''
content:
application/json:
schema:
type: object
example:
data:
partner:
id: 3
name: 'Ravi Kumar'
email: ravi@example.com
phone: '+919876543210'
pan_on_file: true
pan_masked: ••••••234F
status: active
signup_source: invited
profile_completed_at: '2026-09-29T10:00:00.000000Z'
commission:
type: flat
flat_amount_paise: 25000
percent_basis_points: null
custom: true
description: '₹250.00 per paid account'
notes: null
has_payout_account: true
last_login_at: null
created_at: '2026-09-01T10:00:00.000000Z'
account_type: individual
master_partner_id: null
team_role: null
payment_responsibility: platform
platform_payout_terms:
hold_days: 30
minimum_payout_paise: 50000
tds_basis_points: 0
funnel:
clicks: 640
unique_visitors: 512
signups: 31
checkouts_started: 9
paid: 6
click_to_signup_rate: 4.8
signup_to_paid_rate: 19.4
earnings:
on_hold_paise: 50000
available_paise: 0
processing_paise: 0
paid_paise: 100000
lifetime_paise: 150000
paid_out_net_paise: 100000
tds_deducted_paise: 0
links:
-
id: 7
code: RAVI7K
label: 'Main link'
program_id: null
url: 'https://dukanam.com/p/RAVI7K'
destination: register
is_active: true
created_at: '2026-09-01T10:00:00.000000Z'
stats:
clicks: 640
unique_visitors: 512
signups: 31
checkouts_started: 9
paid: 6
click_to_signup_rate: 4.8
signup_to_paid_rate: 19.4
sources:
youtube: 420
whatsapp: 150
direct: 70
daily:
-
date: '2026-09-29'
clicks: 24
signups: 1
paid: 0
available_programs:
-
id: null
name: 'Shop referral program'
description: 'Help shops get started with Dukanam and earn on their first paid plan payment.'
is_default: true
commission:
type: flat
flat_amount_paise: 25000
percent_basis_points: null
custom: true
description: '₹250.00 per paid account'
hold_days: 30
minimum_payout_paise: 50000
tds_basis_points: 0
starts_at: null
ends_at: null
team_overview: null
properties:
data:
type: object
properties:
partner:
type: object
properties:
id:
type: integer
example: 3
name:
type: string
example: 'Ravi Kumar'
email:
type: string
example: ravi@example.com
phone:
type: string
example: '+919876543210'
pan_on_file:
type: boolean
example: true
pan_masked:
type: string
example: ••••••234F
status:
type: string
example: active
signup_source:
type: string
example: invited
profile_completed_at:
type: string
example: '2026-09-29T10:00:00.000000Z'
commission:
type: object
properties:
type:
type: string
example: flat
flat_amount_paise:
type: integer
example: 25000
percent_basis_points:
type: string
example: null
nullable: true
custom:
type: boolean
example: true
description:
type: string
example: '₹250.00 per paid account'
notes:
type: string
example: null
nullable: true
has_payout_account:
type: boolean
example: true
last_login_at:
type: string
example: null
nullable: true
created_at:
type: string
example: '2026-09-01T10:00:00.000000Z'
account_type:
type: string
example: individual
master_partner_id:
type: string
example: null
nullable: true
team_role:
type: string
example: null
nullable: true
payment_responsibility:
type: string
example: platform
platform_payout_terms:
type: object
properties:
hold_days:
type: integer
example: 30
minimum_payout_paise:
type: integer
example: 50000
tds_basis_points:
type: integer
example: 0
funnel:
type: object
properties:
clicks:
type: integer
example: 640
unique_visitors:
type: integer
example: 512
signups:
type: integer
example: 31
checkouts_started:
type: integer
example: 9
paid:
type: integer
example: 6
click_to_signup_rate:
type: number
example: 4.8
signup_to_paid_rate:
type: number
example: 19.4
earnings:
type: object
properties:
on_hold_paise:
type: integer
example: 50000
available_paise:
type: integer
example: 0
processing_paise:
type: integer
example: 0
paid_paise:
type: integer
example: 100000
lifetime_paise:
type: integer
example: 150000
paid_out_net_paise:
type: integer
example: 100000
tds_deducted_paise:
type: integer
example: 0
links:
type: array
example:
-
id: 7
code: RAVI7K
label: 'Main link'
program_id: null
url: 'https://dukanam.com/p/RAVI7K'
destination: register
is_active: true
created_at: '2026-09-01T10:00:00.000000Z'
stats:
clicks: 640
unique_visitors: 512
signups: 31
checkouts_started: 9
paid: 6
click_to_signup_rate: 4.8
signup_to_paid_rate: 19.4
items:
type: object
properties:
id:
type: integer
example: 7
code:
type: string
example: RAVI7K
label:
type: string
example: 'Main link'
program_id:
type: string
example: null
nullable: true
url:
type: string
example: 'https://dukanam.com/p/RAVI7K'
destination:
type: string
example: register
is_active:
type: boolean
example: true
created_at:
type: string
example: '2026-09-01T10:00:00.000000Z'
stats:
type: object
properties:
clicks:
type: integer
example: 640
unique_visitors:
type: integer
example: 512
signups:
type: integer
example: 31
checkouts_started:
type: integer
example: 9
paid:
type: integer
example: 6
click_to_signup_rate:
type: number
example: 4.8
signup_to_paid_rate:
type: number
example: 19.4
sources:
type: object
properties:
youtube:
type: integer
example: 420
whatsapp:
type: integer
example: 150
direct:
type: integer
example: 70
daily:
type: array
example:
- date: '2026-09-29'
clicks: 24
signups: 1
paid: 0
items:
type: object
properties:
date:
type: string
example: '2026-09-29'
clicks:
type: integer
example: 24
signups:
type: integer
example: 1
paid:
type: integer
example: 0
available_programs:
type: array
example:
-
id: null
name: 'Shop referral program'
description: 'Help shops get started with Dukanam and earn on their first paid plan payment.'
is_default: true
commission:
type: flat
flat_amount_paise: 25000
percent_basis_points: null
custom: true
description: '₹250.00 per paid account'
hold_days: 30
minimum_payout_paise: 50000
tds_basis_points: 0
starts_at: null
ends_at: null
items:
type: object
properties:
id:
type: string
example: null
nullable: true
name:
type: string
example: 'Shop referral program'
description:
type: string
example: 'Help shops get started with Dukanam and earn on their first paid plan payment.'
is_default:
type: boolean
example: true
commission:
type: object
properties:
type:
type: string
example: flat
flat_amount_paise:
type: integer
example: 25000
percent_basis_points:
type: string
example: null
nullable: true
custom:
type: boolean
example: true
description:
type: string
example: '₹250.00 per paid account'
hold_days:
type: integer
example: 30
minimum_payout_paise:
type: integer
example: 50000
tds_basis_points:
type: integer
example: 0
starts_at:
type: string
example: null
nullable: true
ends_at:
type: string
example: null
nullable: true
team_overview:
type: string
example: null
nullable: true
tags:
- 'Partner program administration'
requestBody:
required: false
content:
application/json:
schema:
type: object
properties:
period:
type: string
description: ''
example: null
nullable: true
patch:
summary: 'Update a partner.'
operationId: updateAPartner
description: "Send only what changes. Suspending a partner signs them out, stops their links\ncrediting new sign-ups and stops new commissions. Send `commission_type` as null to\nmove individuals back to the program default. Members cannot change hierarchy or\nplatform deals. Master fixed deals must remain above active fixed team rewards.\nNull payout overrides restore global defaults. Masters with members cannot be downgraded."
parameters: []
responses:
200:
description: ''
content:
application/json:
schema:
type: object
example:
data:
id: 3
name: 'Ravi Kumar'
email: ravi@example.com
phone: '+919876543210'
pan_on_file: false
pan_masked: null
status: suspended
commission:
type: percent
flat_amount_paise: null
percent_basis_points: 2500
custom: true
description: '25% of the first payment'
notes: 'Paused while the campaign is reviewed'
last_login_at: null
created_at: '2026-09-01T10:00:00.000000Z'
account_type: individual
master_partner_id: null
team_role: null
payment_responsibility: platform
platform_payout_terms:
hold_days: 30
minimum_payout_paise: 50000
tds_basis_points: 0
properties:
data:
type: object
properties:
id:
type: integer
example: 3
name:
type: string
example: 'Ravi Kumar'
email:
type: string
example: ravi@example.com
phone:
type: string
example: '+919876543210'
pan_on_file:
type: boolean
example: false
pan_masked:
type: string
example: null
nullable: true
status:
type: string
example: suspended
commission:
type: object
properties:
type:
type: string
example: percent
flat_amount_paise:
type: string
example: null
nullable: true
percent_basis_points:
type: integer
example: 2500
custom:
type: boolean
example: true
description:
type: string
example: '25% of the first payment'
notes:
type: string
example: 'Paused while the campaign is reviewed'
last_login_at:
type: string
example: null
nullable: true
created_at:
type: string
example: '2026-09-01T10:00:00.000000Z'
account_type:
type: string
example: individual
master_partner_id:
type: string
example: null
nullable: true
team_role:
type: string
example: null
nullable: true
payment_responsibility:
type: string
example: platform
platform_payout_terms:
type: object
properties:
hold_days:
type: integer
example: 30
minimum_payout_paise:
type: integer
example: 50000
tds_basis_points:
type: integer
example: 0
tags:
- 'Partner program administration'
requestBody:
required: false
content:
application/json:
schema:
type: object
properties:
account_type:
type: string
description: 'individual or master; no downgrading masters with members or changing member roles.'
example: master
master_partner_id:
type: string
description: ''
example: null
commission_hold_days:
type: integer
description: 'Nullable master hold override, 0–180.'
example: 7
nullable: true
payout_minimum_amount:
type: number
description: 'Nullable master minimum payout in rupees.'
example: 1000.0
nullable: true
payout_tds_percent:
type: number
description: 'Nullable master TDS override, 0–30.'
example: 2.0
nullable: true
name:
type: string
description: ''
example: 'Ravi Kumar'
phone:
type: string
description: ''
example: '9876543210'
email:
type: string
description: ''
example: ravi@example.com
nullable: true
status:
type: string
description: 'active or suspended.'
example: suspended
commission_type:
type: string
description: 'flat, percent, first_month or null.'
example: percent
nullable: true
commission_flat_amount:
type: number
description: ''
example: 250.0
nullable: true
commission_percent:
type: number
description: ''
example: 25.0
nullable: true
notes:
type: string
description: ''
example: 'Paused while the campaign is reviewed'
nullable: true
parameters:
-
in: path
name: id
description: 'The ID of the partner.'
example: 1
required: true
schema:
type: integer
'/api/v1/admin/partners/{partner_id}/links':
post:
summary: 'Add a link for a partner.'
operationId: addALinkForAPartner
description: "Codes are letters and numbers, 4–16 characters, unique across partner links and\ncustomer referral codes. Omit `code` to generate one from the partner's name."
parameters: []
responses:
201:
description: ''
content:
application/json:
schema:
type: object
example:
data:
id: 8
code: RAVIINSTA
label: 'Instagram bio'
program_id: null
url: 'https://dukanam.com/p/RAVIINSTA'
destination: pricing
is_active: true
created_at: '2026-09-29T10:00:00.000000Z'
properties:
data:
type: object
properties:
id:
type: integer
example: 8
code:
type: string
example: RAVIINSTA
label:
type: string
example: 'Instagram bio'
program_id:
type: string
example: null
nullable: true
url:
type: string
example: 'https://dukanam.com/p/RAVIINSTA'
destination:
type: string
example: pricing
is_active:
type: boolean
example: true
created_at:
type: string
example: '2026-09-29T10:00:00.000000Z'
422:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'This code is already taken. Try another.'
errors:
code:
- 'This code is already taken. Try another.'
properties:
message:
type: string
example: 'This code is already taken. Try another.'
errors:
type: object
properties:
code:
type: array
example:
- 'This code is already taken. Try another.'
items:
type: string
tags:
- 'Partner program administration'
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
program_id:
type: integer
description: 'Optional eligible named program ID; omit or null for the default program. Unavailable programs return 422.'
example: null
nullable: true
label:
type: string
description: ''
example: 'Instagram bio'
code:
type: string
description: ''
example: RAVIINSTA
nullable: true
destination:
type: string
description: 'register, home or pricing. Defaults to register.'
example: pricing
nullable: true
required:
- label
parameters:
-
in: path
name: partner_id
description: 'The ID of the partner.'
example: 1
required: true
schema:
type: integer
'/api/v1/admin/partners/{partner_id}/links/{id}':
patch:
summary: 'Pause or resume a partner link.'
operationId: pauseOrResumeAPartnerLink
description: "A paused link still sends visitors to sign-up but no longer counts clicks or credits\nsign-ups to the partner."
parameters: []
responses:
200:
description: ''
content:
application/json:
schema:
type: object
example:
data:
id: 8
code: RAVIINSTA
label: 'Instagram bio'
program_id: null
url: 'https://dukanam.com/p/RAVIINSTA'
destination: pricing
is_active: false
created_at: '2026-09-29T10:00:00.000000Z'
properties:
data:
type: object
properties:
id:
type: integer
example: 8
code:
type: string
example: RAVIINSTA
label:
type: string
example: 'Instagram bio'
program_id:
type: string
example: null
nullable: true
url:
type: string
example: 'https://dukanam.com/p/RAVIINSTA'
destination:
type: string
example: pricing
is_active:
type: boolean
example: false
created_at:
type: string
example: '2026-09-29T10:00:00.000000Z'
tags:
- 'Partner program administration'
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
is_active:
type: boolean
description: ''
example: false
required:
- is_active
parameters:
-
in: path
name: partner_id
description: 'The ID of the partner.'
example: 1
required: true
schema:
type: integer
-
in: path
name: id
description: 'The ID of the link.'
example: 1
required: true
schema:
type: integer
'/api/v1/admin/partners/{partner_id}/referrals':
get:
summary: "List a partner's sign-ups."
operationId: listAPartnersSignUps
description: "Every account credited to the partner, with the account and workspace. Partners\nthemselves never see these names."
parameters:
-
in: query
name: status
description: 'registered, subscription_pending or paid.'
example: paid
required: false
schema:
type: string
description: 'registered, subscription_pending or paid.'
example: paid
-
in: query
name: per_page
description: 'Results per page, from 1 to 50.'
example: 25
required: false
schema:
type: integer
description: 'Results per page, from 1 to 50.'
example: 25
responses:
200:
description: ''
content:
application/json:
schema:
type: object
example:
data:
-
id: 41
code: RAVI7K
link:
id: 7
label: 'Main link'
source: web
status: paid
status_label: Paid
user:
id: 88
name: 'Meena Shah'
email: meena@example.com
business:
id: 90
name: 'Meena Stores'
payment_source: razorpay
payment_amount_paise: 84661
commission_blocked_reason: null
registered_at: '2026-09-10T08:00:00.000000Z'
subscription_pending_at: '2026-09-12T08:00:00.000000Z'
paid_at: '2026-09-12T08:05:00.000000Z'
links: {}
meta:
current_page: 1
per_page: 25
total: 1
properties:
data:
type: array
example:
-
id: 41
code: RAVI7K
link:
id: 7
label: 'Main link'
source: web
status: paid
status_label: Paid
user:
id: 88
name: 'Meena Shah'
email: meena@example.com
business:
id: 90
name: 'Meena Stores'
payment_source: razorpay
payment_amount_paise: 84661
commission_blocked_reason: null
registered_at: '2026-09-10T08:00:00.000000Z'
subscription_pending_at: '2026-09-12T08:00:00.000000Z'
paid_at: '2026-09-12T08:05:00.000000Z'
items:
type: object
properties:
id:
type: integer
example: 41
code:
type: string
example: RAVI7K
link:
type: object
properties:
id:
type: integer
example: 7
label:
type: string
example: 'Main link'
source:
type: string
example: web
status:
type: string
example: paid
status_label:
type: string
example: Paid
user:
type: object
properties:
id:
type: integer
example: 88
name:
type: string
example: 'Meena Shah'
email:
type: string
example: meena@example.com
business:
type: object
properties:
id:
type: integer
example: 90
name:
type: string
example: 'Meena Stores'
payment_source:
type: string
example: razorpay
payment_amount_paise:
type: integer
example: 84661
commission_blocked_reason:
type: string
example: null
nullable: true
registered_at:
type: string
example: '2026-09-10T08:00:00.000000Z'
subscription_pending_at:
type: string
example: '2026-09-12T08:00:00.000000Z'
paid_at:
type: string
example: '2026-09-12T08:05:00.000000Z'
links:
type: object
properties: {}
meta:
type: object
properties:
current_page:
type: integer
example: 1
per_page:
type: integer
example: 25
total:
type: integer
example: 1
tags:
- 'Partner program administration'
requestBody:
required: false
content:
application/json:
schema:
type: object
properties:
status:
type: string
description: ''
example: null
nullable: true
per_page:
type: integer
description: 'Must be at least 1. Must not be greater than 50.'
example: 1
nullable: true
parameters:
-
in: path
name: partner_id
description: 'The ID of the partner.'
example: 1
required: true
schema:
type: integer
'/api/v1/admin/partners/{partner_id}/adjustments':
post:
summary: 'Add a bonus or adjustment.'
operationId: addABonusOrAdjustment
description: "A campaign fee or bonus (positive) or a claw-back (negative), in rupees. Approved at\nonce and included in the partner's next payout."
parameters: []
responses:
201:
description: ''
content:
application/json:
schema:
type: object
example:
data:
id: 55
partner_referral_id: null
kind: adjustment
amount_paise: 150000
basis: null
description: 'Diwali campaign fee'
status: approved
status_label: Approved
earned_at: '2026-09-29T10:00:00.000000Z'
hold_until: null
approved_at: '2026-09-29T10:00:00.000000Z'
rejected_at: null
rejection_reason: null
partner_payout_id: null
paid_at: null
properties:
data:
type: object
properties:
id:
type: integer
example: 55
partner_referral_id:
type: string
example: null
nullable: true
kind:
type: string
example: adjustment
amount_paise:
type: integer
example: 150000
basis:
type: string
example: null
nullable: true
description:
type: string
example: 'Diwali campaign fee'
status:
type: string
example: approved
status_label:
type: string
example: Approved
earned_at:
type: string
example: '2026-09-29T10:00:00.000000Z'
hold_until:
type: string
example: null
nullable: true
approved_at:
type: string
example: '2026-09-29T10:00:00.000000Z'
rejected_at:
type: string
example: null
nullable: true
rejection_reason:
type: string
example: null
nullable: true
partner_payout_id:
type: string
example: null
nullable: true
paid_at:
type: string
example: null
nullable: true
tags:
- 'Partner program administration'
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
amount:
type: number
description: 'Rupees; negative to claw back.'
example: 1500.0
description:
type: string
description: 'Shown to the partner.'
example: 'Diwali campaign fee'
required:
- amount
- description
parameters:
-
in: path
name: partner_id
description: 'The ID of the partner.'
example: 1
required: true
schema:
type: integer
/api/v1/admin/partner-commissions:
get:
summary: 'List partner commissions.'
operationId: listPartnerCommissions
description: "A conversion commission is earned when a credited account's first paid charge is\ncaptured; it is `pending` (on hold) until `hold_until`, then `approved`. An approved\ncommission with a `partner_payout_id` is in a payout being sent. `basis` records the\nrule it was earned under. For named-program links it also includes `program_id`,\n`program_name` and the reward/hold terms saved at sign-up."
parameters:
-
in: query
name: partner_id
description: 'Only this partner.'
example: 3
required: false
schema:
type: integer
description: 'Only this partner.'
example: 3
-
in: query
name: status
description: 'pending, approved, paid or rejected.'
example: pending
required: false
schema:
type: string
description: 'pending, approved, paid or rejected.'
example: pending
-
in: query
name: per_page
description: 'Results per page, from 1 to 50.'
example: 25
required: false
schema:
type: integer
description: 'Results per page, from 1 to 50.'
example: 25
responses:
200:
description: ''
content:
application/json:
schema:
type: object
example:
data:
-
id: 54
partner:
id: 3
name: 'Ravi Kumar'
partner_referral_id: 41
kind: conversion
amount_paise: 25000
basis:
type: flat
flat_paise: 25000
percent_basis_points: null
custom: true
base_paise: 84661
hold_days: 30
program_id: 1
program_name: 'Creator campaign'
description: 'Paid account via RAVI7K'
status: pending
status_label: 'On hold'
earned_at: '2026-09-12T08:05:00.000000Z'
hold_until: '2026-10-12T08:05:00.000000Z'
approved_at: null
rejected_at: null
rejection_reason: null
partner_payout_id: null
paid_at: null
links: {}
meta:
current_page: 1
per_page: 25
total: 1
properties:
data:
type: array
example:
-
id: 54
partner:
id: 3
name: 'Ravi Kumar'
partner_referral_id: 41
kind: conversion
amount_paise: 25000
basis:
type: flat
flat_paise: 25000
percent_basis_points: null
custom: true
base_paise: 84661
hold_days: 30
program_id: 1
program_name: 'Creator campaign'
description: 'Paid account via RAVI7K'
status: pending
status_label: 'On hold'
earned_at: '2026-09-12T08:05:00.000000Z'
hold_until: '2026-10-12T08:05:00.000000Z'
approved_at: null
rejected_at: null
rejection_reason: null
partner_payout_id: null
paid_at: null
items:
type: object
properties:
id:
type: integer
example: 54
partner:
type: object
properties:
id:
type: integer
example: 3
name:
type: string
example: 'Ravi Kumar'
partner_referral_id:
type: integer
example: 41
kind:
type: string
example: conversion
amount_paise:
type: integer
example: 25000
basis:
type: object
properties:
type:
type: string
example: flat
flat_paise:
type: integer
example: 25000
percent_basis_points:
type: string
example: null
nullable: true
custom:
type: boolean
example: true
base_paise:
type: integer
example: 84661
hold_days:
type: integer
example: 30
program_id:
type: integer
example: 1
program_name:
type: string
example: 'Creator campaign'
description:
type: string
example: 'Paid account via RAVI7K'
status:
type: string
example: pending
status_label:
type: string
example: 'On hold'
earned_at:
type: string
example: '2026-09-12T08:05:00.000000Z'
hold_until:
type: string
example: '2026-10-12T08:05:00.000000Z'
approved_at:
type: string
example: null
nullable: true
rejected_at:
type: string
example: null
nullable: true
rejection_reason:
type: string
example: null
nullable: true
partner_payout_id:
type: string
example: null
nullable: true
paid_at:
type: string
example: null
nullable: true
links:
type: object
properties: {}
meta:
type: object
properties:
current_page:
type: integer
example: 1
per_page:
type: integer
example: 25
total:
type: integer
example: 1
tags:
- 'Partner program administration'
requestBody:
required: false
content:
application/json:
schema:
type: object
properties:
partner_id:
type: integer
description: ''
example: 16
nullable: true
status:
type: string
description: ''
example: null
nullable: true
per_page:
type: integer
description: 'Must be at least 1. Must not be greater than 50.'
example: 22
nullable: true
'/api/v1/admin/partner-commissions/{commission_id}/approve':
post:
summary: 'Approve a commission now.'
operationId: approveACommissionNow
description: 'Skips the rest of the hold period, so the commission goes into the next payout.'
parameters: []
responses:
200:
description: ''
content:
application/json:
schema:
type: object
example:
data:
id: 54
partner_referral_id: 41
kind: conversion
amount_paise: 25000
basis:
type: flat
flat_paise: 25000
percent_basis_points: null
custom: true
base_paise: 84661
hold_days: 30
description: 'Paid account via RAVI7K'
status: approved
status_label: Approved
earned_at: '2026-09-12T08:05:00.000000Z'
hold_until: '2026-10-12T08:05:00.000000Z'
approved_at: '2026-09-29T10:00:00.000000Z'
rejected_at: null
rejection_reason: null
partner_payout_id: null
paid_at: null
properties:
data:
type: object
properties:
id:
type: integer
example: 54
partner_referral_id:
type: integer
example: 41
kind:
type: string
example: conversion
amount_paise:
type: integer
example: 25000
basis:
type: object
properties:
type:
type: string
example: flat
flat_paise:
type: integer
example: 25000
percent_basis_points:
type: string
example: null
nullable: true
custom:
type: boolean
example: true
base_paise:
type: integer
example: 84661
hold_days:
type: integer
example: 30
description:
type: string
example: 'Paid account via RAVI7K'
status:
type: string
example: approved
status_label:
type: string
example: Approved
earned_at:
type: string
example: '2026-09-12T08:05:00.000000Z'
hold_until:
type: string
example: '2026-10-12T08:05:00.000000Z'
approved_at:
type: string
example: '2026-09-29T10:00:00.000000Z'
rejected_at:
type: string
example: null
nullable: true
rejection_reason:
type: string
example: null
nullable: true
partner_payout_id:
type: string
example: null
nullable: true
paid_at:
type: string
example: null
nullable: true
422:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'Only a commission on hold can be approved.'
errors:
commission:
- 'Only a commission on hold can be approved.'
properties:
message:
type: string
example: 'Only a commission on hold can be approved.'
errors:
type: object
properties:
commission:
type: array
example:
- 'Only a commission on hold can be approved.'
items:
type: string
tags:
- 'Partner program administration'
parameters:
-
in: path
name: commission_id
description: 'The ID of the commission.'
example: 1
required: true
schema:
type: integer
'/api/v1/admin/partner-commissions/{commission_id}/reject':
post:
summary: 'Reject a commission.'
operationId: rejectACommission
description: "For a refund, a fake sign-up or a policy breach. Only an unpaid commission that is not\nin a payout can be rejected; cancel the payout first. The partner sees the reason.\nIts unpaid team reward is also rejected; a reserved team settlement is cancelled,\nreleasing other rewards. Already recorded external team payments remain paid."
parameters: []
responses:
200:
description: ''
content:
application/json:
schema:
type: object
example:
data:
id: 54
partner_referral_id: 41
kind: conversion
amount_paise: 25000
basis: null
description: 'Paid account via RAVI7K'
status: rejected
status_label: Rejected
earned_at: '2026-09-12T08:05:00.000000Z'
hold_until: '2026-10-12T08:05:00.000000Z'
approved_at: null
rejected_at: '2026-09-29T10:00:00.000000Z'
rejection_reason: 'Subscription refunded'
partner_payout_id: null
paid_at: null
properties:
data:
type: object
properties:
id:
type: integer
example: 54
partner_referral_id:
type: integer
example: 41
kind:
type: string
example: conversion
amount_paise:
type: integer
example: 25000
basis:
type: string
example: null
nullable: true
description:
type: string
example: 'Paid account via RAVI7K'
status:
type: string
example: rejected
status_label:
type: string
example: Rejected
earned_at:
type: string
example: '2026-09-12T08:05:00.000000Z'
hold_until:
type: string
example: '2026-10-12T08:05:00.000000Z'
approved_at:
type: string
example: null
nullable: true
rejected_at:
type: string
example: '2026-09-29T10:00:00.000000Z'
rejection_reason:
type: string
example: 'Subscription refunded'
partner_payout_id:
type: string
example: null
nullable: true
paid_at:
type: string
example: null
nullable: true
tags:
- 'Partner program administration'
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
reason:
type: string
description: ''
example: 'Subscription refunded'
required:
- reason
parameters:
-
in: path
name: commission_id
description: 'The ID of the commission.'
example: 1
required: true
schema:
type: integer
/api/v1/admin/partner-payouts:
get:
summary: 'List payouts.'
operationId: listPayouts
description: 'Payouts are listed without full account numbers; download the payment sheet for those.'
parameters:
-
in: query
name: partner_id
description: 'Only this partner.'
example: 3
required: false
schema:
type: integer
description: 'Only this partner.'
example: 3
-
in: query
name: status
description: 'processing, paid or cancelled.'
example: processing
required: false
schema:
type: string
description: 'processing, paid or cancelled.'
example: processing
-
in: query
name: per_page
description: 'Results per page, from 1 to 50.'
example: 25
required: false
schema:
type: integer
description: 'Results per page, from 1 to 50.'
example: 25
responses:
200:
description: ''
content:
application/json:
schema:
type: object
example:
data:
-
id: 12
partner:
id: 3
name: 'Ravi Kumar'
status: processing
status_label: Processing
gross_paise: 100000
tds_basis_points: 200
tds_paise: 2000
net_paise: 98000
commissions_count: 4
account:
method: bank
label: 'HDFC Bank ••6789'
account_holder_name: 'Ravi Kumar'
ifsc: HDFC0001234
reference: null
notes: null
created_at: '2026-09-29T10:00:00.000000Z'
paid_at: null
cancelled_at: null
links: {}
meta:
current_page: 1
per_page: 25
total: 1
properties:
data:
type: array
example:
-
id: 12
partner:
id: 3
name: 'Ravi Kumar'
status: processing
status_label: Processing
gross_paise: 100000
tds_basis_points: 200
tds_paise: 2000
net_paise: 98000
commissions_count: 4
account:
method: bank
label: 'HDFC Bank ••6789'
account_holder_name: 'Ravi Kumar'
ifsc: HDFC0001234
reference: null
notes: null
created_at: '2026-09-29T10:00:00.000000Z'
paid_at: null
cancelled_at: null
items:
type: object
properties:
id:
type: integer
example: 12
partner:
type: object
properties:
id:
type: integer
example: 3
name:
type: string
example: 'Ravi Kumar'
status:
type: string
example: processing
status_label:
type: string
example: Processing
gross_paise:
type: integer
example: 100000
tds_basis_points:
type: integer
example: 200
tds_paise:
type: integer
example: 2000
net_paise:
type: integer
example: 98000
commissions_count:
type: integer
example: 4
account:
type: object
properties:
method:
type: string
example: bank
label:
type: string
example: 'HDFC Bank ••6789'
account_holder_name:
type: string
example: 'Ravi Kumar'
ifsc:
type: string
example: HDFC0001234
reference:
type: string
example: null
nullable: true
notes:
type: string
example: null
nullable: true
created_at:
type: string
example: '2026-09-29T10:00:00.000000Z'
paid_at:
type: string
example: null
nullable: true
cancelled_at:
type: string
example: null
nullable: true
links:
type: object
properties: {}
meta:
type: object
properties:
current_page:
type: integer
example: 1
per_page:
type: integer
example: 25
total:
type: integer
example: 1
tags:
- 'Partner program administration'
requestBody:
required: false
content:
application/json:
schema:
type: object
properties:
partner_id:
type: integer
description: ''
example: 16
nullable: true
status:
type: string
description: ''
example: null
nullable: true
per_page:
type: integer
description: 'Must be at least 1. Must not be greater than 50.'
example: 22
nullable: true
/api/v1/admin/partner-payouts/prepare:
post:
summary: 'Prepare payouts.'
operationId: preparePayouts
description: "Releases commissions whose hold has ended, then groups every approved commission not\nyet in a payout into one `processing` payout per partner, sent to their primary payout\naccount with their effective TDS rate deducted. Master payout overrides apply.\nTeam members are paid separately by their masters and never receive platform payouts.\nPartners under their effective minimum payout, or\nwithout a payout account, are listed and skipped."
parameters: []
responses:
201:
description: ''
content:
application/json:
schema:
type: object
example:
data:
created:
-
id: 12
partner:
id: 3
name: 'Ravi Kumar'
status: processing
status_label: Processing
gross_paise: 100000
tds_basis_points: 200
tds_paise: 2000
net_paise: 98000
commissions_count: 4
account:
method: bank
label: 'HDFC Bank ••6789'
account_holder_name: 'Ravi Kumar'
ifsc: HDFC0001234
reference: null
notes: null
created_at: '2026-09-29T10:00:00.000000Z'
paid_at: null
cancelled_at: null
missing_account:
-
id: 5
name: 'Anita Rao'
below_minimum: []
properties:
data:
type: object
properties:
created:
type: array
example:
-
id: 12
partner:
id: 3
name: 'Ravi Kumar'
status: processing
status_label: Processing
gross_paise: 100000
tds_basis_points: 200
tds_paise: 2000
net_paise: 98000
commissions_count: 4
account:
method: bank
label: 'HDFC Bank ••6789'
account_holder_name: 'Ravi Kumar'
ifsc: HDFC0001234
reference: null
notes: null
created_at: '2026-09-29T10:00:00.000000Z'
paid_at: null
cancelled_at: null
items:
type: object
properties:
id:
type: integer
example: 12
partner:
type: object
properties:
id:
type: integer
example: 3
name:
type: string
example: 'Ravi Kumar'
status:
type: string
example: processing
status_label:
type: string
example: Processing
gross_paise:
type: integer
example: 100000
tds_basis_points:
type: integer
example: 200
tds_paise:
type: integer
example: 2000
net_paise:
type: integer
example: 98000
commissions_count:
type: integer
example: 4
account:
type: object
properties:
method:
type: string
example: bank
label:
type: string
example: 'HDFC Bank ••6789'
account_holder_name:
type: string
example: 'Ravi Kumar'
ifsc:
type: string
example: HDFC0001234
reference:
type: string
example: null
nullable: true
notes:
type: string
example: null
nullable: true
created_at:
type: string
example: '2026-09-29T10:00:00.000000Z'
paid_at:
type: string
example: null
nullable: true
cancelled_at:
type: string
example: null
nullable: true
missing_account:
type: array
example:
- id: 5
name: 'Anita Rao'
items:
type: object
properties:
id:
type: integer
example: 5
name:
type: string
example: 'Anita Rao'
below_minimum:
type: array
example: []
tags:
- 'Partner program administration'
requestBody:
required: false
content:
application/json:
schema:
type: object
properties:
partner_ids:
type: array
description: 'Only these partners. Omit for everyone.'
example:
- 3
items:
type: integer
/api/v1/admin/partner-payouts/export:
get:
summary: 'Download the payment sheet.'
operationId: downloadThePaymentSheet
description: "A CSV with one row per payout and the full account details to transfer to: partner\nname, phone, email, PAN, method, account holder, account number, IFSC, bank, UPI ID,\ngross, TDS and net amounts in rupees, and the reference and date once paid."
parameters:
-
in: query
name: status
description: 'processing (default), paid or cancelled.'
example: processing
required: false
schema:
type: string
description: 'processing (default), paid or cancelled.'
example: processing
responses:
200:
description: 'CSV file'
content:
text/plain:
schema:
type: string
example: 'payout_id,partner_id,partner_name,phone,email,pan,method,account_holder_name,account_number,ifsc,bank_name,upi_id,gross_amount,tds_amount,net_amount,commissions,status,prepared_at,reference,paid_at'
tags:
- 'Partner program administration'
requestBody:
required: false
content:
application/json:
schema:
type: object
properties:
status:
type: string
description: ''
example: null
nullable: true
'/api/v1/admin/partner-payouts/{payout_id}/mark-paid':
post:
summary: 'Mark a payout paid.'
operationId: markAPayoutPaid
description: "Record the bank reference (UTR) and date. The payout and its commissions become\n`paid`, and the partner sees it in their portal."
parameters: []
responses:
200:
description: ''
content:
application/json:
schema:
type: object
example:
data:
id: 12
status: paid
status_label: Paid
gross_paise: 100000
tds_basis_points: 200
tds_paise: 2000
net_paise: 98000
commissions_count: 4
account:
method: bank
label: 'HDFC Bank ••6789'
account_holder_name: 'Ravi Kumar'
ifsc: HDFC0001234
reference: UTR123456789
notes: 'Paid by NEFT'
created_at: '2026-09-29T10:00:00.000000Z'
paid_at: '2026-09-29T12:00:00.000000Z'
cancelled_at: null
properties:
data:
type: object
properties:
id:
type: integer
example: 12
status:
type: string
example: paid
status_label:
type: string
example: Paid
gross_paise:
type: integer
example: 100000
tds_basis_points:
type: integer
example: 200
tds_paise:
type: integer
example: 2000
net_paise:
type: integer
example: 98000
commissions_count:
type: integer
example: 4
account:
type: object
properties:
method:
type: string
example: bank
label:
type: string
example: 'HDFC Bank ••6789'
account_holder_name:
type: string
example: 'Ravi Kumar'
ifsc:
type: string
example: HDFC0001234
reference:
type: string
example: UTR123456789
notes:
type: string
example: 'Paid by NEFT'
created_at:
type: string
example: '2026-09-29T10:00:00.000000Z'
paid_at:
type: string
example: '2026-09-29T12:00:00.000000Z'
cancelled_at:
type: string
example: null
nullable: true
422:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'Only a payout that is processing can be marked paid.'
errors:
payout:
- 'Only a payout that is processing can be marked paid.'
properties:
message:
type: string
example: 'Only a payout that is processing can be marked paid.'
errors:
type: object
properties:
payout:
type: array
example:
- 'Only a payout that is processing can be marked paid.'
items:
type: string
tags:
- 'Partner program administration'
requestBody:
required: false
content:
application/json:
schema:
type: object
properties:
reference:
type: string
description: 'The bank or UPI reference.'
example: UTR123456789
nullable: true
paid_on:
type: date
description: 'Defaults to today; not in the future.'
example: '2026-09-29'
nullable: true
notes:
type: string
description: ''
example: 'Paid by NEFT'
nullable: true
parameters:
-
in: path
name: payout_id
description: 'The ID of the payout.'
example: 1
required: true
schema:
type: integer
'/api/v1/admin/partner-payouts/{payout_id}/cancel':
post:
summary: 'Cancel a payout.'
operationId: cancelAPayout
description: "For a transfer that bounced or will not be made. Its commissions return to approved and\ngo into the next payout."
parameters: []
responses:
200:
description: ''
content:
application/json:
schema:
type: object
example:
data:
id: 12
status: cancelled
status_label: Cancelled
gross_paise: 100000
tds_basis_points: 200
tds_paise: 2000
net_paise: 98000
commissions_count: 4
account:
method: bank
label: 'HDFC Bank ••6789'
account_holder_name: 'Ravi Kumar'
ifsc: HDFC0001234
reference: null
notes: 'Account closed; partner is adding a new one'
created_at: '2026-09-29T10:00:00.000000Z'
paid_at: null
cancelled_at: '2026-09-29T12:00:00.000000Z'
properties:
data:
type: object
properties:
id:
type: integer
example: 12
status:
type: string
example: cancelled
status_label:
type: string
example: Cancelled
gross_paise:
type: integer
example: 100000
tds_basis_points:
type: integer
example: 200
tds_paise:
type: integer
example: 2000
net_paise:
type: integer
example: 98000
commissions_count:
type: integer
example: 4
account:
type: object
properties:
method:
type: string
example: bank
label:
type: string
example: 'HDFC Bank ••6789'
account_holder_name:
type: string
example: 'Ravi Kumar'
ifsc:
type: string
example: HDFC0001234
reference:
type: string
example: null
nullable: true
notes:
type: string
example: 'Account closed; partner is adding a new one'
created_at:
type: string
example: '2026-09-29T10:00:00.000000Z'
paid_at:
type: string
example: null
nullable: true
cancelled_at:
type: string
example: '2026-09-29T12:00:00.000000Z'
tags:
- 'Partner program administration'
requestBody:
required: false
content:
application/json:
schema:
type: object
properties:
notes:
type: string
description: ''
example: 'Account closed; partner is adding a new one'
nullable: true
parameters:
-
in: path
name: payout_id
description: 'The ID of the payout.'
example: 1
required: true
schema:
type: integer
'/api/v1/businesses/{business}/contacts/{contact}/profitability/export':
get:
summary: 'Export party profit and loss.'
operationId: exportPartyProfitAndLoss
description: ''
parameters:
-
in: query
name: format
description: ''
example: pdf
required: true
schema:
type: string
description: ''
example: pdf
enum:
- pdf
- xlsx
- csv
-
in: query
name: period
description: ''
example: this-financial-year
required: false
schema:
type: string
description: ''
example: this-financial-year
enum:
- today
- this-month
- last-month
- this-financial-year
- last-financial-year
- custom
-
in: query
name: from
description: 'Custom start Y-m-d.'
example: null
required: false
schema:
type: string
description: 'Custom start Y-m-d.'
example: null
-
in: query
name: to
description: 'Inclusive custom end Y-m-d.'
example: null
required: false
schema:
type: string
description: 'Inclusive custom end Y-m-d.'
example: null
responses:
200:
description: 'All invoice contribution rows and report total.'
content:
application/octet-stream:
schema:
type: string
format: binary
tags:
- 'Party and referrer profitability'
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
-
in: path
name: contact
description: 'The contact.'
example: 1
required: true
schema:
type: integer
'/api/v1/businesses/{business}/workers/{worker}/profitability/export':
get:
summary: 'Export referrer profit and loss.'
operationId: exportReferrerProfitAndLoss
description: ''
parameters:
-
in: query
name: format
description: ''
example: xlsx
required: true
schema:
type: string
description: ''
example: xlsx
enum:
- pdf
- xlsx
- csv
-
in: query
name: period
description: ''
example: this-financial-year
required: false
schema:
type: string
description: ''
example: this-financial-year
enum:
- today
- this-month
- last-month
- this-financial-year
- last-financial-year
- custom
-
in: query
name: from
description: 'Custom start Y-m-d.'
example: null
required: false
schema:
type: string
description: 'Custom start Y-m-d.'
example: null
-
in: query
name: to
description: 'Inclusive custom end Y-m-d.'
example: null
required: false
schema:
type: string
description: 'Inclusive custom end Y-m-d.'
example: null
responses:
200:
description: 'All owner-attributed invoice contribution rows and report total.'
content:
application/octet-stream:
schema:
type: string
format: binary
tags:
- 'Party and referrer profitability'
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
-
in: path
name: worker
description: 'The worker.'
example: architecto
required: true
schema:
type: string
'/api/v1/businesses/{business}/workers/{worker}/statement/export':
get:
summary: 'Export the referrer commission statement.'
operationId: exportTheReferrerCommissionStatement
description: "Opening balance precedes the selected period. Every dated earning, return debit note,\npayout and correction is retained with a running balance. Negative balances are\nrecoverable; customer dues are not offset. Payments do not create commission expenses."
parameters:
-
in: query
name: format
description: ''
example: csv
required: true
schema:
type: string
description: ''
example: csv
enum:
- pdf
- xlsx
- csv
-
in: query
name: period
description: ''
example: this-financial-year
required: false
schema:
type: string
description: ''
example: this-financial-year
enum:
- today
- this-month
- last-month
- this-financial-year
- last-financial-year
- custom
-
in: query
name: from
description: 'Custom start Y-m-d.'
example: null
required: false
schema:
type: string
description: 'Custom start Y-m-d.'
example: null
-
in: query
name: to
description: 'Inclusive custom end Y-m-d.'
example: null
required: false
schema:
type: string
description: 'Inclusive custom end Y-m-d.'
example: null
responses:
200:
description: 'Complete dated statement with opening, running and closing balances.'
content:
application/octet-stream:
schema:
type: string
format: binary
tags:
- 'Party and referrer profitability'
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
-
in: path
name: worker
description: 'The worker.'
example: architecto
required: true
schema:
type: string
'/api/v1/businesses/{business}/reports/party-profitability':
get:
summary: 'Compare party profit and loss.'
operationId: comparePartyProfitAndLoss
description: "Requires reports.party_profitability and accounting. Includes customer/both parties,\narchived parties and those with no activity. Same dated facts as profile P&L. Summary\ncovers every party matching search/status, independently of pagination. Invoice count\nuses current non-voided invoices issued in the period; current invoice dues exclude\nopening khata balances. Untracked goods have no recorded inventory cost."
parameters:
-
in: query
name: period
description: ''
example: this-financial-year
required: false
schema:
type: string
description: ''
example: this-financial-year
enum:
- today
- this-month
- last-month
- this-financial-year
- last-financial-year
- custom
-
in: query
name: from
description: 'Custom start Y-m-d.'
example: null
required: false
schema:
type: string
description: 'Custom start Y-m-d.'
example: null
-
in: query
name: to
description: 'Inclusive custom end Y-m-d.'
example: null
required: false
schema:
type: string
description: 'Inclusive custom end Y-m-d.'
example: null
-
in: query
name: q
description: 'Party name, up to 100 characters.'
example: null
required: false
schema:
type: string
description: 'Party name, up to 100 characters.'
example: null
-
in: query
name: status
description: ''
example: all
required: false
schema:
type: string
description: ''
example: all
enum:
- active
- archived
- 'all. Default all'
-
in: query
name: sort
description: ''
example: profit
required: false
schema:
type: string
description: ''
example: profit
enum:
- profit
- sales
- 'name. Default profit'
-
in: query
name: per_page
description: 'From 1 to 100.'
example: 20
required: false
schema:
type: integer
description: 'From 1 to 100.'
example: 20
responses:
401:
description: ''
content:
application/json:
schema:
type: object
example:
message: Unauthenticated.
properties:
message:
type: string
example: Unauthenticated.
tags:
- 'Party and referrer profitability'
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
'/api/v1/businesses/{business}/reports/party-profitability/export':
get:
summary: 'Export the party profitability comparison.'
operationId: exportThePartyProfitabilityComparison
description: "Same search/status/date filters as comparison. Every matching party is included,\nindependently of pagination. Inventory costs containing estimates are labelled."
parameters:
-
in: query
name: format
description: ''
example: xlsx
required: true
schema:
type: string
description: ''
example: xlsx
enum:
- pdf
- xlsx
- csv
-
in: query
name: period
description: ''
example: this-financial-year
required: false
schema:
type: string
description: ''
example: this-financial-year
enum:
- today
- this-month
- last-month
- this-financial-year
- last-financial-year
- custom
-
in: query
name: from
description: 'Custom start Y-m-d.'
example: null
required: false
schema:
type: string
description: 'Custom start Y-m-d.'
example: null
-
in: query
name: to
description: 'Inclusive custom end Y-m-d.'
example: null
required: false
schema:
type: string
description: 'Inclusive custom end Y-m-d.'
example: null
-
in: query
name: q
description: 'Party name, up to 100 characters.'
example: null
required: false
schema:
type: string
description: 'Party name, up to 100 characters.'
example: null
-
in: query
name: status
description: ''
example: all
required: false
schema:
type: string
description: ''
example: all
enum:
- active
- archived
- 'all. Default all'
-
in: query
name: sort
description: ''
example: profit
required: false
schema:
type: string
description: ''
example: profit
enum:
- profit
- sales
- 'name. Default profit'
responses:
200:
description: 'All matching parties, current invoice dues and contribution total.'
content:
application/octet-stream:
schema:
type: string
format: binary
tags:
- 'Party and referrer profitability'
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
'/api/v1/businesses/{business}/contacts/{contact}/profitability':
get:
summary: 'Party profit and loss.'
operationId: partyProfitAndLoss
description: "Feature `reports.party_profitability`. Reports customer sales contribution, excluding\ngeneral shop overhead. Supplier spend is not treated as supplier profit/loss."
parameters:
-
in: query
name: period
description: ''
example: this-financial-year
required: false
schema:
type: string
description: ''
example: this-financial-year
enum:
- today
- this-month
- last-month
- this-financial-year
- last-financial-year
- custom
-
in: query
name: from
description: 'Custom start Y-m-d; required with custom period.'
example: null
required: false
schema:
type: string
description: 'Custom start Y-m-d; required with custom period.'
example: null
-
in: query
name: to
description: 'Inclusive custom end Y-m-d; required with custom period.'
example: null
required: false
schema:
type: string
description: 'Inclusive custom end Y-m-d; required with custom period.'
example: null
-
in: query
name: per_page
description: 'From 1 to 100.'
example: 20
required: false
schema:
type: integer
description: 'From 1 to 100.'
example: 20
responses:
401:
description: ''
content:
application/json:
schema:
type: object
example:
message: Unauthenticated.
properties:
message:
type: string
example: Unauthenticated.
tags:
- 'Party and referrer profitability'
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
-
in: path
name: contact
description: 'The contact.'
example: 1
required: true
schema:
type: integer
'/api/v1/businesses/{business}/workers/performance':
get:
summary: 'Compare referrer performance.'
operationId: compareReferrerPerformance
description: "Requires referral_workers and reports.worker_profitability. Includes referrers with no\nreferrals. Sales/costs use owner-entered shares, while each referrer's commission remains\ntheir own amount. Invoice/customer counts count current non-voided referrals issued in\nthe period; dated returns from earlier invoices affect profit independently. Dues and\nbalances are current, and shared customer dues appear under each referrer and\nmust not be added together. Last referral is the latest current non-voided invoice date."
parameters:
-
in: query
name: period
description: ''
example: this-financial-year
required: false
schema:
type: string
description: ''
example: this-financial-year
enum:
- today
- this-month
- last-month
- this-financial-year
- last-financial-year
- custom
-
in: query
name: from
description: 'Custom start Y-m-d.'
example: null
required: false
schema:
type: string
description: 'Custom start Y-m-d.'
example: null
-
in: query
name: to
description: 'Inclusive custom end Y-m-d.'
example: null
required: false
schema:
type: string
description: 'Inclusive custom end Y-m-d.'
example: null
-
in: query
name: q
description: 'Referrer name or trade, up to 100 characters.'
example: null
required: false
schema:
type: string
description: 'Referrer name or trade, up to 100 characters.'
example: null
-
in: query
name: status
description: ''
example: all
required: false
schema:
type: string
description: ''
example: all
enum:
- active
- archived
- 'all. Default all'
-
in: query
name: worker_type
description: ''
example: referral
required: false
schema:
type: string
description: ''
example: referral
enum:
- referral
- cook
- carpenter
- painter
- electrician
- plumber
- doctor
- rmp
- contractor
- sales_agent
- other
-
in: query
name: sort
description: ''
example: profit
required: false
schema:
type: string
description: ''
example: profit
enum:
- profit
- sales
- referrals
- 'name. Default profit'
-
in: query
name: per_page
description: 'From 1 to 100.'
example: 20
required: false
schema:
type: integer
description: 'From 1 to 100.'
example: 20
responses:
401:
description: ''
content:
application/json:
schema:
type: object
example:
message: Unauthenticated.
properties:
message:
type: string
example: Unauthenticated.
tags:
- 'Party and referrer profitability'
requestBody:
required: false
content:
application/json:
schema:
type: object
properties:
q:
type: string
description: 'Must not be greater than 100 characters.'
example: b
nullable: true
status:
type: string
description: ''
example: active
enum:
- active
- archived
- all
nullable: true
worker_type:
type: string
description: ''
example: architecto
nullable: true
sort:
type: string
description: ''
example: profit
enum:
- profit
- sales
- referrals
- name
nullable: true
per_page:
type: integer
description: 'Must be between 1 and 100.'
example: 2
nullable: true
page:
type: integer
description: 'Must be at least 1.'
example: 67
nullable: true
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
'/api/v1/businesses/{business}/workers/{worker}/profitability':
get:
summary: 'Referrer profit and loss.'
operationId: referrerProfitAndLoss
description: "Feature `reports.worker_profitability`, alongside `referral_workers`. Reports the shop's\ncontribution from this referrer's referred invoices. The referrer profile commission\nstatement separately reports the referrer's own earned/paid/due/recoverable amounts.\nThe `costing` object has the same inventory confidence fields as party profitability."
parameters:
-
in: query
name: period
description: ''
example: this-financial-year
required: false
schema:
type: string
description: ''
example: this-financial-year
enum:
- today
- this-month
- last-month
- this-financial-year
- last-financial-year
- custom
-
in: query
name: from
description: 'Custom start Y-m-d.'
example: null
required: false
schema:
type: string
description: 'Custom start Y-m-d.'
example: null
-
in: query
name: to
description: 'Inclusive custom end Y-m-d.'
example: null
required: false
schema:
type: string
description: 'Inclusive custom end Y-m-d.'
example: null
-
in: query
name: per_page
description: 'From 1 to 100.'
example: 20
required: false
schema:
type: integer
description: 'From 1 to 100.'
example: 20
responses:
401:
description: ''
content:
application/json:
schema:
type: object
example:
message: Unauthenticated.
properties:
message:
type: string
example: Unauthenticated.
tags:
- 'Party and referrer profitability'
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
-
in: path
name: worker
description: 'The worker.'
example: architecto
required: true
schema:
type: string
'/api/v1/businesses/{business}/price-lists':
get:
summary: 'List the rate cards of the business.'
operationId: listTheRateCardsOfTheBusiness
description: ''
parameters: []
responses:
401:
description: ''
content:
application/json:
schema:
type: object
example:
message: Unauthenticated.
properties:
message:
type: string
example: Unauthenticated.
tags:
- 'Party pricing'
requestBody:
required: false
content:
application/json:
schema:
type: object
properties:
per_page:
type: integer
description: 'Must be at least 1. Must not be greater than 100.'
example: 1
nullable: true
post:
summary: 'Create a rate card.'
operationId: createARateCard
description: "Every row is either a fixed rate or a percentage off the item master, and applies from its\n`min_quantity` upwards, so one item can carry several quantity breaks on the same card."
parameters: []
responses:
422:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'Enter either a fixed rate or a discount for this item, not both.'
errors:
items.0.sale_price:
- 'Enter either a fixed rate or a discount for this item, not both.'
properties:
message:
type: string
example: 'Enter either a fixed rate or a discount for this item, not both.'
errors:
type: object
properties:
items.0.sale_price:
type: array
example:
- 'Enter either a fixed rate or a discount for this item, not both.'
items:
type: string
tags:
- 'Party pricing'
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
name:
type: string
description: 'Name of the rate card, unique within the business. Must not be greater than 96 characters.'
example: Wholesale
description:
type: string
description: 'Must not be greater than 255 characters.'
example: 'Eius et animi quos velit et.'
nullable: true
price_includes_tax:
type: boolean
description: 'Whether the fixed rates on this card already include GST. Applies only to rows carrying a fixed rate; a percentage row follows the item master.'
example: false
is_active:
type: boolean
description: ''
example: false
valid_from:
type: string
description: 'First day the card prices anything. Leave empty for no start date. Must be a valid date.'
example: '2026-04-01'
nullable: true
valid_to:
type: string
description: 'Last day the card prices anything. Outside the window the item master applies. Must be a valid date. Must be a date after or equal to valid_from.'
example: '2027-03-31'
nullable: true
items:
type: array
description: 'The rows of the card. Sending this key replaces every existing row; omitting it leaves the rows untouched. Must not have more than 500 items.'
example: null
items:
type: object
properties:
item_id:
type: integer
description: ''
example: 16
sale_price:
type: number
description: 'This field is required when items.*.discount_percent is not present. Must be at least 0. Must not be greater than 999999999.'
example: 455
nullable: true
discount_percent:
type: number
description: 'Must be at least 0. Must not be greater than 100.'
example: 5
nullable: true
min_quantity:
type: number
description: 'Must be at least 0. Must not be greater than 999999.'
example: 25
nullable: true
required:
- item_id
required:
- name
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
'/api/v1/businesses/{business}/price-lists/{id}':
get:
summary: ''
operationId: getApiV1BusinessesBusinessPriceListsId
description: ''
parameters: []
responses:
401:
description: ''
content:
application/json:
schema:
type: object
example:
message: Unauthenticated.
properties:
message:
type: string
example: Unauthenticated.
tags:
- 'Party pricing'
put:
summary: 'Replace a rate card.'
operationId: replaceARateCard
description: "Sending `items` replaces every row on the card; leaving it out edits the card header and\nkeeps the rows. Saved documents keep the rate they were raised at either way."
parameters: []
responses: {}
tags:
- 'Party pricing'
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
name:
type: string
description: 'Name of the rate card, unique within the business. Must not be greater than 96 characters.'
example: Wholesale
description:
type: string
description: 'Must not be greater than 255 characters.'
example: 'Eius et animi quos velit et.'
nullable: true
price_includes_tax:
type: boolean
description: 'Whether the fixed rates on this card already include GST. Applies only to rows carrying a fixed rate; a percentage row follows the item master.'
example: false
is_active:
type: boolean
description: ''
example: false
valid_from:
type: string
description: 'First day the card prices anything. Leave empty for no start date. Must be a valid date.'
example: '2026-04-01'
nullable: true
valid_to:
type: string
description: 'Last day the card prices anything. Outside the window the item master applies. Must be a valid date. Must be a date after or equal to valid_from.'
example: '2027-03-31'
nullable: true
items:
type: array
description: 'The rows of the card. Sending this key replaces every existing row; omitting it leaves the rows untouched. Must not have more than 500 items.'
example: null
items:
type: object
properties:
item_id:
type: integer
description: ''
example: 16
sale_price:
type: number
description: 'This field is required when items.*.discount_percent is not present. Must be at least 0. Must not be greater than 999999999.'
example: 455
nullable: true
discount_percent:
type: number
description: 'Must be at least 0. Must not be greater than 100.'
example: 5
nullable: true
min_quantity:
type: number
description: 'Must be at least 0. Must not be greater than 999999.'
example: 25
nullable: true
required:
- item_id
required:
- name
delete:
summary: 'Delete a rate card that no party carries.'
operationId: deleteARateCardThatNoPartyCarries
description: ''
parameters: []
responses:
422:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'Move the parties on this rate card to another one before deleting it.'
errors:
price_list:
- 'Move the parties on this rate card to another one before deleting it.'
properties:
message:
type: string
example: 'Move the parties on this rate card to another one before deleting it.'
errors:
type: object
properties:
price_list:
type: array
example:
- 'Move the parties on this rate card to another one before deleting it.'
items:
type: string
tags:
- 'Party pricing'
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
-
in: path
name: id
description: 'The ID of the price list.'
example: 1
required: true
schema:
type: integer
'/api/v1/businesses/{business}/payment-accounts':
get:
summary: ''
operationId: getApiV1BusinessesBusinessPaymentAccounts
description: ''
parameters: []
responses:
401:
description: ''
content:
application/json:
schema:
type: object
example:
message: Unauthenticated.
properties:
message:
type: string
example: Unauthenticated.
tags:
- 'Payment accounts'
post:
summary: ''
operationId: postApiV1BusinessesBusinessPaymentAccounts
description: ''
parameters: []
responses: {}
tags:
- 'Payment accounts'
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
'/api/v1/businesses/{business}/payment-accounts/{paymentAccount_id}':
patch:
summary: ''
operationId: patchApiV1BusinessesBusinessPaymentAccountsPaymentAccount_id
description: ''
parameters: []
responses: {}
tags:
- 'Payment accounts'
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
-
in: path
name: paymentAccount_id
description: 'The ID of the paymentAccount.'
example: 1
required: true
schema:
type: integer
'/api/v1/businesses/{business}/payment-accounts/transfer':
post:
summary: ''
operationId: postApiV1BusinessesBusinessPaymentAccountsTransfer
description: ''
parameters: []
responses: {}
tags:
- 'Payment accounts'
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
from_payment_account_id:
type: integer
description: ''
example: 16
to_payment_account_id:
type: integer
description: 'The value and from_payment_account_id must be different.'
example: 16
amount:
type: number
description: ''
example: 4326.41688
transferred_on:
type: string
description: 'Must be a valid date.'
example: '2026-01-15'
reference:
type: string
description: 'Must not be greater than 64 characters.'
example: m
nullable: true
notes:
type: string
description: 'Must not be greater than 255 characters.'
example: i
nullable: true
required:
- from_payment_account_id
- to_payment_account_id
- amount
- transferred_on
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
'/api/v1/businesses/{business}/payment-accounts/{paymentAccount_id}/reconciliation':
get:
summary: 'Show eligible transactions and statement-reconciliation history.'
operationId: showEligibleTransactionsAndStatementReconciliationHistory
description: ''
parameters: []
responses:
401:
description: ''
content:
application/json:
schema:
type: object
example:
message: Unauthenticated.
properties:
message:
type: string
example: Unauthenticated.
tags:
- 'Payment accounts'
requestBody:
required: false
content:
application/json:
schema:
type: object
properties:
statement_date:
type: string
description: 'Must be a valid date. Must be a date before or equal to today.'
example: '2026-01-15'
nullable: true
post:
summary: 'Complete and lock a statement reconciliation.'
operationId: completeAndLockAStatementReconciliation
description: ''
parameters: []
responses: {}
tags:
- 'Payment accounts'
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
statement_date:
type: string
description: 'Must be a valid date. Must be a date before or equal to today.'
example: '2026-01-15'
statement_balance:
type: number
description: 'Must be between -999999999 and 999999999.'
example: -999999998
transaction_ids:
type: array
description: ''
example:
- 16
items:
type: integer
notes:
type: string
description: 'Must not be greater than 255 characters.'
example: 'n'
nullable: true
required:
- statement_date
- statement_balance
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
-
in: path
name: paymentAccount_id
description: 'The ID of the paymentAccount.'
example: 1
required: true
schema:
type: integer
'/api/v1/businesses/{business}/payment-accounts/{paymentAccount_id}/reconcile/{journalLine_id}':
post:
summary: ''
operationId: postApiV1BusinessesBusinessPaymentAccountsPaymentAccount_idReconcileJournalLine_id
description: ''
parameters: []
responses: {}
tags:
- 'Payment accounts'
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
-
in: path
name: paymentAccount_id
description: 'The ID of the paymentAccount.'
example: 1
required: true
schema:
type: integer
-
in: path
name: journalLine_id
description: 'The ID of the journalLine.'
example: 1
required: true
schema:
type: integer
/api/v1/admin/notifications:
get:
summary: 'List sent broadcasts.'
operationId: listSentBroadcasts
description: 'Newest first, with the filters each was sent to and how many devices Firebase accepted.'
parameters:
-
in: query
name: per_page
description: 'Results per page, from 1 to 50.'
example: 24
required: false
schema:
type: integer
description: 'Results per page, from 1 to 50.'
example: 24
responses:
200:
description: ''
content:
application/json:
schema:
type: object
example:
data:
-
id: 9
title: 'GST filing window opens tomorrow'
body: 'GSTR-3B for this month can be prepared from the GST workspace starting tomorrow.'
image_url: 'https://app.dukanam.test/storage/broadcast-images/gst.png'
link_url: 'https://app.dukanam.test/gst'
audience:
state_codes:
- '33'
city: null
store_types: []
plan_ids:
- 2
subscription_statuses:
- active
workspace_status: active
recipients: owners
audience_summary: 'Tamil Nadu · Growth plan · Active · Active workspaces · Business owners only'
businesses_count: 128
recipients_count: 96
sent_count: 94
failed_count: 2
status: sent
failure_message: null
created_by:
id: 1
name: 'Platform Admin'
created_at: '2026-09-20T09:00:00.000000Z'
sent_at: '2026-09-20T09:00:12.000000Z'
links: {}
meta:
current_page: 1
per_page: 24
total: 1
properties:
data:
type: array
example:
-
id: 9
title: 'GST filing window opens tomorrow'
body: 'GSTR-3B for this month can be prepared from the GST workspace starting tomorrow.'
image_url: 'https://app.dukanam.test/storage/broadcast-images/gst.png'
link_url: 'https://app.dukanam.test/gst'
audience:
state_codes:
- '33'
city: null
store_types: []
plan_ids:
- 2
subscription_statuses:
- active
workspace_status: active
recipients: owners
audience_summary: 'Tamil Nadu · Growth plan · Active · Active workspaces · Business owners only'
businesses_count: 128
recipients_count: 96
sent_count: 94
failed_count: 2
status: sent
failure_message: null
created_by:
id: 1
name: 'Platform Admin'
created_at: '2026-09-20T09:00:00.000000Z'
sent_at: '2026-09-20T09:00:12.000000Z'
items:
type: object
properties:
id:
type: integer
example: 9
title:
type: string
example: 'GST filing window opens tomorrow'
body:
type: string
example: 'GSTR-3B for this month can be prepared from the GST workspace starting tomorrow.'
image_url:
type: string
example: 'https://app.dukanam.test/storage/broadcast-images/gst.png'
link_url:
type: string
example: 'https://app.dukanam.test/gst'
audience:
type: object
properties:
state_codes:
type: array
example:
- '33'
items:
type: string
city:
type: string
example: null
nullable: true
store_types:
type: array
example: []
plan_ids:
type: array
example:
- 2
items:
type: integer
subscription_statuses:
type: array
example:
- active
items:
type: string
workspace_status:
type: string
example: active
recipients:
type: string
example: owners
audience_summary:
type: string
example: 'Tamil Nadu · Growth plan · Active · Active workspaces · Business owners only'
businesses_count:
type: integer
example: 128
recipients_count:
type: integer
example: 96
sent_count:
type: integer
example: 94
failed_count:
type: integer
example: 2
status:
type: string
example: sent
failure_message:
type: string
example: null
nullable: true
created_by:
type: object
properties:
id:
type: integer
example: 1
name:
type: string
example: 'Platform Admin'
created_at:
type: string
example: '2026-09-20T09:00:00.000000Z'
sent_at:
type: string
example: '2026-09-20T09:00:12.000000Z'
links:
type: object
properties: {}
meta:
type: object
properties:
current_page:
type: integer
example: 1
per_page:
type: integer
example: 24
total:
type: integer
example: 1
401:
description: ''
content:
application/json:
schema:
type: object
example:
message: Unauthenticated.
properties:
message:
type: string
example: Unauthenticated.
403:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'This action is unauthorized.'
properties:
message:
type: string
example: 'This action is unauthorized.'
tags:
- 'Platform broadcasts'
requestBody:
required: false
content:
application/json:
schema:
type: object
properties:
per_page:
type: integer
description: 'Must be at least 1. Must not be greater than 50.'
example: 1
nullable: true
post:
summary: 'Send a broadcast.'
operationId: sendABroadcast
description: "The broadcast is recorded and then pushed on the queue, so the response returns with\nstatus `queued` and `recipients_count` already resolved. Send as `multipart/form-data`\nwhen attaching an image; array filters use `state_codes[]` style keys. A filter set that\nreaches no registered device is rejected with `422` and nothing is pushed."
parameters: []
responses:
201:
description: ''
content:
application/json:
schema:
type: object
example:
data:
id: 9
title: 'GST filing window opens tomorrow'
body: 'GSTR-3B for this month can be prepared from the GST workspace starting tomorrow.'
image_url: 'https://app.dukanam.test/storage/broadcast-images/gst.png'
link_url: 'https://app.dukanam.test/gst'
audience:
state_codes:
- '33'
city: null
store_types: []
plan_ids:
- 2
subscription_statuses:
- active
workspace_status: active
recipients: owners
audience_summary: 'Tamil Nadu · Growth plan · Active · Active workspaces · Business owners only'
businesses_count: 128
recipients_count: 96
sent_count: 0
failed_count: 0
status: queued
failure_message: null
created_by:
id: 1
name: 'Platform Admin'
created_at: '2026-09-20T09:00:00.000000Z'
sent_at: null
properties:
data:
type: object
properties:
id:
type: integer
example: 9
title:
type: string
example: 'GST filing window opens tomorrow'
body:
type: string
example: 'GSTR-3B for this month can be prepared from the GST workspace starting tomorrow.'
image_url:
type: string
example: 'https://app.dukanam.test/storage/broadcast-images/gst.png'
link_url:
type: string
example: 'https://app.dukanam.test/gst'
audience:
type: object
properties:
state_codes:
type: array
example:
- '33'
items:
type: string
city:
type: string
example: null
nullable: true
store_types:
type: array
example: []
plan_ids:
type: array
example:
- 2
items:
type: integer
subscription_statuses:
type: array
example:
- active
items:
type: string
workspace_status:
type: string
example: active
recipients:
type: string
example: owners
audience_summary:
type: string
example: 'Tamil Nadu · Growth plan · Active · Active workspaces · Business owners only'
businesses_count:
type: integer
example: 128
recipients_count:
type: integer
example: 96
sent_count:
type: integer
example: 0
failed_count:
type: integer
example: 0
status:
type: string
example: queued
failure_message:
type: string
example: null
nullable: true
created_by:
type: object
properties:
id:
type: integer
example: 1
name:
type: string
example: 'Platform Admin'
created_at:
type: string
example: '2026-09-20T09:00:00.000000Z'
sent_at:
type: string
example: null
nullable: true
401:
description: ''
content:
application/json:
schema:
type: object
example:
message: Unauthenticated.
properties:
message:
type: string
example: Unauthenticated.
403:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'This action is unauthorized.'
properties:
message:
type: string
example: 'This action is unauthorized.'
422:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'No workspace matching these filters has a registered device.'
errors:
recipients:
- 'No workspace matching these filters has a registered device.'
properties:
message:
type: string
example: 'No workspace matching these filters has a registered device.'
errors:
type: object
properties:
recipients:
type: array
example:
- 'No workspace matching these filters has a registered device.'
items:
type: string
tags:
- 'Platform broadcasts'
requestBody:
required: true
content:
multipart/form-data:
schema:
type: object
properties:
state_codes:
type: array
description: 'GST state codes to target, for example 33 for Tamil Nadu. Omit to reach every state.'
example:
- '33'
- '29'
items:
type: string
city:
type: string
description: 'City to target, matched case-insensitively against the workspace address. Must not be greater than 120 characters.'
example: Coimbatore
nullable: true
store_types:
type: array
description: 'Store type slugs to target. Omit to reach every store type.'
example:
- pharmacy
items:
type: string
plan_ids:
type: array
description: "Plans whose subscribers should be targeted, matched on each workspace's newest subscription."
example:
- 2
items:
type: integer
subscription_statuses:
type: array
description: 'Subscription statuses to target: active, trialing, past_due, paused, ended, or none for workspaces that never subscribed.'
example:
- active
- trialing
items:
type: string
workspace_status:
type: string
description: 'active, suspended or any. Defaults to active.'
example: active
enum:
- any
- active
- suspended
nullable: true
recipients:
type: string
description: 'owners to reach only the business owner, members to reach every active team member. Defaults to owners.'
example: owners
enum:
- owners
- members
nullable: true
title:
type: string
description: 'Notification title, up to 120 characters. Must not be greater than 120 characters.'
example: 'GST filing window opens tomorrow'
body:
type: string
description: 'Notification message, up to 1,000 characters. Must not be greater than 1000 characters.'
example: 'GSTR-3B for this month can be prepared from the GST workspace starting tomorrow.'
image:
type: string
format: binary
description: 'Optional JPEG, PNG or WebP banner, 200x200 to 4000x4000 pixels, maximum 1 MB. Sent as multipart/form-data. Must be an image. Must not be greater than 1024 kilobytes.'
nullable: true
image_url:
type: string
description: 'Optional HTTPS URL of an already-hosted banner. Cannot be combined with image. Must be a valid URL. Must not be greater than 2048 characters.'
example: 'https://cdn.example.com/banners/gst.png'
nullable: true
link_url:
type: string
description: 'Optional URL delivered as data.url for the client to open when the push is tapped. Must be a valid URL. Must not be greater than 2048 characters.'
example: 'https://app.dukanam.test/gst'
nullable: true
required:
- title
- body
/api/v1/admin/notifications/audience:
get:
summary: 'Preview how many shops and devices a filter set reaches.'
operationId: previewHowManyShopsAndDevicesAFilterSetReaches
description: "Nothing is sent or stored. `reachable` is the number of people with a registered device,\nwhich is what a send would actually push to, and `push_enabled` reports whether Firebase\npush is configured on this deployment at all.\n\nThe list filters are spelled out here because Scribe reduces an `in`-constrained list to a\nsingle value when it reads them off the form request."
parameters:
-
in: query
name: state_codes
description: 'GST state codes to target, for example 33 for Tamil Nadu. Omit to reach every state.'
example:
- '33'
- '29'
required: false
schema:
type: array
description: 'GST state codes to target, for example 33 for Tamil Nadu. Omit to reach every state.'
example:
- '33'
- '29'
items:
type: string
-
in: query
name: city
description: 'City to target, matched case-insensitively against the workspace address. Must not be greater than 120 characters.'
example: Coimbatore
required: false
schema:
type: string
description: 'City to target, matched case-insensitively against the workspace address. Must not be greater than 120 characters.'
example: Coimbatore
nullable: true
-
in: query
name: store_types
description: 'Store type slugs to target. Omit to reach every store type.'
example:
- pharmacy
required: false
schema:
type: array
description: 'Store type slugs to target. Omit to reach every store type.'
example:
- pharmacy
items:
type: string
-
in: query
name: plan_ids
description: "Plans whose subscribers should be targeted, matched on each workspace's newest subscription."
example:
- 2
required: false
schema:
type: array
description: "Plans whose subscribers should be targeted, matched on each workspace's newest subscription."
example:
- 2
items:
type: integer
-
in: query
name: subscription_statuses
description: 'Subscription statuses to target: active, trialing, past_due, paused, ended, or none for workspaces that never subscribed.'
example:
- active
- trialing
required: false
schema:
type: array
description: 'Subscription statuses to target: active, trialing, past_due, paused, ended, or none for workspaces that never subscribed.'
example:
- active
- trialing
items:
type: string
-
in: query
name: workspace_status
description: 'active, suspended or any. Defaults to active.'
example: any
required: false
schema:
type: string
description: 'active, suspended or any. Defaults to active.'
example: any
enum:
- any
- active
- suspended
nullable: true
-
in: query
name: recipients
description: 'owners to reach only the business owner, members to reach every active team member. Defaults to owners.'
example: owners
required: false
schema:
type: string
description: 'owners to reach only the business owner, members to reach every active team member. Defaults to owners.'
example: owners
enum:
- owners
- members
nullable: true
responses:
200:
description: ''
content:
application/json:
schema:
type: object
example:
data:
businesses: 128
recipients: 141
reachable: 96
push_enabled: true
audience:
state_codes:
- '33'
city: null
store_types: []
plan_ids:
- 2
subscription_statuses:
- active
workspace_status: active
recipients: owners
audience_summary: 'Tamil Nadu · Growth plan · Active · Active workspaces · Business owners only'
properties:
data:
type: object
properties:
businesses:
type: integer
example: 128
recipients:
type: integer
example: 141
reachable:
type: integer
example: 96
push_enabled:
type: boolean
example: true
audience:
type: object
properties:
state_codes:
type: array
example:
- '33'
items:
type: string
city:
type: string
example: null
nullable: true
store_types:
type: array
example: []
plan_ids:
type: array
example:
- 2
items:
type: integer
subscription_statuses:
type: array
example:
- active
items:
type: string
workspace_status:
type: string
example: active
recipients:
type: string
example: owners
audience_summary:
type: string
example: 'Tamil Nadu · Growth plan · Active · Active workspaces · Business owners only'
401:
description: ''
content:
application/json:
schema:
type: object
example:
message: Unauthenticated.
properties:
message:
type: string
example: Unauthenticated.
403:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'This action is unauthorized.'
properties:
message:
type: string
example: 'This action is unauthorized.'
422:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'The selected recipients is invalid.'
errors:
recipients:
- 'The selected recipients is invalid.'
properties:
message:
type: string
example: 'The selected recipients is invalid.'
errors:
type: object
properties:
recipients:
type: array
example:
- 'The selected recipients is invalid.'
items:
type: string
tags:
- 'Platform broadcasts'
'/api/v1/admin/businesses/{business_id}/device-usage':
get:
summary: 'Inspect aggregate phone usage.'
operationId: inspectAggregatePhoneUsage
description: "Super-admin only, with no-store caching. Dates use Asia/Kolkata. Defaults to the last\n30 calendar days including today; maximum 366 days. Latest is the all-time latest snapshot;\nsnapshots and event totals are limited to the requested range. No installation IDs or raw reports.\nSource is device only while the latest reported mode is local; otherwise use server records."
parameters:
-
in: query
name: from
description: 'Start date, YYYY-MM-DD.'
example: '2026-09-01'
required: false
schema:
type: string
description: 'Start date, YYYY-MM-DD.'
example: '2026-09-01'
-
in: query
name: to
description: 'End date, YYYY-MM-DD.'
example: '2026-09-27'
required: false
schema:
type: string
description: 'End date, YYYY-MM-DD.'
example: '2026-09-27'
responses:
200:
description: ''
content:
application/json:
schema:
type: object
example:
data:
from: '2026-09-01'
to: '2026-09-27'
timezone: Asia/Kolkata
source: device
mode: local
app_version: 1.1.1
last_reported_at: '2026-09-27T03:44:05.000000Z'
latest:
date: '2026-09-27'
mode: local
totals:
invoices: 5
money:
billed_value_paise: 120000
reported_at: '2026-09-27T03:44:05.000000Z'
snapshots: []
events:
-
key: invoice_shared_whatsapp
label: 'Invoice shared whatsapp'
count: 2
milestones:
first_invoice_at: '2026-09-27T03:40:00.000000Z'
properties:
data:
type: object
properties:
from:
type: string
example: '2026-09-01'
to:
type: string
example: '2026-09-27'
timezone:
type: string
example: Asia/Kolkata
source:
type: string
example: device
mode:
type: string
example: local
app_version:
type: string
example: 1.1.1
last_reported_at:
type: string
example: '2026-09-27T03:44:05.000000Z'
latest:
type: object
properties:
date:
type: string
example: '2026-09-27'
mode:
type: string
example: local
totals:
type: object
properties:
invoices:
type: integer
example: 5
money:
type: object
properties:
billed_value_paise:
type: integer
example: 120000
reported_at:
type: string
example: '2026-09-27T03:44:05.000000Z'
snapshots:
type: array
example: []
events:
type: array
example:
- key: invoice_shared_whatsapp
label: 'Invoice shared whatsapp'
count: 2
items:
type: object
properties:
key:
type: string
example: invoice_shared_whatsapp
label:
type: string
example: 'Invoice shared whatsapp'
count:
type: integer
example: 2
milestones:
type: object
properties:
first_invoice_at:
type: string
example: '2026-09-27T03:40:00.000000Z'
401:
description: ''
content:
application/json:
schema:
type: object
example:
message: Unauthenticated.
properties:
message:
type: string
example: Unauthenticated.
403:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'This action is unauthorized.'
properties:
message:
type: string
example: 'This action is unauthorized.'
404:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'Not found.'
properties:
message:
type: string
example: 'Not found.'
422:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'The to field must be a date after or equal to from.'
errors:
to:
- 'The to field must be a date after or equal to from.'
properties:
message:
type: string
example: 'The to field must be a date after or equal to from.'
errors:
type: object
properties:
to:
type: array
example:
- 'The to field must be a date after or equal to from.'
items:
type: string
tags:
- 'Platform business insights'
requestBody:
required: false
content:
application/json:
schema:
type: object
properties:
from:
type: string
description: 'Must be a valid date in the format Y-m-d.'
example: '2026-01-15'
to:
type: string
description: 'Must be a valid date in the format Y-m-d.'
example: '2026-01-15'
parameters:
-
in: path
name: business_id
description: 'Business ID.'
example: 1
required: true
schema:
type: integer
'/api/v1/admin/businesses/{id}':
get:
summary: 'Inspect a business and its daily usage.'
operationId: inspectABusinessAndItsDailyUsage
description: "Requires a super-admin token; ordinary workspace members cannot access this endpoint.\nDaily usage counts records by creation time in the business timezone, including today\n(partial), with zero-filled dates in ascending order. It does not measure sessions or time spent.\nInvoices include every status. Customer contacts include type customer and both;\nsupplier contacts include type supplier and both, so a contact marked as both is counted in each.\nAdded counts include soft-deleted items, customers and suppliers; current totals exclude them.\nContact classification reflects the contact's current type. Permanently deleted records\ncannot be counted. Last record added is the latest creation across these four categories,\nregardless of the selected period. All timestamps are ISO 8601; calendar dates are YYYY-MM-DD.\n\nOwner insights include mobile activity (active within 30 days, inactive, seen, unknown),\nISO 8601 first/last seen timestamps, push registration, first observed login, and acquisition.\nInstallation status is always unknown. Mobile activity reflects mobile-scoped API tokens,\nnot proof of a current installation. First observed login starts when tracking is enabled.\nAcquisition source is referral, campaign, referring_site, direct_or_unknown, or unknown."
parameters:
-
in: query
name: days
description: 'Calendar days including today: 7, 30 or 90. Defaults to 30.'
example: 7
required: false
schema:
type: integer
description: 'Calendar days including today: 7, 30 or 90. Defaults to 30.'
example: 7
responses:
200:
description: ''
content:
application/json:
schema:
type: object
example:
data:
business:
id: 17
name: 'Sampada Stores'
legal_name: 'Sampada Retail LLP'
store_type: grocery
is_active: true
email: shop@example.com
phone: '+919876543210'
owner:
id: 42
name: 'Kavitha Reddy'
email: kavitha@example.com
phone: null
gstin: null
gst_registration_type: unregistered
address:
line_1: '14 Market Road'
line_2: 'First floor'
city: Pune
state_code: '27'
state_name: Maharashtra
pincode: '411001'
currency: INR
timezone: Asia/Kolkata
default_locale: en
created_at: '2026-09-01T04:30:00.000000Z'
usage:
days: 7
from: '2026-09-08'
to: '2026-09-14'
timezone: Asia/Kolkata
generated_at: '2026-09-14T08:00:00.000000Z'
totals:
invoices: 23
items: 12
customers: 31
suppliers: 4
added:
invoices: 3
items: 2
customers: 4
suppliers: 1
today:
date: '2026-09-14'
invoices: 3
items: 2
customers: 4
suppliers: 1
total: 10
active_days: 1
last_record_added_at: '2026-09-14T07:00:00.000000Z'
daily:
-
date: '2026-09-08'
invoices: 0
items: 0
customers: 0
suppliers: 0
total: 0
-
date: '2026-09-09'
invoices: 0
items: 0
customers: 0
suppliers: 0
total: 0
-
date: '2026-09-10'
invoices: 0
items: 0
customers: 0
suppliers: 0
total: 0
-
date: '2026-09-11'
invoices: 0
items: 0
customers: 0
suppliers: 0
total: 0
-
date: '2026-09-12'
invoices: 0
items: 0
customers: 0
suppliers: 0
total: 0
-
date: '2026-09-13'
invoices: 0
items: 0
customers: 0
suppliers: 0
total: 0
-
date: '2026-09-14'
invoices: 3
items: 2
customers: 4
suppliers: 1
total: 10
owner_insights:
mobile:
status: unknown
label: 'No app activity recorded'
installation_status: unknown
first_seen_at: null
last_seen_at: null
push_registered: false
first_observed_login:
at: null
method: null
channel: null
acquisition:
source: unknown
properties:
data:
type: object
properties:
business:
type: object
properties:
id:
type: integer
example: 17
name:
type: string
example: 'Sampada Stores'
legal_name:
type: string
example: 'Sampada Retail LLP'
store_type:
type: string
example: grocery
is_active:
type: boolean
example: true
email:
type: string
example: shop@example.com
phone:
type: string
example: '+919876543210'
owner:
type: object
properties:
id:
type: integer
example: 42
name:
type: string
example: 'Kavitha Reddy'
email:
type: string
example: kavitha@example.com
phone:
type: string
example: null
nullable: true
gstin:
type: string
example: null
nullable: true
gst_registration_type:
type: string
example: unregistered
address:
type: object
properties:
line_1:
type: string
example: '14 Market Road'
line_2:
type: string
example: 'First floor'
city:
type: string
example: Pune
state_code:
type: string
example: '27'
state_name:
type: string
example: Maharashtra
pincode:
type: string
example: '411001'
currency:
type: string
example: INR
timezone:
type: string
example: Asia/Kolkata
default_locale:
type: string
example: en
created_at:
type: string
example: '2026-09-01T04:30:00.000000Z'
usage:
type: object
properties:
days:
type: integer
example: 7
from:
type: string
example: '2026-09-08'
to:
type: string
example: '2026-09-14'
timezone:
type: string
example: Asia/Kolkata
generated_at:
type: string
example: '2026-09-14T08:00:00.000000Z'
totals:
type: object
properties:
invoices:
type: integer
example: 23
items:
type: integer
example: 12
customers:
type: integer
example: 31
suppliers:
type: integer
example: 4
added:
type: object
properties:
invoices:
type: integer
example: 3
items:
type: integer
example: 2
customers:
type: integer
example: 4
suppliers:
type: integer
example: 1
today:
type: object
properties:
date:
type: string
example: '2026-09-14'
invoices:
type: integer
example: 3
items:
type: integer
example: 2
customers:
type: integer
example: 4
suppliers:
type: integer
example: 1
total:
type: integer
example: 10
active_days:
type: integer
example: 1
last_record_added_at:
type: string
example: '2026-09-14T07:00:00.000000Z'
daily:
type: array
example:
- date: '2026-09-08'
invoices: 0
items: 0
customers: 0
suppliers: 0
total: 0
- date: '2026-09-09'
invoices: 0
items: 0
customers: 0
suppliers: 0
total: 0
- date: '2026-09-10'
invoices: 0
items: 0
customers: 0
suppliers: 0
total: 0
- date: '2026-09-11'
invoices: 0
items: 0
customers: 0
suppliers: 0
total: 0
- date: '2026-09-12'
invoices: 0
items: 0
customers: 0
suppliers: 0
total: 0
- date: '2026-09-13'
invoices: 0
items: 0
customers: 0
suppliers: 0
total: 0
- date: '2026-09-14'
invoices: 3
items: 2
customers: 4
suppliers: 1
total: 10
items:
type: object
properties:
date:
type: string
example: '2026-09-08'
invoices:
type: integer
example: 0
items:
type: integer
example: 0
customers:
type: integer
example: 0
suppliers:
type: integer
example: 0
total:
type: integer
example: 0
owner_insights:
type: object
properties:
mobile:
type: object
properties:
status:
type: string
example: unknown
label:
type: string
example: 'No app activity recorded'
installation_status:
type: string
example: unknown
first_seen_at:
type: string
example: null
nullable: true
last_seen_at:
type: string
example: null
nullable: true
push_registered:
type: boolean
example: false
first_observed_login:
type: object
properties:
at:
type: string
example: null
nullable: true
method:
type: string
example: null
nullable: true
channel:
type: string
example: null
nullable: true
acquisition:
type: object
properties:
source:
type: string
example: unknown
401:
description: ''
content:
application/json:
schema:
type: object
example:
message: Unauthenticated.
properties:
message:
type: string
example: Unauthenticated.
403:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'This action is unauthorized.'
properties:
message:
type: string
example: 'This action is unauthorized.'
404:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'Not found.'
properties:
message:
type: string
example: 'Not found.'
422:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'The selected days is invalid.'
errors:
days:
- 'The selected days is invalid.'
properties:
message:
type: string
example: 'The selected days is invalid.'
errors:
type: object
properties:
days:
type: array
example:
- 'The selected days is invalid.'
items:
type: string
tags:
- 'Platform business insights'
parameters:
-
in: path
name: id
description: 'Business ID to inspect.'
example: 17
required: true
schema:
type: integer
'/api/v1/admin/insights/{section}':
get:
summary: 'Get one overview section.'
operationId: getOneOverviewSection
description: "`today` compares today with yesterday and returns seven-day series (oldest first) for each\nheadline number, invoices per hour (hours still to come are null), active businesses by\nchannel, counts that need attention, and today's milestones. `growth` returns signups,\nactivation (5+ invoices) and signup-to-paid rates against the previous period of equal\nlength, a daily series, the activation funnel for people who signed up in the range, signup\nsources, and weekly retention for up to eight weekly cohorts of new businesses. `engagement`\nreturns active businesses today and over 7 and 30 days ending at `to`, stickiness (average\ndaily actives over the last 7 days ÷ 30-day actives), a daily series split by channel with\ninvoices and billed value, the spread of businesses by invoice count, feature adoption among\nactive businesses, the top 10 businesses by invoices, and up to 10 businesses quiet for 14+\ndays. `revenue` returns paid MRR and ARR (paying subscriptions only: no trials, no\ncomplimentary plans), money collected in the range and the previous one, GST collected,\ntrial-to-paid conversion for trials that ended in the range, six months of new and churned\nMRR, money collected per day by Razorpay and Apple, the current plan mix and a watch list."
parameters:
-
in: query
name: from
description: 'First calendar day, YYYY-MM-DD. Defaults to 29 days before `to`. Ignored by the today section. Must be a valid date in the format Y-m-d.'
example: '2026-01-15'
required: false
schema:
type: string
description: 'First calendar day, YYYY-MM-DD. Defaults to 29 days before `to`. Ignored by the today section. Must be a valid date in the format Y-m-d.'
example: '2026-01-15'
nullable: true
-
in: query
name: to
description: 'Last calendar day, YYYY-MM-DD. Defaults to today. The range may cover at most 366 days. Must be a valid date in the format Y-m-d.'
example: '2026-01-15'
required: false
schema:
type: string
description: 'Last calendar day, YYYY-MM-DD. Defaults to today. The range may cover at most 366 days. Must be a valid date in the format Y-m-d.'
example: '2026-01-15'
nullable: true
responses:
200:
description: ''
content:
application/json:
schema:
oneOf:
-
description: ''
type: object
example:
data:
date: '2026-09-26'
kpis:
new_signups:
today: 0
yesterday: 1
last_7_days:
- 1
- 0
- 0
- 0
- 0
- 1
- 0
new_businesses:
today: 0
yesterday: 1
last_7_days:
- 1
- 0
- 0
- 0
- 0
- 1
- 0
active_businesses:
today: 1
yesterday: 2
last_7_days:
- 0
- 0
- 0
- 0
- 0
- 2
- 1
share_of_businesses: 33.3
total_businesses: 3
web: 1
mobile: 1
invoices_created:
today: 2
yesterday: 1
last_7_days:
- 5
- 0
- 0
- 0
- 0
- 1
- 2
per_active_business: 2
billed_value_paise:
today: 240000
yesterday: 120000
last_7_days:
- 600000
- 0
- 0
- 0
- 0
- 120000
- 240000
average_invoice_paise: 120000
subscription_revenue_paise:
today: 33814
yesterday: 0
last_7_days:
- 0
- 0
- 0
- 0
- 0
- 0
- 33814
payments: 1
gross_paise: 39900
new_paying_businesses:
today: 1
yesterday: 0
last_7_days:
- 0
- 0
- 0
- 0
- 0
- 0
- 1
paying_businesses: 1
app_link_opens:
today: 0
yesterday: 0
last_7_days:
- 0
- 0
- 0
- 0
- 0
- 0
- 0
store_redirects: 0
link_previews: 0
invoices_by_hour:
today:
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 2
- 0
- 0
- 0
- 0
- null
- null
- null
- null
- null
- null
- null
- null
yesterday:
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 1
- 0
- 0
- 0
- 0
- 0
active_by_channel:
mobile_only: 0
web_only: 0
both: 1
records_only: 0
needs_attention:
past_due: 0
trials_ending_7_days: 1
unpaid_checkouts_7_days: 0
new_without_invoice: 0
feedback_pending: 0
testimonials_pending: 0
milestones:
-
at: '2026-09-26T15:00:00+05:30'
type: trial
business: 'Ravi Hardware'
text: 'started a Smart Books trial'
-
at: '2026-09-26T09:30:00+05:30'
type: payment
business: 'Kavya Fashions'
text: 'paid for Business · ₹399 incl. GST'
properties:
data:
type: object
properties:
date:
type: string
example: '2026-09-26'
kpis:
type: object
properties:
new_signups:
type: object
properties:
today:
type: integer
example: 0
yesterday:
type: integer
example: 1
last_7_days:
type: array
example:
- 1
- 0
- 0
- 0
- 0
- 1
- 0
items:
type: integer
new_businesses:
type: object
properties:
today:
type: integer
example: 0
yesterday:
type: integer
example: 1
last_7_days:
type: array
example:
- 1
- 0
- 0
- 0
- 0
- 1
- 0
items:
type: integer
active_businesses:
type: object
properties:
today:
type: integer
example: 1
yesterday:
type: integer
example: 2
last_7_days:
type: array
example:
- 0
- 0
- 0
- 0
- 0
- 2
- 1
items:
type: integer
share_of_businesses:
type: number
example: 33.3
total_businesses:
type: integer
example: 3
web:
type: integer
example: 1
mobile:
type: integer
example: 1
invoices_created:
type: object
properties:
today:
type: integer
example: 2
yesterday:
type: integer
example: 1
last_7_days:
type: array
example:
- 5
- 0
- 0
- 0
- 0
- 1
- 2
items:
type: integer
per_active_business:
type: integer
example: 2
billed_value_paise:
type: object
properties:
today:
type: integer
example: 240000
yesterday:
type: integer
example: 120000
last_7_days:
type: array
example:
- 600000
- 0
- 0
- 0
- 0
- 120000
- 240000
items:
type: integer
average_invoice_paise:
type: integer
example: 120000
subscription_revenue_paise:
type: object
properties:
today:
type: integer
example: 33814
yesterday:
type: integer
example: 0
last_7_days:
type: array
example:
- 0
- 0
- 0
- 0
- 0
- 0
- 33814
items:
type: integer
payments:
type: integer
example: 1
gross_paise:
type: integer
example: 39900
new_paying_businesses:
type: object
properties:
today:
type: integer
example: 1
yesterday:
type: integer
example: 0
last_7_days:
type: array
example:
- 0
- 0
- 0
- 0
- 0
- 0
- 1
items:
type: integer
paying_businesses:
type: integer
example: 1
app_link_opens:
type: object
properties:
today:
type: integer
example: 0
yesterday:
type: integer
example: 0
last_7_days:
type: array
example:
- 0
- 0
- 0
- 0
- 0
- 0
- 0
items:
type: integer
store_redirects:
type: integer
example: 0
link_previews:
type: integer
example: 0
invoices_by_hour:
type: object
properties:
today:
type: array
example:
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 2
- 0
- 0
- 0
- 0
- null
- null
- null
- null
- null
- null
- null
- null
items:
type: integer
yesterday:
type: array
example:
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 0
- 1
- 0
- 0
- 0
- 0
- 0
items:
type: integer
active_by_channel:
type: object
properties:
mobile_only:
type: integer
example: 0
web_only:
type: integer
example: 0
both:
type: integer
example: 1
records_only:
type: integer
example: 0
needs_attention:
type: object
properties:
past_due:
type: integer
example: 0
trials_ending_7_days:
type: integer
example: 1
unpaid_checkouts_7_days:
type: integer
example: 0
new_without_invoice:
type: integer
example: 0
feedback_pending:
type: integer
example: 0
testimonials_pending:
type: integer
example: 0
milestones:
type: array
example:
- at: '2026-09-26T15:00:00+05:30'
type: trial
business: 'Ravi Hardware'
text: 'started a Smart Books trial'
- at: '2026-09-26T09:30:00+05:30'
type: payment
business: 'Kavya Fashions'
text: 'paid for Business · ₹399 incl. GST'
items:
type: object
properties:
at:
type: string
example: '2026-09-26T15:00:00+05:30'
type:
type: string
example: trial
business:
type: string
example: 'Ravi Hardware'
text:
type: string
example: 'started a Smart Books trial'
-
description: engagement
type: object
example:
data:
range:
from: '2026-09-20'
to: '2026-09-26'
days: 7
kpis:
active_today: 1
active_7_days: 2
active_30_days: 3
active_businesses_total: 3
stickiness: 14.3
daily:
-
date: '2026-09-20'
mobile_only: 0
web_only: 0
both: 0
records_only: 0
invoices: 5
billed_value_paise: 600000
-
date: '2026-09-21'
mobile_only: 0
web_only: 0
both: 0
records_only: 0
invoices: 0
billed_value_paise: 0
-
date: '2026-09-22'
mobile_only: 0
web_only: 0
both: 0
records_only: 0
invoices: 0
billed_value_paise: 0
-
date: '2026-09-23'
mobile_only: 0
web_only: 0
both: 0
records_only: 0
invoices: 0
billed_value_paise: 0
-
date: '2026-09-24'
mobile_only: 0
web_only: 0
both: 0
records_only: 0
invoices: 0
billed_value_paise: 0
-
date: '2026-09-25'
mobile_only: 1
web_only: 1
both: 0
records_only: 0
invoices: 1
billed_value_paise: 120000
-
date: '2026-09-26'
mobile_only: 0
web_only: 0
both: 1
records_only: 0
invoices: 2
billed_value_paise: 240000
invoice_distribution:
-
label: None
businesses: 2
-
label: 1–10
businesses: 1
-
label: 11–50
businesses: 0
-
label: 51–200
businesses: 0
-
label: 200+
businesses: 0
feature_adoption:
-
key: sales_invoices
label: 'Sales invoices'
businesses: 1
share: 50
-
key: pos
label: 'POS counter'
businesses: 1
share: 50
-
key: payments
label: 'Payments received'
businesses: 0
share: 0
-
key: stock
label: 'Stock tracking'
businesses: 0
share: 0
-
key: purchases
label: 'Purchase bills'
businesses: 0
share: 0
-
key: expenses
label: Expenses
businesses: 0
share: 0
-
key: quotes_challans
label: 'Quotes and challans'
businesses: 0
share: 0
-
key: email
label: 'Emailed a document'
businesses: 0
share: 0
-
key: gst
label: 'GST returns'
businesses: 0
share: 0
top_businesses:
-
id: 1
name: 'Kavya Fashions'
plan: Business
invoices: 8
billed_value_paise: 960000
last_active_on: '2026-09-26'
last_channel: both
going_quiet:
-
id: 3
name: 'Durga Textiles'
last_active_on: '2026-09-05'
quiet_days: 21
invoices_ever: 3
phone: '9848012345'
properties:
data:
type: object
properties:
range:
type: object
properties:
from:
type: string
example: '2026-09-20'
to:
type: string
example: '2026-09-26'
days:
type: integer
example: 7
kpis:
type: object
properties:
active_today:
type: integer
example: 1
active_7_days:
type: integer
example: 2
active_30_days:
type: integer
example: 3
active_businesses_total:
type: integer
example: 3
stickiness:
type: number
example: 14.3
daily:
type: array
example:
- date: '2026-09-20'
mobile_only: 0
web_only: 0
both: 0
records_only: 0
invoices: 5
billed_value_paise: 600000
- date: '2026-09-21'
mobile_only: 0
web_only: 0
both: 0
records_only: 0
invoices: 0
billed_value_paise: 0
- date: '2026-09-22'
mobile_only: 0
web_only: 0
both: 0
records_only: 0
invoices: 0
billed_value_paise: 0
- date: '2026-09-23'
mobile_only: 0
web_only: 0
both: 0
records_only: 0
invoices: 0
billed_value_paise: 0
- date: '2026-09-24'
mobile_only: 0
web_only: 0
both: 0
records_only: 0
invoices: 0
billed_value_paise: 0
- date: '2026-09-25'
mobile_only: 1
web_only: 1
both: 0
records_only: 0
invoices: 1
billed_value_paise: 120000
- date: '2026-09-26'
mobile_only: 0
web_only: 0
both: 1
records_only: 0
invoices: 2
billed_value_paise: 240000
items:
type: object
properties:
date:
type: string
example: '2026-09-20'
mobile_only:
type: integer
example: 0
web_only:
type: integer
example: 0
both:
type: integer
example: 0
records_only:
type: integer
example: 0
invoices:
type: integer
example: 5
billed_value_paise:
type: integer
example: 600000
invoice_distribution:
type: array
example:
- label: None
businesses: 2
- label: 1–10
businesses: 1
- label: 11–50
businesses: 0
- label: 51–200
businesses: 0
- label: 200+
businesses: 0
items:
type: object
properties:
label:
type: string
example: None
businesses:
type: integer
example: 2
feature_adoption:
type: array
example:
- key: sales_invoices
label: 'Sales invoices'
businesses: 1
share: 50
- key: pos
label: 'POS counter'
businesses: 1
share: 50
- key: payments
label: 'Payments received'
businesses: 0
share: 0
- key: stock
label: 'Stock tracking'
businesses: 0
share: 0
- key: purchases
label: 'Purchase bills'
businesses: 0
share: 0
- key: expenses
label: Expenses
businesses: 0
share: 0
- key: quotes_challans
label: 'Quotes and challans'
businesses: 0
share: 0
- key: email
label: 'Emailed a document'
businesses: 0
share: 0
- key: gst
label: 'GST returns'
businesses: 0
share: 0
items:
type: object
properties:
key:
type: string
example: sales_invoices
label:
type: string
example: 'Sales invoices'
businesses:
type: integer
example: 1
share:
type: integer
example: 50
top_businesses:
type: array
example:
- id: 1
name: 'Kavya Fashions'
plan: Business
invoices: 8
billed_value_paise: 960000
last_active_on: '2026-09-26'
last_channel: both
items:
type: object
properties:
id:
type: integer
example: 1
name:
type: string
example: 'Kavya Fashions'
plan:
type: string
example: Business
invoices:
type: integer
example: 8
billed_value_paise:
type: integer
example: 960000
last_active_on:
type: string
example: '2026-09-26'
last_channel:
type: string
example: both
going_quiet:
type: array
example:
- id: 3
name: 'Durga Textiles'
last_active_on: '2026-09-05'
quiet_days: 21
invoices_ever: 3
phone: '9848012345'
items:
type: object
properties:
id:
type: integer
example: 3
name:
type: string
example: 'Durga Textiles'
last_active_on:
type: string
example: '2026-09-05'
quiet_days:
type: integer
example: 21
invoices_ever:
type: integer
example: 3
phone:
type: string
example: '9848012345'
401:
description: ''
content:
application/json:
schema:
type: object
example:
message: Unauthenticated.
properties:
message:
type: string
example: Unauthenticated.
403:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'This action is unauthorized.'
properties:
message:
type: string
example: 'This action is unauthorized.'
404:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'Not found.'
properties:
message:
type: string
example: 'Not found.'
422:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'Choose a range of at most 366 days.'
errors:
from:
- 'Choose a range of at most 366 days.'
properties:
message:
type: string
example: 'Choose a range of at most 366 days.'
errors:
type: object
properties:
from:
type: array
example:
- 'Choose a range of at most 366 days.'
items:
type: string
tags:
- 'Platform overview'
parameters:
-
in: path
name: section
description: 'today, growth, engagement or revenue.'
example: engagement
required: true
schema:
type: string
/api/v1/fcm-test:
post:
summary: ''
operationId: postApiV1FcmTest
description: ''
parameters: []
responses:
200:
description: ''
content:
application/json:
schema:
type: object
example:
data:
sent: true
token: eY2x9_e…n_token
title: 'Stock alert'
body: 'Rice is running low.'
data:
type: low_stock
item_id: '7'
properties:
data:
type: object
properties:
sent:
type: boolean
example: true
token:
type: string
example: eY2x9_e…n_token
title:
type: string
example: 'Stock alert'
body:
type: string
example: 'Rice is running low.'
data:
type: object
properties:
type:
type: string
example: low_stock
item_id:
type: string
example: '7'
404:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'Not Found.'
properties:
message:
type: string
example: 'Not Found.'
422:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'The data payload must be a flat object.'
errors:
data:
- 'The data payload must be a flat object.'
properties:
message:
type: string
example: 'The data payload must be a flat object.'
errors:
type: object
properties:
data:
type: array
example:
- 'The data payload must be a flat object.'
items:
type: string
503:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'Firebase push is disabled. Set FIREBASE_PUSH_ENABLED=true and provide a readable service-account credentials file.'
properties:
message:
type: string
example: 'Firebase push is disabled. Set FIREBASE_PUSH_ENABLED=true and provide a readable service-account credentials file.'
tags:
- 'Push testing'
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
fcm_token:
type: string
description: 'The device registration token to push to.'
example: eY2x9_example_fcm_registration_token
title:
type: string
description: 'Notification title, max 120 characters.'
example: 'Stock alert'
body:
type: string
description: 'Notification message, max 1000 characters.'
example: 'Rice is running low.'
data:
type: object
description: 'optional Flat key/value data payload. Values are delivered as strings.'
example:
type: low_stock
item_id: '7'
properties: {}
nullable: true
required:
- fcm_token
- title
- body
security: []
'/api/v1/businesses/{business}/invoices/{invoice}/workers':
get:
summary: "Read the invoice's current referrers."
operationId: readTheInvoicesCurrentReferrers
description: "Finance-only assignments are separate from customer-facing invoice payloads.\nCommission amounts are original earnings before return adjustments; use the referrer\nstatement for debit notes and current balances. Voided invoices have no active awards."
parameters: []
responses:
401:
description: ''
content:
application/json:
schema:
type: object
example:
message: Unauthenticated.
properties:
message:
type: string
example: Unauthenticated.
tags:
- 'Referrers and referral commissions'
put:
summary: 'Set all invoice referrers and commission rules.'
operationId: setAllInvoiceReferrersAndCommissionRules
description: "Replaces the complete assignment set atomically (maximum 10, no duplicate referrers).\nEvery performance share is required and shares must total 10000 (100%). Shares allocate\nsales, goods cost and shared invoice expenses exactly once; each commission is calculated\nindependently. An empty array removes all assignments through compensating corrections.\nFixed commissions reduce proportionally on returns, with cumulative paise rounding.\nChanging assignment rules keeps every previous earning, debit note and payout in history."
parameters: []
responses: {}
tags:
- 'Referrers and referral commissions'
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
workers:
type: array
description: 'Complete assignment set, or [] to remove all.'
example:
-
worker_id: 1
commission_kind: percentage
rate_basis_points: 1000
attribution_basis_points: 6000
-
worker_id: 2
commission_kind: fixed
fixed_amount_paise: 20000
attribution_basis_points: 4000
items:
type: object
properties:
worker_id:
type: integer
description: 'Active referrer in this business.'
example: 1
commission_kind:
type: string
description: ''
example: percentage
enum:
- percentage
- fixed
rate_basis_points:
type: integer
description: 'Percentage from 0 to 10000; omitted retains the saved rate or uses the referrer default for a new assignment.'
example: 1000
fixed_amount_paise:
type: integer
description: 'Required for fixed commission, 0 to 99999999900.'
example: null
attribution_basis_points:
type: integer
description: 'Owner-entered share from 1 to 10000; shares total 10000.'
example: 6000
required:
- worker_id
- commission_kind
- attribution_basis_points
required:
- workers
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
-
in: path
name: invoice
description: 'The invoice.'
example: 1
required: true
schema:
type: integer
'/api/v1/businesses/{business}/workers':
get:
summary: 'List referrers.'
operationId: listReferrers
description: ''
parameters:
-
in: query
name: q
description: 'Search name, trade or phone.'
example: carpenter
required: false
schema:
type: string
description: 'Search name, trade or phone.'
example: carpenter
-
in: query
name: status
description: ''
example: active
required: false
schema:
type: string
description: ''
example: active
enum:
- active
- archived
- all
-
in: query
name: worker_type
description: ''
example: referral
required: false
schema:
type: string
description: ''
example: referral
enum:
- referral
- cook
- carpenter
- painter
- electrician
- plumber
- doctor
- rmp
- contractor
- sales_agent
- other
-
in: query
name: per_page
description: 'From 1 to 100.'
example: 20
required: false
schema:
type: integer
description: 'From 1 to 100.'
example: 20
responses:
200:
description: ''
content:
application/json:
schema:
type: object
example:
data: []
current_page: 1
per_page: 20
total: 0
properties:
data:
type: array
example: []
current_page:
type: integer
example: 1
per_page:
type: integer
example: 20
total:
type: integer
example: 0
tags:
- 'Referrers and referral commissions'
requestBody:
required: false
content:
application/json:
schema:
type: object
properties:
q:
type: string
description: 'Must not be greater than 100 characters.'
example: b
nullable: true
status:
type: string
description: ''
example: active
enum:
- active
- archived
- all
nullable: true
worker_type:
type: string
description: ''
example: architecto
nullable: true
per_page:
type: integer
description: 'Must be between 1 and 100.'
example: 2
nullable: true
post:
summary: 'Add a referrer.'
operationId: addAReferrer
description: "Aadhaar is optional, encrypted at rest, and returned only as its masked last four digits.\nPAN and bank details are encrypted, excluded from audit metadata and raw model serialization.\nLists and write responses mask bank numbers; only the owner/administrator detail response includes the full number.\nPhotos are private and require the same workspace, plan and role checks as a profile."
parameters: []
responses:
201:
description: ''
content:
application/json:
schema:
type: object
example:
data:
id: 1
name: Ravi
worker_type: carpenter
worker_type_label: Carpenter
commission_rate_basis_points: 1000
is_active: true
balance_paise: 0
commission_due_paise: 0
recoverable_paise: 0
aadhaar_masked: null
pan_masked: null
bank_account: null
properties:
data:
type: object
properties:
id:
type: integer
example: 1
name:
type: string
example: Ravi
worker_type:
type: string
example: carpenter
worker_type_label:
type: string
example: Carpenter
commission_rate_basis_points:
type: integer
example: 1000
is_active:
type: boolean
example: true
balance_paise:
type: integer
example: 0
commission_due_paise:
type: integer
example: 0
recoverable_paise:
type: integer
example: 0
aadhaar_masked:
type: string
example: null
nullable: true
pan_masked:
type: string
example: null
nullable: true
bank_account:
type: string
example: null
nullable: true
tags:
- 'Referrers and referral commissions'
requestBody:
required: true
content:
multipart/form-data:
schema:
type: object
properties:
name:
type: string
description: "Referrer's name."
example: Ravi
worker_type:
type: string
description: 'Defaults to referral when omitted.'
example: carpenter
enum:
- referral
- cook
- carpenter
- painter
- electrician
- plumber
- doctor
- rmp
- contractor
- sales_agent
- other
trade:
type: string
description: 'Trade or referral role.'
example: Carpenter
phone:
type: string
description: 'Phone number.'
example: '9876543210'
email:
type: string
description: 'Email address.'
example: ravi@example.com
aadhaar_number:
type: string
description: 'Optional 12 digits.'
example: null
pan_number:
type: string
description: 'Optional PAN, 5 letters + 4 digits + 1 letter; trimmed and uppercased. Blank retains the saved value.'
example: ABCDE1234F
clear_pan:
type: boolean
description: 'Remove the saved PAN.'
example: false
bank_account:
type: object
description: 'Optional payout bank details. Omit or leave all fields blank to retain; partial updates merge with saved details.'
example:
bank_name: 'Example Bank'
account_holder: 'Ravi Kumar'
account_number: '001234567890'
ifsc: HDFC0000123
branch: Hyderabad
properties:
bank_name:
type: string
description: 'Bank name, up to 128 characters; required for a new bank account.'
example: null
account_holder:
type: string
description: 'Account holder, up to 128 characters; required for a new bank account.'
example: null
account_number:
type: string
description: '6–34 digits as a string, preserving leading zeroes; required for a new bank account.'
example: null
ifsc:
type: string
description: 'Valid 11-character IFSC, trimmed and uppercased; required for a new bank account.'
example: null
branch:
type: string
description: 'Optional branch, up to 128 characters; blank clears the branch when other bank fields are provided.'
example: null
clear_bank_account:
type: boolean
description: 'Remove all saved bank details.'
example: false
contact_id:
type: integer
description: "Customer account for the referrer's own purchases."
example: null
commission_rate_basis_points:
type: integer
description: 'Default percentage, 0 to 10000.'
example: 1000
notes:
type: string
description: 'Internal notes, up to 2000 characters.'
example: null
photo:
type: string
format: binary
description: 'Private JPEG, PNG or WebP, at most 2 MB and 8192 pixels per side. Omit to retain.'
remove_photo:
type: boolean
description: 'Set true to remove the saved photo; cannot be combined with an upload.'
example: false
required:
- name
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
'/api/v1/businesses/{business}/workers/{worker}':
get:
summary: 'Referrer profile and commission statement.'
operationId: referrerProfileAndCommissionStatement
description: "Earnings are independent of collection. The statement retains every payout, debit note\nand compensating correction. A negative balance is recoverable from the referrer. Referred\ninvoice dues and the referrer's own customer-account dues are separate current balances.\n\nFull bank account numbers are returned only here for owners and administrators; fetch fresh and do not cache offline.\nAadhaar and PAN remain masked. The response is served with Cache-Control: no-store, private."
parameters:
-
in: query
name: period
description: ''
example: this-financial-year
required: false
schema:
type: string
description: ''
example: this-financial-year
enum:
- today
- this-month
- last-month
- this-financial-year
- last-financial-year
- custom
-
in: query
name: from
description: 'Custom period start, Y-m-d; both dates required.'
example: null
required: false
schema:
type: string
description: 'Custom period start, Y-m-d; both dates required.'
example: null
-
in: query
name: to
description: 'Inclusive custom end, Y-m-d.'
example: null
required: false
schema:
type: string
description: 'Inclusive custom end, Y-m-d.'
example: null
-
in: query
name: page
description: 'Statement page.'
example: 1
required: false
schema:
type: integer
description: 'Statement page.'
example: 1
-
in: query
name: per_page
description: 'From 1 to 100.'
example: 20
required: false
schema:
type: integer
description: 'From 1 to 100.'
example: 20
responses:
401:
description: ''
content:
application/json:
schema:
type: object
example:
message: Unauthenticated.
properties:
message:
type: string
example: Unauthenticated.
tags:
- 'Referrers and referral commissions'
requestBody:
required: false
content:
application/json:
schema:
type: object
properties:
per_page:
type: integer
description: 'Must be between 1 and 100.'
example: 2
nullable: true
page:
type: integer
description: 'Must be at least 1.'
example: 22
nullable: true
put:
summary: 'Update or archive a referrer.'
operationId: updateOrArchiveAReferrer
description: "Archiving preserves financial history and prevents new assignments. Changing a default\npercentage affects future assignments only. Leave Aadhaar blank to retain it; pass\n`clear_aadhaar=true` to remove it. Existing payouts and invoice rates are retained."
parameters: []
responses: {}
tags:
- 'Referrers and referral commissions'
requestBody:
required: true
content:
multipart/form-data:
schema:
type: object
properties:
name:
type: string
description: "Referrer's name."
example: Ravi
worker_type:
type: string
description: 'Omit to retain the saved type.'
example: carpenter
enum:
- referral
- cook
- carpenter
- painter
- electrician
- plumber
- doctor
- rmp
- contractor
- sales_agent
- other
trade:
type: string
description: 'Trade or referral role.'
example: Carpenter
phone:
type: string
description: 'Phone number.'
example: '9876543210'
email:
type: string
description: 'Email address.'
example: ravi@example.com
aadhaar_number:
type: string
description: 'Optional 12 digits.'
example: null
pan_number:
type: string
description: 'Optional PAN, 5 letters + 4 digits + 1 letter; trimmed and uppercased. Blank retains the saved value.'
example: ABCDE1234F
clear_pan:
type: boolean
description: 'Remove the saved PAN.'
example: false
bank_account:
type: object
description: 'Optional payout bank details. Omit or leave all fields blank to retain; partial updates merge with saved details.'
example:
bank_name: 'Example Bank'
account_holder: 'Ravi Kumar'
account_number: '001234567890'
ifsc: HDFC0000123
branch: Hyderabad
properties:
bank_name:
type: string
description: 'Bank name, up to 128 characters; required for a new bank account.'
example: null
account_holder:
type: string
description: 'Account holder, up to 128 characters; required for a new bank account.'
example: null
account_number:
type: string
description: '6–34 digits as a string, preserving leading zeroes; required for a new bank account.'
example: null
ifsc:
type: string
description: 'Valid 11-character IFSC, trimmed and uppercased; required for a new bank account.'
example: null
branch:
type: string
description: 'Optional branch, up to 128 characters; blank clears the branch when other bank fields are provided.'
example: null
clear_bank_account:
type: boolean
description: 'Remove all saved bank details.'
example: false
clear_aadhaar:
type: boolean
description: 'Remove the stored identifier.'
example: false
contact_id:
type: integer
description: 'Linked own customer account.'
example: null
commission_rate_basis_points:
type: integer
description: 'Default rate, 0 to 10000.'
example: 1000
is_active:
type: boolean
description: 'Whether new referrals can be assigned.'
example: true
notes:
type: string
description: 'Internal notes.'
example: null
photo:
type: string
format: binary
description: 'Private JPEG, PNG or WebP, at most 2 MB and 8192 pixels per side. Omit to retain.'
remove_photo:
type: boolean
description: 'Set true to remove the saved photo; cannot be combined with an upload.'
example: false
required:
- name
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
-
in: path
name: worker
description: 'The worker.'
example: architecto
required: true
schema:
type: string
'/api/v1/businesses/{business}/workers/{worker}/photo':
get:
summary: "View the referrer's private photo."
operationId: viewTheReferrersPrivatePhoto
description: "Production photos use private S3; legacy photos use their recorded disk.\nThis authenticated route never exposes an object key or public bucket URL.\n\nReturns an authenticated private image, never a public storage URL."
parameters: []
responses:
401:
description: ''
content:
application/json:
schema:
type: object
example:
message: Unauthenticated.
properties:
message:
type: string
example: Unauthenticated.
404:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'Not Found'
properties:
message:
type: string
example: 'Not Found'
tags:
- 'Referrers and referral commissions'
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
-
in: path
name: worker
description: 'The worker.'
example: architecto
required: true
schema:
type: string
'/api/v1/businesses/{business}/workers/{worker}/payouts':
post:
summary: 'Pay earned commission.'
operationId: payEarnedCommission
description: "Partial/full payment is the owner's decision, independent of customer receipts. Paying\nreduces commission payable and the selected cash/bank account, without adding another\nexpense. Cash requires an open register. Overpayments are rejected; retries require the\nsame UUID and identical amount/date/method/account/reference, otherwise 422."
parameters: []
responses: {}
tags:
- 'Referrers and referral commissions'
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
amount_paise:
type: integer
description: 'Positive paise, no more than current commission due.'
example: 30000
occurred_on:
type: string
description: 'Payment date, Y-m-d.'
example: '2026-10-04'
payment_method:
type: string
description: ''
example: bank
enum:
- cash
- bank
- upi
- card
- other
payment_account_id:
type: integer
description: 'Active compatible payment account in this business.'
example: null
nullable: true
reference:
type: string
description: 'Transfer reference, up to 128 characters.'
example: null
nullable: true
idempotency_key:
type: string
description: 'Stable UUID for retries.'
example: e0e7b8fb-96bb-44bd-95f8-aad1eb6cf473
required:
- amount_paise
- occurred_on
- payment_method
- idempotency_key
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
-
in: path
name: worker
description: 'The worker.'
example: architecto
required: true
schema:
type: string
'/api/v1/businesses/{business}/workers/{worker}/payouts/{entry}/void':
post:
summary: 'Void a referrer payout.'
operationId: voidAReferrerPayout
description: "Adds a current-date compensating event and reverses the cash/bank posting. Original\nearning, payout and return records remain. Repeated void requests do not duplicate it."
parameters: []
responses: {}
tags:
- 'Referrers and referral commissions'
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
reason:
type: string
description: 'Why the payment is being corrected, up to 1000 characters.'
example: 'Incorrect bank transfer'
required:
- reason
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
-
in: path
name: worker
description: 'The worker.'
example: architecto
required: true
schema:
type: string
-
in: path
name: entry
description: ''
example: architecto
required: true
schema:
type: string
'/api/v1/businesses/{business}/invoices/{invoice}/worker':
post:
summary: 'Assign the invoice referrer and earn commission.'
operationId: assignTheInvoiceReferrerAndEarnCommission
description: "Assignment is idempotent for an unchanged referrer/rate/invoice. Overrides require an\nowner or administrator. Reassignment appends financial corrections on the original dates;\nprior payouts remain in the previous referrer's account. Existing returns receive debit\nnotes using cumulative rounding. Customer invoice print/shared payloads omit commissions."
parameters: []
responses:
200:
description: ''
content:
application/json:
schema:
type: object
example:
data:
id: 1
worker_id: 1
invoice_id: 1
rate_basis_points: 1000
basis_paise: 300000
earned_paise: 30000
earned_on: '2026-01-10'
properties:
data:
type: object
properties:
id:
type: integer
example: 1
worker_id:
type: integer
example: 1
invoice_id:
type: integer
example: 1
rate_basis_points:
type: integer
example: 1000
basis_paise:
type: integer
example: 300000
earned_paise:
type: integer
example: 30000
earned_on:
type: string
example: '2026-01-10'
tags:
- 'Referrers and referral commissions'
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
attribution_basis_points:
type: integer
description: 'Owner-entered single-referrer share; must be 10000.'
example: 10000
worker_id:
type: integer
description: 'Active referrer in this business.'
example: 1
rate_basis_points:
type: integer
description: 'Optional override from 0 to 10000; omitted uses the referrer default.'
example: 1000
nullable: true
required:
- attribution_basis_points
- worker_id
parameters:
-
in: path
name: business
description: 'The business.'
example: 1
required: true
schema:
type: integer
-
in: path
name: invoice
description: 'The invoice.'
example: 1
required: true
schema:
type: integer
'/api/v1/businesses/{business_id}/usage-report':
post:
summary: 'Report aggregate phone usage.'
operationId: reportAggregatePhoneUsage
description: "Available to every active member, including free workspaces with unfinished onboarding.\nMaximum 64 KB and 30 requests per hour per user, independent of the general API limit.\nReusing a report_id returns 204 without applying event deltas again (90-day deduplication window).\nUnknown numeric metrics are retained; unknown text fields are discarded, never stored.\nMetric keys must be snake_case, at most 64 characters. Counts must be JSON integers,\nnot numeric strings. Server dates use Asia/Kolkata; phone timestamps are metadata.\nTotals replace the day's snapshot; events accumulate. Competing local installs keep the\nhigher invoice count (ties retain the existing snapshot). Server mode may omit totals/money."
parameters: []
responses:
204:
description: ''
content:
application/json:
schema:
type: object
example: {}
properties: {}
401:
description: ''
content:
application/json:
schema:
type: object
example:
message: Unauthenticated.
properties:
message:
type: string
example: Unauthenticated.
404:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'Not found.'
properties:
message:
type: string
example: 'Not found.'
413:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'Usage reports must not exceed 64 KB.'
properties:
message:
type: string
example: 'Usage reports must not exceed 64 KB.'
422:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'The report id field is required.'
errors:
report_id:
- 'The report id field is required.'
properties:
message:
type: string
example: 'The report id field is required.'
errors:
type: object
properties:
report_id:
type: array
example:
- 'The report id field is required.'
items:
type: string
429:
description: ''
content:
application/json:
schema:
type: object
example:
message: 'Too Many Attempts.'
properties:
message:
type: string
example: 'Too Many Attempts.'
tags:
- 'Usage reports'
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
report_id:
type: string
description: 'Unique report UUID.'
example: 9b2f6c1e-4a7d-4f0e-9a51-3d2c8e7b6a10
installation_id:
type: string
description: 'Random installation UUID, never a hardware ID.'
example: 5d8a1f3c-7e20-4b6a-8c11-0f9e2d4b7a33
reported_at:
type: string
description: 'ISO 8601 timestamp with offset.'
example: '2026-09-27T09:14:05+05:30'
period_start:
type: string
description: 'Nullable ISO 8601 timestamp with offset.'
example: '2026-09-26T08:02:11+05:30'
nullable: true
mode:
type: string
description: 'local or server.'
example: local
app:
type: object
description: 'Application metadata.'
example: []
properties:
version:
type: string
description: 'Version, maximum 32 characters.'
example: 1.1.1
build:
type: integer
description: 'Nonnegative build number.'
example: 20
platform:
type: string
description: 'android or ios.'
example: android
os_version:
type: string
description: 'Must match the regex /^[a-zA-Z0-9._+-]+$/. Must not be greater than 32 characters.'
example: g
locale:
type: string
description: 'Must match the regex /^[a-zA-Z_-]+$/. Must not be greater than 32 characters.'
example: en_MT
theme:
type: string
description: 'Must match the regex /^[a-z_]+$/. Must not be greater than 32 characters.'
example: m
required:
- version
- build
- platform
totals:
type: object
description: 'Running record counts, each integer 0–10000000.'
example:
invoices: 5
customers: 2
properties: {}
money:
type: object
description: 'Running sums in paise, each integer -100000000000000–100000000000000.'
example:
billed_value_paise: 120000
properties: {}
milestones:
type: object
description: 'Nullable ISO timestamps: earliest first_* and onboarding_completed_at, latest last_*.'
example:
first_invoice_at: '2026-09-27T09:10:00+05:30'
properties:
first_invoice_at:
type: string
description: 'Must be a valid date. Must match the regex /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,6})?(?:Z|[+-]\d{2}:\d{2})$/D.'
example: '2026-01-15'
nullable: true
last_invoice_at:
type: string
description: 'Must be a valid date. Must match the regex /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,6})?(?:Z|[+-]\d{2}:\d{2})$/D.'
example: '2026-01-15'
nullable: true
first_payment_at:
type: string
description: 'Must be a valid date. Must match the regex /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,6})?(?:Z|[+-]\d{2}:\d{2})$/D.'
example: '2026-01-15'
nullable: true
last_payment_at:
type: string
description: 'Must be a valid date. Must match the regex /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,6})?(?:Z|[+-]\d{2}:\d{2})$/D.'
example: '2026-01-15'
nullable: true
first_record_at:
type: string
description: 'Must be a valid date. Must match the regex /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,6})?(?:Z|[+-]\d{2}:\d{2})$/D.'
example: '2026-01-15'
nullable: true
last_record_at:
type: string
description: 'Must be a valid date. Must match the regex /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,6})?(?:Z|[+-]\d{2}:\d{2})$/D.'
example: '2026-01-15'
nullable: true
onboarding_completed_at:
type: string
description: 'Must be a valid date. Must match the regex /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,6})?(?:Z|[+-]\d{2}:\d{2})$/D.'
example: '2026-01-15'
nullable: true
first_share_at:
type: string
description: 'Must be a valid date. Must match the regex /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,6})?(?:Z|[+-]\d{2}:\d{2})$/D.'
example: '2026-01-15'
nullable: true
events:
type: object
description: 'At most 300 action deltas, each integer 0–100000.'
example:
invoice_created: 5
invoice_shared_whatsapp: 2
properties: {}
required:
- report_id
- installation_id
- reported_at
- mode
- app
parameters:
-
in: path
name: business_id
description: 'Business ID.'
example: 1
required: true
schema:
type: integer