Skip to content

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):

FieldTypeRequiredDescription
qstringyesFree-text query in English, Russian or Armenian, e.g. "IT services, deadline > week"
countriesstring[]noBuyer countries, ISO 3166-1 alpha-2 (e.g. ["DE", "AM"])
cpvstring[]noCPV codes or prefixes (e.g. ["72", "48"])
minBudgetnumber [0..]noMinimum estimated budget, in the tender currency
maxBudgetnumber [0..]noMaximum estimated budget, in the tender currency
deadlineAfterstring (date-time) | string (date)noOnly 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"noCurrency 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.
limitinteger [1..20] default 10noNumber 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):

FieldTypeDescription
results[]array of objects
results[].idstring
results[].titlestring
results[].subjectstringWhat 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[].buyerstring | null
results[].countrystring | nullISO 3166-1 alpha-2
results[].budgetstring | nullEstimated budget as a decimal string, e.g. "125000.00"
results[].currencystring | nullISO 4217
results[].deadlinestring (date-time) | nullSubmission deadline, ISO 8601 UTC
results[].urlstring
results[].source"ted" | "gnumner" | "eaeu"
hintstringHow 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 parameterDescription
idTender id from search results

Example:

curl https://tenderscope.app/v1/tenders/1234 \
  -H "Authorization: Bearer sk_live_YOUR_KEY"

Response 200 (JSON):

FieldTypeDescription
idstring
titlestring
subjectstringWhat 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.
buyerstring | null
countrystring | nullISO 3166-1 alpha-2
budgetstring | nullEstimated budget as a decimal string, e.g. "125000.00"
currencystring | nullISO 4217
deadlinestring (date-time) | nullSubmission deadline, ISO 8601 UTC
urlstring
source"ted" | "gnumner" | "eaeu"
status"open" | "amended" | "cancelled" | "closed"
publishedAtstring (date-time) | null
regionstring | null
lotsCountinteger [-9007199254740991..9007199254740991] | null
updatedAtstring (date-time)
cpvCodesstring[]Omitted on the Free plan
okpd2Codesstring[]OKPD2 codes (EAEU sources); omitted on the Free plan
lots[]array of objectsOmitted on the Free plan
lots[].titlestring
lots[].budgetstring | null
lots[].currencystring | null
lots[].cpvCodesstring[]
docsUrlstring[]Links to tender documents; omitted on the Free plan
hintstring

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):

FieldTypeRequiredDescription
namestringyesWatchlist name
filters.qstringnoFree-text query (any language), e.g. "IT services, Armenia"
filters.countriesstring[]noBuyer countries, ISO 3166-1 alpha-2 (e.g. ["DE", "AM"])
filters.cpvstring[]noCPV codes or prefixes (e.g. ["72", "48"])
filters.minBudgetnumber [0..]noMinimum estimated budget, in the tender currency
filters.maxBudgetnumber [0..]noMaximum estimated budget, in the tender currency
filters.deadlineAfterstring (date-time) | string (date)noOnly 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"noCurrency 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.emailboolean default truenoSend alerts by email
channels.webhookstring (uri)noHTTPS 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):

FieldTypeDescription
watchlistIdstring (uuid)
estimatedMatchesinteger [0..9007199254740991]Open tenders matching the filters right now
hintstringNote 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):

FieldTypeDescription
alerts[]array of objects
alerts[].watchlistIdstring (uuid)
alerts[].watchlistNamestring
alerts[].reason"new" | "deadline-24h" | "amended"
alerts[].createdAtstring (date-time)
alerts[].tender.idstring
alerts[].tender.titlestring
alerts[].tender.subjectstringWhat 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.buyerstring | null
alerts[].tender.countrystring | nullISO 3166-1 alpha-2
alerts[].tender.budgetstring | nullEstimated budget as a decimal string, e.g. "125000.00"
alerts[].tender.currencystring | nullISO 4217
alerts[].tender.deadlinestring (date-time) | nullSubmission deadline, ISO 8601 UTC
alerts[].tender.urlstring
alerts[].tender.source"ted" | "gnumner" | "eaeu"
hintstring

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/health

Response 200 (JSON):

FieldTypeDescription
status"ok" | "degraded"
sources[]array of objects
sources[].code"ted" | "gnumner" | "eaeu"
sources[].status"ok" | "error" | "never" | "stalled"
sources[].lastSuccessAtstring (date-time) | null

Status codes: 200 ok or degraded

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