Developer docs

Tenderen API (v1)

The Tenderen API is a JSON REST API for searching tenders, managing saved tenders, bids, alerts, teams and documents, and running AI analysis. It is available on the Enterprise plan and authenticated with API keys.

Base URL

All endpoints are versioned under /api/v1. Requests and responses are JSON over HTTPS.

https://api.tenderen.com/api/v1

Authentication

Send your API key as a Bearer token in the Authorization header. Keys are created in the app under Developers → API keys by a company Owner or Admin on the Enterprise plan. The full key is shown once; Tenderen stores only a SHA-256 hash of it. Keys can have an expiry date and can be revoked at any time.

curl "https://api.tenderen.com/api/v1/tenders?q=software&cpvCodes=72000000&limit=20" \
  -H "Authorization: Bearer $TENDEREN_API_KEY"

Scopes

Each key carries a list of scopes. New keys default to read-only (tenders:read). Calling an endpoint whose scope the key lacks returns 403 INSUFFICIENT_SCOPE.

Endpoints

Paths are relative to the base URL. The scope column shows the permission the key needs.

Tenders

MethodPathScopeWhat it does
GET/tenderstenders:readSearch tenders with filters and pagination.
GET/tenders/:idtenders:readGet one tender.
GET/tenders/:id/documentstenders:readList the documents attached to a tender.
GET/tenders/:id/requirementstenders:readList the requirements extracted from a tender.
GET/tenders/savedtenders:readList your company's saved tenders.
POST/tenders/savedtenders:writeSave a tender.
PATCH/tenders/saved/:idtenders:writeUpdate a saved tender.

Bids

MethodPathScopeWhat it does
POST/bidsbids:writeCreate a bid for a tender.
GET/bidsbids:readList bids.
GET/bids/:idbids:readGet one bid.
PATCH/bids/:idbids:writeUpdate a bid.
POST/bids/:id/submitbids:writeMark a bid as submitted.

AI analysis

MethodPathScopeWhat it does
POST/analysis/tender/:idanalysis:writeRun an AI analysis of a tender.
POST/analysis/risk/:idanalysis:writeRun an AI risk analysis of a tender.
POST/analysis/match/:idanalysis:writeScore how well a tender matches your company.
POST/analysis/gaps/:idanalysis:writeFind gaps between the requirements and your company profile.

Market data

MethodPathScopeWhat it does
GET/market/insightsmarket:readMarket statistics.
GET/market/awardsmarket:readSearch contract awards.
GET/market/competitorsmarket:readCompetitors seen in contract awards.

Alerts and notifications

MethodPathScopeWhat it does
GET/alertsalerts:readList alerts.
POST/alertsalerts:writeCreate an alert.
GET/alerts/:idalerts:readGet one alert.
PATCH/alerts/:idalerts:writeUpdate an alert.
DELETE/alerts/:idalerts:writeDelete an alert.
GET/alerts/notifications/listalerts:readList notifications.
PATCH/alerts/notifications/:id/readalerts:writeMark a notification as read.

Teams

MethodPathScopeWhat it does
GET/teamsteams:readList teams.
POST/teamsteams:writeCreate a team.
GET/teams/:idteams:readGet one team.
PATCH/teams/:idteams:writeUpdate a team.
GET/teams/:id/membersteams:readList team members.
POST/teams/:id/membersteams:writeAdd a team member.
DELETE/teams/:id/members/:memberIdteams:writeRemove a team member.

Consortiums

MethodPathScopeWhat it does
GET/consortiumstenders:readList consortiums.
POST/consortiumstenders:writeCreate a consortium for a tender.
GET/consortiums/:idtenders:readGet one consortium.
GET/consortiums/:id/memberstenders:readList consortium members.
POST/consortiums/:id/memberstenders:writeInvite a partner company.
GET/consortiums/:id/responsibilitiestenders:readList who is responsible for what.

Documents (assets)

MethodPathScopeWhat it does
GET/assetsassets:readList documents in your library.
POST/assetsassets:writeCreate a document record.
GET/assets/:idassets:readGet one document.
DELETE/assets/:idassets:writeDelete a document.
POST/assets/:id/uploadassets:writeUpload a file to a document record.
GET/assets/:id/downloadassets:readDownload a document.

Compliance

MethodPathScopeWhat it does
GET/compliance/:tenderIdcompliance:readCompliance matrix for a tender.
POST/compliance/espdcompliance:readGenerate an ESPD response.
GET/compliance/certifications/listcompliance:readList your company's certifications.

Chat with a tender

MethodPathScopeWhat it does
POST/chatchat:writeAsk a question about a tender.
GET/chat/:sessionId/messageschat:readGet the messages in a chat session.

Webhooks

MethodPathScopeWhat it does
POST/webhookswebhooks:manageRegister a webhook endpoint.
GET/webhookswebhooks:manageList webhook endpoints.
PATCH/webhooks/:idwebhooks:manageChange a webhook's URL, events or active flag.
DELETE/webhooks/:idwebhooks:manageDelete a webhook endpoint.
POST/webhooks/:id/testwebhooks:manageSend a test event to a webhook now.
POST/webhooks/:id/rotate-secretwebhooks:manageIssue a new signing secret.
GET/webhooks/:id/deliverieswebhooks:manageDelivery history for a webhook.

