REST API
The REST API offers the same four operations as the MCP tools. This page is generated at build time from the same OpenAPI document the API serves at /v1/openapi.json (built from the contracts shared with the MCP server), so inputs, outputs and status codes always match.
Base URL: https://tenderscope.app. Authenticate with Authorization: Bearer sk_live_… (REST keys only). Machine-readable schema: openapi.json (OpenAPI 3.1.0, version 1.2.0).
POST/v1/tenders/search
Search open tenders. MCP tool: search_tenders
Request body (JSON):
| Field | Type | Required | Description |
|---|---|---|---|
q | string | yes | Free-text query in English, Russian or Armenian, e.g. "IT services, deadline > week" |
countries | string[] | no | Buyer countries, ISO 3166-1 alpha-2 (e.g. ["DE", "AM"]) |
cpv | string[] | no | CPV codes or prefixes (e.g. ["72", "48"]) |
minBudget | number [0..] | no | Minimum estimated budget, in the tender currency |
maxBudget | number [0..] | no | Maximum estimated budget, in the tender currency |
deadlineAfter | string (date-time) | string (date) | no | Only tenders whose deadline is after this ISO 8601 date/datetime |
budgetCurrency | "EUR" | "USD" | "GBP" | "CHF" | "NOK" | "ISK" | "SEK" | "DKK" | "PLN" | "CZK" | "HUF" | "RON" | "BGN" | "AMD" | "RUB" | no | Currency of minBudget/maxBudget (ISO 4217). When set, each tender budget is converted at fixed reference rates (updated quarterly), so EU (EUR, PLN, SEK…), Armenian (AMD) and Russian (RUB) tenders compare fairly; tenders in other currencies are skipped. When omitted, budgets are compared in each tender's own currency. |
limit | integer [1..20] default 10 | no | Number of results, 1–20 (default 10) |
Example:
curl -X POST https://tenderscope.app/v1/tenders/search \
-H "Authorization: Bearer sk_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"q": "IT services, deadline > week", "limit": 5}'Response 200 (JSON):
| Field | Type | Description |
|---|---|---|
results[] | array of objects | |
results[].id | string | |
results[].title | string | |
results[].subject | string | What is procured: lot names from the tender (for Armenian tenders, from the invitation document), in the original language (e.g. Armenian), separated by "; ". Omitted when unknown or the same as the title. |
results[].buyer | string | null | |
results[].country | string | null | ISO 3166-1 alpha-2 |
results[].budget | string | null | Estimated budget as a decimal string, e.g. "125000.00" |
results[].currency | string | null | ISO 4217 |
results[].deadline | string (date-time) | null | Submission deadline, ISO 8601 UTC |
results[].url | string | |
results[].source | "ted" | "gnumner" | "eaeu" | |
hint | string | How to broaden the query when there are few or no results |
degradedSources | "ted" | "gnumner" | "eaeu"[] | Sources currently failing; data may be stale |
Status codes: 200 Up to 20 tender cards 400 invalid_input — the request does not match the schema 401 unauthorized — missing, invalid or revoked API key (sk_live_…) 403 plan_required or limit_reached — the feature or limit needs a higher plan 429 quota_exceeded — monthly call limit reached 500 internal
GET/v1/tenders/{id}
Get the full card of one tender. MCP tool: get_tender
| Path parameter | Description |
|---|---|
id | Tender id from search results |
Example:
curl https://tenderscope.app/v1/tenders/1234 \
-H "Authorization: Bearer sk_live_YOUR_KEY"Response 200 (JSON):
| Field | Type | Description |
|---|---|---|
id | string | |
title | string | |
subject | string | What is procured: lot names from the tender (for Armenian tenders, from the invitation document), in the original language (e.g. Armenian), separated by "; ". Omitted when unknown or the same as the title. |
buyer | string | null | |
country | string | null | ISO 3166-1 alpha-2 |
budget | string | null | Estimated budget as a decimal string, e.g. "125000.00" |
currency | string | null | ISO 4217 |
deadline | string (date-time) | null | Submission deadline, ISO 8601 UTC |
url | string | |
source | "ted" | "gnumner" | "eaeu" | |
status | "open" | "amended" | "cancelled" | "closed" | |
publishedAt | string (date-time) | null | |
region | string | null | |
lotsCount | integer [-9007199254740991..9007199254740991] | null | |
updatedAt | string (date-time) | |
cpvCodes | string[] | Omitted on the Free plan |
okpd2Codes | string[] | OKPD2 codes (EAEU sources); omitted on the Free plan |
lots[] | array of objects | Omitted on the Free plan |
lots[].title | string | |
lots[].budget | string | null | |
lots[].currency | string | null | |
lots[].cpvCodes | string[] | |
docsUrl | string[] | Links to tender documents; omitted on the Free plan |
hint | string |
Status codes: 200 Full tender card 400 invalid_input — the request does not match the schema 401 unauthorized — missing, invalid or revoked API key (sk_live_…) 403 plan_required or limit_reached — the feature or limit needs a higher plan 404 not_found 429 quota_exceeded — monthly call limit reached 500 internal
POST/v1/watchlists
Save filters as a watchlist. MCP tool: add_to_watchlist
Request body (JSON):
| Field | Type | Required | Description |
|---|---|---|---|
name | string | yes | Watchlist name |
filters.q | string | no | Free-text query (any language), e.g. "IT services, Armenia" |
filters.countries | string[] | no | Buyer countries, ISO 3166-1 alpha-2 (e.g. ["DE", "AM"]) |
filters.cpv | string[] | no | CPV codes or prefixes (e.g. ["72", "48"]) |
filters.minBudget | number [0..] | no | Minimum estimated budget, in the tender currency |
filters.maxBudget | number [0..] | no | Maximum estimated budget, in the tender currency |
filters.deadlineAfter | string (date-time) | string (date) | no | Only tenders whose deadline is after this ISO 8601 date/datetime |
filters.budgetCurrency | "EUR" | "USD" | "GBP" | "CHF" | "NOK" | "ISK" | "SEK" | "DKK" | "PLN" | "CZK" | "HUF" | "RON" | "BGN" | "AMD" | "RUB" | no | Currency of minBudget/maxBudget (ISO 4217). When set, each tender budget is converted at fixed reference rates (updated quarterly), so EU (EUR, PLN, SEK…), Armenian (AMD) and Russian (RUB) tenders compare fairly; tenders in other currencies are skipped. When omitted, budgets are compared in each tender's own currency. |
channels.email | boolean default true | no | Send alerts by email |
channels.webhook | string (uri) | no | HTTPS endpoint for signed JSON alerts |
Example:
curl -X POST https://tenderscope.app/v1/watchlists \
-H "Authorization: Bearer sk_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"name": "IT in Armenia", "filters": {"q": "IT services, Armenia"}, "channels": {"email": true}}'Response 201 (JSON):
| Field | Type | Description |
|---|---|---|
watchlistId | string (uuid) | |
estimatedMatches | integer [0..9007199254740991] | Open tenders matching the filters right now |
hint | string | Note about the filters, e.g. which currency the budget range uses |
Status codes: 201 Watchlist created 400 invalid_input — the request does not match the schema 401 unauthorized — missing, invalid or revoked API key (sk_live_…) 403 plan_required or limit_reached — the feature or limit needs a higher plan 429 quota_exceeded — monthly call limit reached 500 internal
GET/v1/alerts
Open alerts from your watchlists, nearest deadline first. MCP tool: get_deadline_alerts
Example:
curl https://tenderscope.app/v1/alerts \
-H "Authorization: Bearer sk_live_YOUR_KEY"Response 200 (JSON):
| Field | Type | Description |
|---|---|---|
alerts[] | array of objects | |
alerts[].watchlistId | string (uuid) | |
alerts[].watchlistName | string | |
alerts[].reason | "new" | "deadline-24h" | "amended" | |
alerts[].createdAt | string (date-time) | |
alerts[].tender.id | string | |
alerts[].tender.title | string | |
alerts[].tender.subject | string | What is procured: lot names from the tender (for Armenian tenders, from the invitation document), in the original language (e.g. Armenian), separated by "; ". Omitted when unknown or the same as the title. |
alerts[].tender.buyer | string | null | |
alerts[].tender.country | string | null | ISO 3166-1 alpha-2 |
alerts[].tender.budget | string | null | Estimated budget as a decimal string, e.g. "125000.00" |
alerts[].tender.currency | string | null | ISO 4217 |
alerts[].tender.deadline | string (date-time) | null | Submission deadline, ISO 8601 UTC |
alerts[].tender.url | string | |
alerts[].tender.source | "ted" | "gnumner" | "eaeu" | |
hint | string |
Status codes: 200 Up to 50 alerts 400 invalid_input — the request does not match the schema 401 unauthorized — missing, invalid or revoked API key (sk_live_…) 403 plan_required or limit_reached — the feature or limit needs a higher plan 429 quota_exceeded — monthly call limit reached 500 internal
GET/health
Service and data sources status (for uptime monitors).
Example:
curl https://tenderscope.app/healthResponse 200 (JSON):
| Field | Type | Description |
|---|---|---|
status | "ok" | "degraded" | |
sources[] | array of objects | |
sources[].code | "ted" | "gnumner" | "eaeu" | |
sources[].status | "ok" | "error" | "never" | "stalled" | |
sources[].lastSuccessAt | string (date-time) | null |
Status codes: 200 ok or degraded
Errors
Section titled “Errors”Every error has the same shape:
{ "error": { "code": "quota_exceeded", "message": "Monthly call limit reached. …" } }| HTTP | code |
Meaning |
|---|---|---|
| 400 | invalid_input |
Bad input: limit out of 1 to 20, minBudget above maxBudget, unknown budgetCurrency, malformed date, a watchlist filter that matches everything |
| 401 | unauthorized |
No key, wrong key format (an MCP token on REST, or the other way round), or revoked key |
| 403 | plan_required |
The feature needs a higher plan (structured filters or webhooks on Free) |
| 403 | limit_reached |
Watchlist limit of your plan reached |
| 404 | not_found |
No tender with this id |
| 429 | quota_exceeded |
Monthly call limit used up; buy a $5 pack (paid plans) or upgrade |
| 500 | internal |
Our fault; try again later |