openapi: 3.2.0 info: title: GSTIN Bulk API version: 1.1.0 summary: Verify Indian GST registration numbers, one at a time or in bulk. description: 'Live GSTIN verification against the Indian GST register, sourced through a GST Suvidha Provider network rather than by scraping the public portal.' termsOfService: https://gstinapi.com/terms license: name: Commercial — see terms of service url: https://gstinapi.com/terms contact: name: GSTIN API support url: https://gstinapi.com/contact email: contact@gstinapi.com x-documentation: https://gstinapi.com/docs servers: - url: https://gstinapi.com description: Production security: - ApiKeyAuth: [] tags: - name: Bulk description: Verify a list of GSTINs as an asynchronous job. paths: /api/bulk/jobs: post: tags: - Bulk operationId: createBulkJob summary: Submit a list of GSTINs description: 'Accepts up to 10000 GSTINs and returns immediately with a job ID; the list is verified in the background at a rate this service controls, so no client-side batching or rate limiting is needed. Supply either `gstins` as an array, or `text` as a blob to have the GSTINs pulled out of it — one per line, comma separated, or pasted straight from a spreadsheet column. Malformed entries are settled at submission without ever reaching the government source. Duplicates are verified once and charged once, but still appear as separate rows in the results, in the order submitted. An account may hold 3 unfinished jobs at once. Throttled to 20 submissions per minute.' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/BulkJobRequest' examples: array: summary: An array of GSTINs value: name: Vendor master gstins: - 27AAACR5055K1Z7 - 29AAACI1195H1ZI pasted: summary: A pasted spreadsheet column value: text: '27AAACR5055K1Z7 29AAACI1195H1ZI ' responses: '202': description: Accepted and queued. Poll the job to follow it. content: application/json: schema: $ref: '#/components/schemas/BulkJobEnvelope' '401': $ref: '#/components/responses/Unauthorized' '402': $ref: '#/components/responses/PaymentRequired' '422': description: 'Nothing usable in the submission: `no_gstins` when no candidates were found, `too_many_gstins` when the list is over the per-job ceiling.' content: application/json: schema: $ref: '#/components/schemas/Error' '429': description: Either the submission throttle, or `too_many_active_jobs` when the account already holds the maximum number of unfinished jobs. content: application/json: schema: $ref: '#/components/schemas/Error' get: tags: - Bulk operationId: listBulkJobs summary: List recent jobs description: Jobs belonging to the account, newest first. Jobs are scoped to the account rather than the key, so a rotated key still reaches the jobs it started. parameters: - name: per_page in: query schema: type: integer default: 25 minimum: 1 maximum: 100 - name: page in: query schema: type: integer minimum: 1 responses: '200': description: A page of jobs. content: application/json: schema: type: object properties: success: type: boolean jobs: type: array items: $ref: '#/components/schemas/BulkJob' pagination: $ref: '#/components/schemas/Pagination' '401': $ref: '#/components/responses/Unauthorized' /api/bulk/jobs/{job_id}: get: tags: - Bulk operationId: getBulkJob summary: Check a job description: 'The endpoint to poll. Every few seconds is fine — it reads one row and never touches the government source. Poll until `status` is `completed`, `cancelled` or `failed`. A completed job carrying `stopped_reason: insufficient_credits` ran out of balance part way through: the rows it never reached are marked `skipped` in the results and were not charged.' parameters: - $ref: '#/components/parameters/JobId' responses: '200': description: The job as it stands. content: application/json: schema: $ref: '#/components/schemas/BulkJobEnvelope' '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/JobNotFound' delete: tags: - Bulk operationId: cancelBulkJob summary: Cancel a running job description: Rows already verified keep their results and remain charged. Nothing still pending is looked up or billed — those rows become `skipped`. parameters: - $ref: '#/components/parameters/JobId' responses: '200': description: Cancelled. content: application/json: schema: type: object properties: success: type: boolean job: $ref: '#/components/schemas/BulkJob' '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/JobNotFound' '409': description: '`already_finished` — the job had already completed, failed or been cancelled.' content: application/json: schema: $ref: '#/components/schemas/Error' /api/bulk/jobs/{job_id}/results: get: tags: - Bulk operationId: getBulkResults summary: Collect the results description: 'One entry per submitted row, in the order submitted, paginated. Readable while the job is still running — rows appear as they are verified — so a client that wants to start processing early can. Pass `format=csv` for a spreadsheet instead of JSON, or `outcome` to take only certain rows.' parameters: - $ref: '#/components/parameters/JobId' - name: format in: query description: Pass `csv` to stream the results as a CSV download instead of JSON. schema: type: string enum: - json - csv - name: outcome in: query description: Comma-separated outcomes to include, e.g. `ok` for verified rows only, or `not_found,invalid_format` to review the rest. schema: type: string example: ok - name: per_page in: query schema: type: integer default: 100 minimum: 1 maximum: 1000 - name: page in: query schema: type: integer minimum: 1 responses: '200': description: A page of results, or the whole job as CSV. content: application/json: schema: type: object properties: success: type: boolean job: $ref: '#/components/schemas/BulkJob' results: type: array items: $ref: '#/components/schemas/BulkResultRow' pagination: $ref: '#/components/schemas/Pagination' text/csv: schema: type: string format: binary '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/JobNotFound' components: schemas: Pagination: type: object properties: page: type: integer per_page: type: integer total: type: integer last_page: type: integer has_more: type: boolean BulkJob: type: object description: A submitted list and how far through it we are. properties: job_id: type: string format: uuid status: type: string enum: - queued - processing - completed - cancelled - failed description: Terminal states are `completed`, `cancelled` and `failed`. Stop polling on any of them. stopped_reason: type: - string - 'null' enum: - insufficient_credits - cancelled - null description: Why a job ended before reaching every row. Null on a job that ran to completion normally. name: type: - string - 'null' source: type: string enum: - api - dashboard progress: type: object properties: percent: type: integer minimum: 0 maximum: 100 total_rows: type: integer description: Entries submitted, duplicates included. total_lookups: type: integer description: Distinct valid GSTINs — what actually reaches the government source, and the most this job can cost. completed_lookups: type: integer results: type: object description: Outcome tallies, counted over distinct GSTINs rather than rows. properties: succeeded: type: integer not_found: type: integer invalid: type: integer failed: type: integer skipped: type: integer credits_used: type: integer description: Credits charged so far. Equals `results.succeeded`. created_at: type: string format: date-time started_at: type: - string - 'null' format: date-time finished_at: type: - string - 'null' format: date-time BulkJobRequest: type: object description: Supply exactly one of `gstins` or `text`. properties: gstins: type: array items: type: string maxLength: 64 minItems: 1 maxItems: 10000 description: The GSTINs to verify, in the order you want them back. text: type: string description: A blob to extract GSTINs from — one per line, comma separated, or pasted from a spreadsheet. name: type: string maxLength: 255 description: A label for the job, to recognise it in the list later. BulkJobEnvelope: type: object properties: success: type: boolean job: $ref: '#/components/schemas/BulkJob' links: type: object description: Where to poll this job and collect its results. properties: self: type: string format: uri results: type: string format: uri csv: type: string format: uri TaxpayerProfile: type: object description: A verified registration, normalised. This is the shape bulk results carry; the single endpoint returns the raw envelope instead. properties: success: type: boolean gstin: type: string legal_name: type: - string - 'null' trade_name: type: - string - 'null' description: Falls back to the legal name when the business reports no separate trade name. status: type: string description: Active, Cancelled, Suspended, Provisional, … taxpayer_type: type: - string - 'null' constitution: type: - string - 'null' registration_date: type: - string - 'null' format: date cancellation_date: type: - string - 'null' format: date last_updated_at: type: - string - 'null' format: date active_years: type: - integer - 'null' nature_of_business: type: array items: type: string jurisdiction: type: object properties: centre: type: - string - 'null' centre_code: type: - string - 'null' state: type: - string - 'null' state_code: type: - string - 'null' principal_address: type: - object - 'null' properties: formatted: type: string building: type: - string - 'null' street: type: - string - 'null' locality: type: - string - 'null' district: type: - string - 'null' state: type: - string - 'null' pincode: type: - string - 'null' nature: type: array items: type: string additional_address_count: type: integer e_invoice: type: object properties: enabled: type: boolean generating: type: boolean turnover_slab: type: - string - 'null' eligibility: type: - string - 'null' filing_counts: type: object description: Count of filings held per return type. additionalProperties: type: integer derived: type: object description: Read from the GSTIN itself, not reported by the register. properties: state_code: type: - string - 'null' state_name: type: - string - 'null' pan: type: - string - 'null' pan_holder_type: type: - string - 'null' entity_code: type: - string - 'null' registration_number_in_state: type: - integer - 'null' source: type: object properties: fetched_at: type: - string - 'null' format: date-time served_from_cache: type: boolean description: The register itself refreshes a few times a day, so recent lookups are served from cache. Cached rows are charged the same as fresh ones. BulkResultRow: type: object description: One submitted row and what became of it. properties: row: type: integer description: Position in the submitted list, 1-based. input: type: string description: What was submitted for this row, verbatim. gstin: type: - string - 'null' description: The normalised GSTIN, or null if the entry was not a usable one. outcome: $ref: '#/components/schemas/Outcome' message: type: - string - 'null' description: Why this row is not `ok`. Null when it is. data: oneOf: - $ref: '#/components/schemas/TaxpayerProfile' - type: 'null' description: The verified profile. Present only when `outcome` is `ok`. Outcome: type: string enum: - ok - not_found - invalid_format - upstream_error - skipped - pending description: 'What happened to a row, and whether it cost anything: - `ok` — a live registration was found. **One credit.** - `not_found` — well-formed, but no registration exists. Free. - `invalid_format` — not a valid GSTIN; rejected on its checksum without contacting the government source. Free. - `upstream_error` — the government source could not answer. Free; resubmit the row. - `skipped` — the job was cancelled or ran out of credits before reaching this row. Free. - `pending` — not verified yet. Only seen while a job is still running.' Error: type: object properties: success: type: boolean const: false reason: type: string description: A stable code to branch on. The message is for people and may be reworded. enum: - no_gstins - too_many_gstins - too_many_active_jobs - insufficient_credits - already_finished message: type: string required: - success - message responses: PaymentRequired: description: The account has no credits left. Nothing was verified and nothing was charged. content: application/json: schema: $ref: '#/components/schemas/Error' example: success: false message: Insufficient credits Unauthorized: description: The API key was missing, unknown, or not active. content: application/json: schema: $ref: '#/components/schemas/Error' example: success: false message: Invalid or missing API key JobNotFound: description: No job with that ID on this account. parameters: JobId: name: job_id in: path required: true description: The `job_id` returned when the job was submitted. schema: type: string format: uuid securitySchemes: ApiKeyAuth: type: apiKey in: header name: X-API-Key description: Per-account key, created and rotated from the dashboard at https://gstinapi.com/settings/api-keys. New accounts receive 20 free credits on email verification.