Developers
Bidkoo API
Read-only access to the tenders Bidkoo collects: search and filter them, fetch one in full, and get counts. Requests use an API key and return JSON.
Base URL
https://api.bidkoo.com/api/v1
Plan
API access is included in the Business plan.
Authentication
Send your API key as a bearer token in the Authorization header of every request:
Authorization: Bearer tb_live_YOUR_API_KEY- Create a key in Settings → API. Keys start with
tb_live_followed by 64 characters. - The full key is shown once, when you create it. We store only a hash of it, so it cannot be shown again: if you lose it, regenerate it.
- An account has one key. Regenerating replaces it, and revoking removes it; either way the old key stops working at once.
- Keep the key on your server. Do not put it in a web page or mobile app: anyone who has it can use your quota. Browser requests from other websites are refused by the API's CORS policy.
- While the key's account is not on a plan with API access (for example after the subscription ends or is downgraded), requests are refused with
403 PLAN_REQUIRED. The key works again once the account is back on such a plan.
List tenders
GET/ext/tenders
Search and filter tenders. All parameters are optional; an unknown parameter is an error rather than being ignored. Lists are comma-separated.
| Parameter | Type | Description |
|---|---|---|
| q | string, up to 200 characters | Text search over the title, description, organisation and other text fields. A value that is exactly a state name (for example Gujarat) filters by that state instead. |
| category | comma-separated list | One or more of WORKS, GOODS, SERVICES, CONSULTANCY. |
| state | comma-separated list, up to 40 | State names as they appear in the state field, for example Gujarat,Tamil Nadu. |
| status | comma-separated list | One or more of ACTIVE, CORRIGENDUM, CLOSED, AWARDED, CANCELLED. ACTIVE and CORRIGENDUM are open for bidding. |
| sourcePortal | comma-separated list, up to 20 | Portal names as they appear in the sourcePortal field, for example GeM. |
| minValue | number (rupees) | Estimated value at least this. Tenders with no stated value are left out when minValue or maxValue is set. |
| maxValue | number (rupees) | Estimated value at most this. |
| publishedFrom | ISO 8601 date or date-time | Published on or after. A bare date (2026-11-01) starts at 00:00 UTC. |
| publishedTo | ISO 8601 date or date-time | Published on or before. A bare date runs to the end of that day, 23:59:59.999 UTC. |
| deadlineFrom | ISO 8601 date or date-time | Bid submission deadline on or after. |
| deadlineTo | ISO 8601 date or date-time | Bid submission deadline on or before. |
| page | integer, from 1 | Page number. Default 1. page × limit may not exceed 10,000; narrow the filters to reach further. |
| limit | integer, 1 to 50 | Tenders per page. Default 20. |
| sort | newest | deadline | value_desc | value_asc | relevance | Order of results; see the table below. |
Sort orders
| newest | Most recently published first. The default. |
| deadline | Soonest bid submission deadline first. |
| value_desc | Highest estimated value first. |
| value_asc | Lowest estimated value first. |
| relevance | Best match for q first. Without q, the same as newest. |
curl -H "Authorization: Bearer tb_live_YOUR_API_KEY" \
"https://api.bidkoo.com/api/v1/ext/tenders?category=GOODS&state=Gujarat&status=ACTIVE%2CCORRIGENDUM&deadlineFrom=2026-11-01&sort=deadline&limit=50"{
"success": true,
"data": [
{
"id": "cmexample00000000000000001",
"title": "Example: Supply of laboratory equipment",
"description": "Example description, as published on the source portal.",
"referenceNumber": "EXAMPLE/2026/001",
"organization": "Example Organisation",
"department": null,
"tenderType": "OPEN",
"category": "GOODS",
"status": "ACTIVE",
"state": "Gujarat",
"district": null,
"city": null,
"estimatedValue": 1250000,
"emdAmount": 25000,
"publishedDate": "2026-10-28T05:30:00.000Z",
"bidSubmissionDeadline": "2026-11-12T09:30:00.000Z",
"bidOpeningDate": null,
"reservedForMSME": true,
"reservedForStartups": false,
"sourcePortal": "GeM",
"sourceUrl": "https://portal.example/tenders/EXAMPLE-2026-001"
}
],
"meta": {
"page": 1,
"limit": 20,
"total": 1
}
}Get a tender
GET/ext/tenders/{id}
One tender in full, by the id from the list. It adds eligibility criteria, documents and award results to the list fields. An unknown id is 404 TENDER_NOT_FOUND.
curl -H "Authorization: Bearer tb_live_YOUR_API_KEY" \
"https://api.bidkoo.com/api/v1/ext/tenders/cmexample00000000000000001"{
"success": true,
"data": {
"id": "cmexample00000000000000001",
"title": "Example: Supply of laboratory equipment",
"description": "Example description, as published on the source portal.",
"referenceNumber": "EXAMPLE/2026/001",
"organization": "Example Organisation",
"department": null,
"tenderType": "OPEN",
"category": "GOODS",
"status": "ACTIVE",
"state": "Gujarat",
"district": null,
"city": null,
"estimatedValue": 1250000,
"emdAmount": 25000,
"publishedDate": "2026-10-28T05:30:00.000Z",
"bidSubmissionDeadline": "2026-11-12T09:30:00.000Z",
"bidOpeningDate": "2026-11-13T05:30:00.000Z",
"reservedForMSME": true,
"reservedForStartups": false,
"sourcePortal": "GeM",
"sourceUrl": "https://portal.example/tenders/EXAMPLE-2026-001",
"preBidMeetingDate": null,
"emdExemptMSME": true,
"minTurnover": 5000000,
"minNetWorth": null,
"minExperience": 3,
"requiredCertifications": [
"Example certification"
],
"documents": [
{
"name": "Example bid document",
"fileUrl": "https://portal.example/tenders/EXAMPLE-2026-001/document.pdf",
"fileType": "pdf",
"fileSize": null
}
],
"results": []
}
}Statistics
GET/ext/stats
Tender counts. active counts tenders open for bidding (ACTIVE and CORRIGENDUM); closed counts CLOSED, AWARDED and CANCELLED; byCategory and byState count open tenders.
| Parameter | Type | Description |
|---|---|---|
| state | string | Limit total, active, closed and byCategory to one state. byState always covers every state. |
curl -H "Authorization: Bearer tb_live_YOUR_API_KEY" \
"https://api.bidkoo.com/api/v1/ext/stats?state=Gujarat"{
"success": true,
"data": {
"total": 1200,
"active": 800,
"closed": 400,
"byCategory": {
"GOODS": 450,
"WORKS": 200,
"SERVICES": 120,
"CONSULTANCY": 30
},
"byState": {
"Gujarat": 300,
"Maharashtra": 250
}
}
}Tender fields
Every tender has the fields below; those marked detail are only in /ext/tenders/{id}. A field the tender does not state is null.
| Field | Type | Description |
|---|---|---|
| id | string | Bidkoo’s id for the tender. Use it with GET /ext/tenders/{id}. |
| title | string | Title as published. |
| description | string | null | Description as published. |
| referenceNumber | string | The issuing organisation’s reference number. |
| organization | string | Issuing organisation. |
| department | string | null | Department, when stated. |
| tenderType | string | One of OPEN, LIMITED, SINGLE, TWO_PACKET, EOI, AUCTION, RFP, RFQ. |
| category | string | One of WORKS, GOODS, SERVICES, CONSULTANCY. |
| status | string | One of ACTIVE, CORRIGENDUM, CLOSED, AWARDED, CANCELLED. |
| state | string | State or union territory. |
| district | string | null | District, when the tender states it. |
| city | string | null | City, when the tender states it. |
| estimatedValue | number | null | Estimated value in rupees (not paise). null when not stated. |
| emdAmount | number | null | Earnest money deposit in rupees, when stated. |
| publishedDate | string (ISO 8601, UTC) | When the tender was published. |
| bidSubmissionDeadline | string (ISO 8601, UTC) | Last date and time to submit a bid. |
| bidOpeningDate | string | null | Bid opening date and time, when stated. |
| reservedForMSME | boolean | Reserved for micro, small and medium enterprises. |
| reservedForStartups | boolean | Reserved for startups. |
| sourcePortal | string | The portal the tender was collected from, for example GeM. |
| sourceUrl | string | Link to the tender on that portal. |
| preBidMeetingDatedetail | string | null | Pre-bid meeting date and time, when stated. |
| emdExemptMSMEdetail | boolean | MSMEs are exempt from the earnest money deposit. |
| minTurnoverdetail | number | null | Minimum turnover required, in rupees, when stated. |
| minNetWorthdetail | number | null | Minimum net worth required, in rupees, when stated. |
| minExperiencedetail | number | null | Minimum years of experience required, when stated. |
| requiredCertificationsdetail | string[] | Certifications the tender asks for, when stated. |
| documentsdetail | object[] | Tender documents: name, fileUrl (a link on the source portal), fileType and fileSize, each null when not known. |
| resultsdetail | object[] | Award results, when published: winnerName, winnerCompany, awardedValue (rupees) and awardedDate. |
Responses and errors
Every response is JSON with a success flag. Successful responses carry data (and meta for lists); errors carry error.code, a readable error.message and sometimes error.details. Match on the code, not the message.
{
"success": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "Invalid input data",
"details": [
{
"field": "limit",
"message": "Number must be less than or equal to 50"
}
]
}
}| Status | Code | Meaning |
|---|---|---|
| 400 | VALIDATION_ERROR | A parameter is invalid or unknown. error.details names each field and the problem. |
| 401 | NO_API_KEY | No Authorization: Bearer header. |
| 401 | INVALID_API_KEY_FORMAT | The bearer token is not an API key. Keys start with tb_live_. |
| 401 | INVALID_API_KEY | The key is not recognised. Check that it was copied in full. |
| 401 | API_KEY_REVOKED | The key was revoked, or replaced by a newer key. |
| 401 | API_KEY_REGENERATE_REQUIRED | The key was created before keys were stored securely. Generate a new one in Settings → API. |
| 403 | PLAN_REQUIRED | The key’s account is not on a plan with API access, for example after its subscription ended or was downgraded. |
| 403 | ACCOUNT_DEACTIVATED | The key’s account has been deactivated. |
| 404 | TENDER_NOT_FOUND | No tender has that id. |
| 404 | NOT_FOUND | No such endpoint. |
| 429 | RATE_LIMIT_EXCEEDED | A rate limit was reached. Wait for the seconds in the Retry-After header. |
| 500 | INTERNAL_ERROR | Something failed on our side. Retry later. |
Rate limits
- 60 requests per minute and 10,000 requests per day, per account. The day resets at 00:00 UTC (05:30 IST). Regenerating the key does not reset either count.
- Responses to requests with a valid key carry
RateLimit-Limit,RateLimit-RemainingandRateLimit-Reset(seconds) for whichever limit is closer to running out, andRateLimit-Policy: 60;w=60, 10000;w=86400. - Over a limit, the API answers
429 RATE_LIMIT_EXCEEDEDwith aRetry-Afterheader in seconds. Refused requests do not count. - After 50 requests that fail authentication (401 or 403) from one IP address within 15 minutes, further requests from that address are refused with 429 until the 15-minute window ends.
{
"success": false,
"error": {
"code": "RATE_LIMIT_EXCEEDED",
"message": "Rate limit of 60 requests per minute exceeded. Retry after 21 seconds.",
"details": {
"window": "minute",
"limit": 60,
"retryAfterSeconds": 21
}
}
}Notes on the data
- Amounts are in rupees (INR), not paise.
- Dates are ISO 8601 in UTC. India Standard Time is UTC+05:30, so a deadline of
2026-11-12T09:30:00.000Zis 15:00 IST. - Tenders are collected from public procurement portals. Confirm the details on the source portal (
sourceUrl) before you bid. meta.totalis exact up to 10,000; a larger result set may report 10,000. Paging reaches the first 10,000 results.- When the search index is unavailable, results come straight from the database:
qthen matches only title, description and organisation (a state name included, as text), andrelevancesorts asnewest.