The question usually arrives phrased as a competition. Should we build on the REST API or use the MCP server?
It's the wrong frame. MCP and REST are two doors into the same room. Same account, same credential, same tender corpus, same rate limit. Nothing is available through one that is fundamentally withheld by the other — with two specific exceptions we'll come to.
What actually differs is who is on the other side. REST is consumed by software that must behave identically every time it runs. MCP is consumed by a language model reasoning on behalf of a human who asked a question in English. Almost every real decision follows from that.
This guide uses the Tenderkart Client API and MCP server as the worked example, but the reasoning transfers to any service that offers both.
The one-question test
Before comparing feature tables, ask this:
Does the output need to be byte-identical every time it runs?
If yes — a nightly CRM sync, a compliance record, a dashboard refresh, an alert pipeline — use REST. Determinism is the whole requirement, and an LLM in the loop is a liability, not a feature.
If no — a BD manager asking which tenders are worth looking at, an analyst reading a BOQ, someone chasing a hunch about a buyer's procurement pattern — use MCP. The value is in the reasoning, and specifying that reasoning as code would take longer than the answer is worth.
That test resolves most cases correctly. The rest of this post is for the ones it doesn't.
What's genuinely identical
Worth stating plainly, because a lot of MCP marketing implies otherwise:
- Same data. Both reach the same normalised corpus across 80+ Indian tender sources.
- Same account scope. Your saved filters, your enabled capabilities, your access profile.
- Same rate limit. 30 requests per minute per API key by default, shared across both surfaces. This one surprises people — see the budgeting section below.
- Same optional capabilities. Full-corpus search is enabled per API key. If it's off,
GET /tendersreturns403and thesearch_tendersMCP tool is hidden. Connecting through Claude does not unlock anything.
MCP is not a lighter-weight tier or a demo mode. It's the same access, differently shaped.
Side by side
| REST API | MCP Server | |
|---|---|---|
| Consumer | Your code | An AI assistant |
| Interface | 8 HTTP endpoints | 7 typed tools |
| Invocation | Explicit calls you write | Model decides from natural language |
| Output | Deterministic JSON | Reasoned prose, varies between runs |
| Auth | API key in X-API-Key or Bearer | OAuth token (recommended) or API key |
| Incremental sync | Yes — updated_at cursor, resumable | Available, but not the right tool for it |
| Market stats | POST /stats | Not exposed |
| Document download | GET /documents/{id} streams the file | Signed URL, valid 30 minutes |
| Machine-readable spec | GET /openapi.yaml, no key needed | Tool schemas advertised on connect |
| Setup effort | Engineering work | About four minutes, no code |
| Best for | Pipelines, CRM, dashboards, alerts | Research, screening, document analysis |
The two things MCP genuinely cannot do
Market statistics. POST /stats returns aggregate tender counts and value totals in INR crores for a date range, filtered by keyword, state, and portal. There is no MCP equivalent. If you want "how many crores of solar tenders were published in Maharashtra last month," that is a REST call.
This trips people up because an AI assistant will happily count what it retrieved and present the total. That number is not a market total — it's a count of a capped, ranked result set. The search window tops out at 10,000 records, and pagination.total reports the true match count even when it exceeds what you can page through. Treat conversational aggregates as indicative and use /stats when the number matters.
Guaranteed-complete change capture. Both surfaces expose filter sync, but MCP wraps it in a model's judgement about when to stop paging. For a pipeline that must not drop records, you want the loop written explicitly.
Where REST wins
Incremental sync is a solved problem in REST and a fragile one in conversation
The sync contract is precise, and getting it right matters more than it looks:
import time, requests
BASE = "/api/v1/client"
HEADERS = {"X-API-Key": API_KEY}
def sync_filter(filter_id, last_updated_after=None):
"""Returns the new cursor to store, or None if the run should be retried."""
params = {"limit": 100}
if last_updated_after:
params["updated_after"] = last_updated_after # omit on first backfill
cursor = None
while True:
req = {"cursor": cursor, "limit": 100} if cursor else params
r = requests.get(f"{BASE}/filters/{filter_id}/tenders",
headers=HEADERS, params=req)
if r.status_code == 429:
time.sleep(int(r.headers.get("Retry-After", 60)))
continue
if r.status_code == 400 and cursor:
# Filter was edited mid-page. Restart from last committed watermark.
return None
r.raise_for_status()
body = r.json()
for tender in body["tenders"]:
upsert(tender) # idempotent, keyed on tender["id"]
page = body["pagination"]
if not page.get("has_more"):
# next_updated_after appears only on the final page
return body["sync"]["next_updated_after"]
cursor = page["next_cursor"]
Four details in that loop are the difference between a reliable pipeline and one that silently loses tenders:
Sync on updated_at, never published_at. Some source pipelines lag by up to 24 hours. A tender published Monday can land in Tenderkart on Wednesday. Anyone cursoring on publication date drops those records permanently and never sees an error. Keep published_at for tender-age reporting and closing_at for deadline logic — but the cursor is updated_at.
Only commit next_updated_after after the final page. It appears exclusively on the last page of a run. Commit it mid-run and a crash leaves you with a watermark ahead of the data you actually stored.
Upsert idempotently on id. A tender that changes while you're paging can appear twice. That's expected behaviour, not a bug to work around.
Let the server pick updated_before. Omitted, Tenderkart freezes a safe upper bound slightly behind current server time, so very recent writes aren't skipped while they're still becoming searchable. Setting it yourself to now() reintroduces exactly the race the default prevents.
None of this is complicated. All of it is impossible to guarantee when a language model is deciding when to stop paging.
Other REST-shaped work
Backfill. Omit updated_after on the first run and page through the history.
CRM and ERP integration. Tenders becoming leads in Salesforce, HubSpot, or Zoho needs stable field mapping and deduplication.
Alerting. Deadline reminders, corrigendum notifications, status changes into Slack or WhatsApp.
Dashboards. /stats on a schedule, written to your warehouse.
Audit trails. When someone asks in six months why a tender wasn't screened, you want a log, not a chat transcript.
Code generation. GET /openapi.yaml requires no API key — point your generator or coding agent at it and get a typed client for free.
Where MCP wins
Questions you'd never write code for
Nobody is going to build an endpoint for "which tenders in my filters this month have an EMD under ₹2 lakh but a value over ₹5 crore, and which of those have restrictive PQ criteria?" You'd spend an afternoon on it and ask it twice.
In conversation it takes fifteen seconds and the follow-up question costs nothing. That asymmetry — cheap ad-hoc analysis — is the actual argument for MCP.
Documents
An assistant can chain get_tender → list_tender_documents → prepare_tender_document_download, then read the BOQ or NIT and answer questions about it. Building that as a pipeline means writing a document parser, handling Excel and PDF variants, and maintaining it as buyers change formats.
The signed URL returned by prepare_tender_document_download is valid for 30 minutes, needs no authentication header, and does not expose your credential — so it can be handed to a downstream process or shared with a colleague safely.
Non-technical users
A bid manager with no engineering support can connect the server in four minutes and start asking questions. There is no ticket, no sprint, no integration project. For most bid teams this is the single biggest unlock, and it's easy to undervalue if you're evaluating from an engineering seat.
Composition with your other tools
Once tender data is in the assistant alongside your CRM, drive, and calendar connectors, cross-system questions become possible: "which of these closing tenders do we already have a cost estimate for in Drive?" No integration work — the assistant composes across connectors.
Parameter differences that will trip you
If you use both surfaces, note that the shapes are deliberately idiomatic to each and not identical. This catches people porting logic across.
| Concept | REST GET /tenders | MCP search_tenders |
|---|---|---|
| Geography | state, city, authority (repeatable, singular) | states, cities, authorities (arrays) |
| Classification | product_category, tender_category, portal | product_categories, procurement_types, portals |
| Status | status (repeatable or comma-separated) | statuses (array) |
| Format | URL query parameters only | Typed JSON arguments |
Two REST-specific behaviours worth internalising:
GET /tenders rejects a JSON body with 400. Filters go in the query string. This is a common first mistake, usually made by copying the /filters response shape into a request body — that JSON is a response, describing filters already saved on your account.
Unknown query parameters are rejected, not ignored. A typo returns 400 rather than silently widening your result set. Loud failure is the right default here; it just surprises people used to permissive APIs.
Status buckets are shared: active, under_evaluation, awarded, cancelled. unknown appears in responses for unmapped source statuses but is not accepted as a query filter.
The architecture most teams land on
Not either/or. Both, with a clean division:
┌──────────────────────┐
REST ────► │ Your system of │ ────► CRM · dashboards · alerts
(cron) │ record │
└──────────────────────┘
deterministic
auditable
complete
┌──────────────────────┐
MCP ────► │ Your AI assistant │ ────► BD, bid, and analyst teams
(humans) └──────────────────────┘
exploratory
conversational
fast to change
REST feeds the system of record: complete, deduplicated, auditable, resumable. MCP sits in front of the people, answering the questions that come up between syncs.
The two don't conflict, and running both from separate API keys keeps their rate-limit budgets independent.
Budgeting the shared rate limit
This is the operational gotcha. 30 requests per minute per key, shared across REST and MCP.
A single research conversation can chain a dozen tool calls — list filters, sync, open three tenders, list documents, prepare two downloads. Run that while a nightly sync is paging through a backfill on the same key and you'll see 429s on both sides.
Three practical measures:
- Separate keys for pipeline and humans. Cleanest fix, and it makes usage attributable.
- Respect
Retry-After. Authenticated responses carryX-RateLimit-Limit,X-RateLimit-Remaining, andX-RateLimit-Reset. Use them rather than fixed backoff. - Schedule syncs off-hours. Backfills especially — they're the heaviest thing you'll run.
Security: two different models
REST puts a long-lived API key in your server environment. Standard practice applies: environment variables or a secret manager, never in frontend code, repositories, logs, or screenshots. It's a bearer credential with your full account scope, so treat it accordingly.
MCP over OAuth issues the AI client a short-lived token. The grant points at the access profile you selected, so account scope, key revocation, and the shared request limit remain authoritative at Tenderkart. The raw API key is never sent to the AI client.
For most organisations, OAuth is the more defensible posture — particularly where the alternative is asking a non-technical bid manager to paste a permanent credential into a desktop app. All MCP scopes are read-only (filters:read, tenders:read, documents:read), so a connected assistant can look but cannot modify anything.
Decision checklist
Choose REST if you're: syncing to a CRM or warehouse · running scheduled jobs · building alerts · needing audit trails · computing market statistics · generating a typed client · requiring identical output every run.
Choose MCP if you're: answering ad-hoc questions · screening tenders against a capability profile · reading BOQs and NITs · enabling non-technical users · prototyping before committing to an integration · composing tender data with other connected tools.
Choose both if you have a system of record and humans who ask questions. Which, in practice, is most bid teams past their first few people.
If you've landed on MCP, the setup walkthrough for Claude and ChatGPT takes about four minutes end to end, and 20 prompts for tender research is the fastest way to find out what it's good at. If you've landed on REST, the Client API reference has every endpoint.
FAQ
Is MCP replacing REST APIs?
No. MCP is a layer for AI assistants to consume tools; underneath, most MCP servers call the same REST infrastructure. Deterministic software integrations remain REST's job, and there's no sign of that changing.
Can I use the same API key for both?
Yes, but the 30 requests per minute limit is per key and shared across both surfaces. Separate keys for your pipeline and your team are cleaner and make usage attributable.
Does MCP give an AI assistant more access than the REST API?
No — less, in fact. MCP scopes are read-only, and optional capabilities like full-corpus search stay governed by your API key. If search isn't enabled, the MCP tool is hidden entirely.
Which is faster to get running?
MCP, by a wide margin — roughly four minutes with no code. REST needs an engineer, though a public keyless OpenAPI spec at /openapi.yaml means a coding agent can generate a working client quickly.
Can I get market statistics through MCP?
No. POST /stats is REST-only. An assistant can count records it retrieved, but that is a capped result set rather than a market total.
What happens if a saved filter changes while a sync is running?
The cursor can return 400. Restart from your last stored sync.next_updated_after — which is why you commit that value only after the final page of a completed run.
Full endpoint reference at tenderkart.in/api-docs. MCP setup for Claude, ChatGPT, Perplexity, Cursor, and Codex at tenderkart.in/mcp-setup. Or book 30 minutes and we'll help you pick.