REST API
JSON over HTTPS. Base URL:
https://www.tender-lock.co/api/v1
Authentication
Send your organisation API key as a Bearer token on every request. Keys start with tl_live_ and are created in Settings → API & MCP.
Authorization: Bearer tl_live_YOUR_KEY
Errors
Errors return a non-2xx status and a JSON body:
{ "error": { "type": "not_found", "message": "Tender not found." } }
| Status | Meaning |
|---|---|
| 401 | Missing, invalid, expired or revoked key |
| 402 | Not included in your plan |
| 403 | Key lacks write access, or bids are still sealed |
| 404 | Not found in your organisation |
| 409 | Not allowed in the tender's current state |
| 422 | Invalid input |
| 429 | Rate limit: 120 requests per minute per key |
Endpoints
/meWho you are: organisation, key name, scopes and plan.
/tendersList your organisation's tenders, newest first. Optional status filter: draft, pending_approval, live, closed, awarded, no_award, cancelled.
Query: status limit
/tenders/{id}Full detail of one tender: scope, dates, requirements, evaluation criteria and invited suppliers.
/tenders needs write accessCreate a DRAFT tender (never published). A person must review, run checks and publish it in TenderLock. closes_at is ISO 8601 in your organisation's time zone, e.g. 2026-11-30T12:00.
| Field | Type | |
|---|---|---|
title | string | required |
description | string | required |
closes_at | string | required |
category | string | optional |
estimated_value | number | optional |
requirements | array | optional |
criteria | array | optional |
/tenders/{id}/suppliers needs write accessAdd a supplier to a DRAFT tender. Nothing is sent: invitations go out only when a person publishes the tender.
| Field | Type | |
|---|---|---|
supplier_name | string | required |
email | string | required |
/tenders/{id}/clarificationsClarification questions and answers for a tender.
/tenders/{id}/submissionsSubmitted bids for a tender, with prices and weighted scores. Only available after the closing date; bids stay sealed until then.
/tenders/{id}/auditThe append-only audit trail for a tender.
Query: limit
/suppliersSearch your supplier address book by company, email or category.
Query: q limit
/suppliers needs write accessAdd a supplier to your address book.
| Field | Type | |
|---|---|---|
company_name | string | required |
email | string | required |
contact_name | string | optional |
category | string | optional |
/suppliers/{id}One supplier from your address book with Companies House rating, latest Creditsafe credit check (score, band, limit, turnover, profit, CCJs) and the tenders they were invited to.
/suppliers/{id}/credit-check needs write accessRun a Creditsafe credit check on a supplier (Business plan and above). Uses credits; you are not charged if the check fails.
| Field | Type | |
|---|---|---|
supplier_id | string | required |
/tenders/{id}/blue-teamBlue Team items for a tender: gaps found, suggested fixes and whether each was applied or dismissed.
/tenders/{id}/red-teamThe latest Red Team stress test for a tender: readiness, summary and every finding with severity, explanation and status.
/tenders/{id}/reviewWho is on the Blue and Red review teams for a tender and the sign-offs recorded.
/entitiesThe companies (entities) in your group, with tender and people counts.
/teamYour team: roles, super admin and owner flags, entities, 2FA, invite status and last sign-in. Key must be created by an owner, super admin or team admin. Optional status: active, invited, disabled.
Query: status
/team/{id}Full audit of one team member: details, roles, entities, 2FA devices, signed-in devices, failed sign-ins, tender stats and recent activity.
Query: limit
/team needs write accessInvite someone to your team with one or more roles (role keys or names, e.g. "Evaluator"). Emails the invitation and returns the invite link. Key must be created by an owner, super admin or team admin. Super admin can only be given in TenderLock.
| Field | Type | |
|---|---|---|
email | string | required |
roles | array | required |
/templatesYour saved tender templates.
/reports/summaryHeadline reporting numbers: tenders by status, created, published, awarded value, submissions and credits used. Optional days (default 90).
Query: days
/creditsYour organisation's plan and current credit balance.
/billingYour plan, billing interval, status, current period, next bill date and amount, cancellation and account credit.
/contactsSearch your shared contact book (people at suppliers and buyers). Respects the key creator's role: without "see everyone's contacts" only their own contacts are returned. Optional q (name, email, job title), company_id, owner_id, limit.
Query: q company_id owner_id limit
/contacts/{id}One contact with phones, all email addresses, owner, creator, linked companies, recent emails and call/SMS/WhatsApp logs.
/contacts needs write accessAdd a person to your contact book, optionally linked to a company. The key creator becomes the owner.
| Field | Type | |
|---|---|---|
email | string | required |
name | string | optional |
job_title | string | optional |
mobile | string | optional |
work_phone | string | optional |
company_id | string | optional |
/companiesSearch your companies. Each company can be a Supplier, a Buyer or both. Optional type: supplier, buyer or both; q; limit.
Query: type q limit
/companies/{id}/type needs write accessMark a company as a Supplier, a Buyer or both.
| Field | Type | |
|---|---|---|
company_id | string | required |
is_supplier | boolean | optional |
is_buyer | boolean | optional |
/emailsEmail conversations sent or received through TenderLock, newest first, with unread counts and tracking. Optional contact_id, company_id, tender_id, unread_only.
Query: contact_id company_id tender_id unread_only limit
/emails/{thread_id}Every message in one email conversation, with delivery, open and click events.
/emails needs write accessSend an email to a contact from TenderLock, as the key creator. Goes from their connected mailbox if they have one, otherwise from TenderLock. body is plain text or simple HTML. Pass thread_id to reply in an existing conversation. Optional company_id and tender_id to link it.
| Field | Type | |
|---|---|---|
contact_id | string | required |
subject | string | required |
body | string | required |
thread_id | string | optional |
company_id | string | optional |
/comm-logsLogged calls, SMS and WhatsApp messages for a contact or company: date, time, who, direction, outcome and note.
Query: contact_id company_id channel limit
/comm-logs needs write accessLog a call, SMS or WhatsApp against a contact or company. Date, time and author are recorded automatically. outcome is for calls: connected, no_answer, voicemail, busy, wrong_number.
| Field | Type | |
|---|---|---|
contact_id | string | optional |
company_id | string | optional |
channel | string | required |
direction | string | optional |
outcome | string | optional |
note | string | required |
/audit-logOrganisation-wide audit log: who did what, when, from which IP and location. Key must be created by an owner or team admin. Optional type (login or action), user_id, since (ISO date), limit.
Query: type user_id since limit
Examples
Create a draft tender (curl)
curl -X POST https://www.tender-lock.co/api/v1/tenders \
-H "Authorization: Bearer tl_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"title": "Office cleaning, Manchester HQ",
"description": "Daily office cleaning for a 4,000 sq ft office...",
"closes_at": "2026-11-30T12:00",
"estimated_value": 24000,
"requirements": [
{"text": "Confirm you hold public liability insurance of at least £5m", "kind": "mandatory"}
],
"criteria": [
{"name": "Quality", "weight": 60},
{"name": "Price", "weight": 40, "is_price": true}
]
}'
List live tenders (Python)
import requests
r = requests.get("https://www.tender-lock.co/api/v1/tenders", params={"status": "live"},
headers={"Authorization": "Bearer tl_live_YOUR_KEY"})
for t in r.json()["data"]:
print(t["title"], t["closes_at"])
Results after close (JavaScript)
const res = await fetch("https://www.tender-lock.co/api/v1/tenders/TENDER_ID/submissions", {
headers: { Authorization: "Bearer tl_live_YOUR_KEY" }
});
const { data } = await res.json(); // ranked bids with weighted scores