Account

MethodPathScopeWhat it does
GET/meaccount:readThe user and company the key belongs to.
GET/me/usageaccount:readAPI usage for the key.
GET/healthnoneHealth check (no authentication).

Pagination and filtering

List endpoints accept page (starting at 1) and limit (1–100, default 20) and return a pagination object with page, limit, total and pages. GET /tenders also accepts q, cpvCodes and regions (comma-separated), status, source, minValue, maxValue, deadlineBefore, deadlineAfter, sort (publishedAt, deadline or estimatedValue) and order (asc or desc).

{
  "success": true,
  "data": [ { "id": "…", "title": "…", "deadline": "…" } ],
  "pagination": { "page": 1, "limit": 20, "total": 412, "pages": 21 }
}

Errors

Errors return success: false with a human-readable error and a machine-readable code.

{
  "success": false,
  "error": "Insufficient permissions. Required scope: bids:write",
  "code": "INSUFFICIENT_SCOPE"
}
CodeHTTP statusMeaning
INVALID_AUTH401Missing, malformed or unknown API key.
KEY_INACTIVE401The key has been revoked.
KEY_EXPIRED401The key has passed its expiry date.
INSUFFICIENT_SCOPE403The key lacks the scope this endpoint needs.
INVALID_DATA400The request body failed validation; see details.
INVALID_PARAMS400A query or path parameter is invalid.
FORBIDDEN403You are not allowed to change this resource.
NOT_FOUND404The resource does not exist or belongs to another company.
RATE_LIMIT_EXCEEDED429Too many requests in the current hour.
INTERNAL_ERROR500Something failed on our side; retry later.

Rate limits

Each API key may make 1,000 requests per hour by default (the limit is stored per key). Going over returns 429 RATE_LIMIT_EXCEEDED; wait for the hourly window to reset. The MCP endpoint has its own limit of 1,000 requests per hour per token.

Webhooks

Webhooks are part of the Enterprise plan. Events only go to endpoints that belong to the company the event concerns, and endpoints must be public https addresses.

Register an HTTPS endpoint with POST /webhooks (or under Developers → Webhooks in the app) and choose the events you want. The response contains a signing secret, shown only once; POST /webhooks/:id/rotate-secret issues a new one and POST /webhooks/:id/test sends a test event right away.

Events

  • tender.new — A newly published tender matches one of your company's alerts (once per tender).
  • tender.deadline — A tender in your pipeline has its submission deadline in 7, 3 or 1 days.
  • tender.awarded — A contract award was published for a tender in your pipeline.
  • bid.submitted — A pipeline entry moved to Submitted.
  • bid.status_changed — A pipeline entry changed stage.
  • alert.triggered — An alert found new matching tenders.

Verifying signatures

Every delivery is a POST with a JSON body holding id, event, createdAt and data, and the headers X-Tenderen-Event, X-Tenderen-Event-Id, X-Tenderen-Delivery and X-Tenderen-Signature. The signature header has the form t=TIMESTAMP,v1=SIGNATURE, where TIMESTAMP is in Unix seconds and SIGNATURE is the hex HMAC-SHA256 of TIMESTAMP, a dot and the raw body, keyed with your signing secret. Compare it in constant time, reject timestamps more than five minutes old, and use the event id to ignore duplicates.

import crypto from 'node:crypto';

function isValidTenderenSignature(rawBody, signatureHeader, secret) {
  const parts = Object.fromEntries((signatureHeader ?? '').split(',').map((p) => p.split('=')));
  const t = Number(parts.t);
  if (!t || Math.abs(Date.now() / 1000 - t) > 300) return false;
  const expected = crypto.createHmac('sha256', secret).update(`${t}.${rawBody}`).digest('hex');
  const a = Buffer.from(parts.v1 ?? '');
  const b = Buffer.from(expected);
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

A delivery that does not get a 2xx answer within 10 seconds is retried after 1 minute, 5 minutes, 30 minutes and 2 hours; redirects are not followed. After 50 failed deliveries in a row the endpoint is turned off and its owner is notified; PATCH /webhooks/:id with active: true turns it back on. The delivery history (30 days) is available from GET /webhooks/:id/deliveries.

MCP connector for AI assistants

Tenderen runs a remote Model Context Protocol server, so AI assistants such as Claude, ChatGPT and Cursor can search tenders, assess fit, track a pipeline, draft bids and ask the tender documents on your behalf — 18 capability tools, with read access granted by default and writes, submissions and deletions as separate grants on the consent screen. Connect with OAuth (authorization code with PKCE; dynamic client registration is supported) or with an API key as a Bearer token. Tools that cannot be undone preview first and need confirmation. Every call is listed under Developers → AI connections, where connections can be tested and revoked.

MCP endpoint (Streamable HTTP)

https://tenderen.com/api/mcp

Legacy endpoint (HTTP+SSE)

https://tenderen.com/api/mcp/sse

OAuth discovery

https://tenderen.com/.well-known/oauth-authorization-server
https://tenderen.com/.well-known/oauth-protected-resource

Support

Questions about the API or a specific integration: [email protected].

Last reviewed 4 October 2026