The Bid Developer API (1.0.0)

Download OpenAPI specification:

REST API for estate agent partners to submit referrals, track property progress, access documents, and receive real-time webhook notifications.

Getting Started

Your Bid account manager will provide a client_id and client_secret. See the Quickstart guide to make your first API call in 5 minutes.

Quickstart

1. Get Your Credentials

Your Bid account manager will provide:

  • client_id — UUID identifying your integration
  • client_secret — starts with bid_cs_ (store securely, never expose in client-side code)

2. Exchange for an Access Token

curl -X POST https://api.thebid.uk/v1/oauth/token \
  -H "Content-Type: application/json" \
  -d '{
    "client_id": "YOUR_CLIENT_ID",
    "client_secret": "YOUR_CLIENT_SECRET",
    "grant_type": "client_credentials"
  }'

Response (RFC 6749 §5.1 — flat JSON, no data wrapper):

{
  "access_token": "bid_at_...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "permissions": ["referrals:read", "referrals:write", "properties:read", "buyers:read"]
}

3. Check Your Account

curl https://api.thebid.uk/v1/me \
  -H "Authorization: Bearer bid_at_..."

Response:

{
  "data": {
    "agent_id": "uuid",
    "agent_name": "Acme Estate Agents",
    "branches": [
      { "branch_id": "uuid", "branch_name": "London Office", "phone_number": "020 7123 4567" }
    ],
    "api_key_name": "Production Key",
    "permissions": ["referrals:read", "referrals:write", "properties:read", "buyers:read"]
  },
  "request_id": "uuid"
}

4. Cache Reference Data

Fetch enum values so your UI stays in sync with accepted values:

curl https://api.thebid.uk/v1/enums

Response includes referral_types, property_types, tenure_types, bedroom_options, property_status_categories, offer_statuses, document_types, webhook_event_types, and more. No auth required.

5. List Your Branches

curl https://api.thebid.uk/v1/branches \
  -H "Authorization: Bearer bid_at_..."

Response:

{
  "data": [
    { "branch_id": "uuid", "branch_name": "London Office", "phone_number": "020 7123 4567" }
  ],
  "request_id": "uuid"
}

Cache the branch_id values — you'll need one to submit referrals.

6. Submit a Referral

curl -X POST https://api.thebid.uk/v1/referrals \
  -H "Authorization: Bearer bid_at_..." \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "branch_id": "YOUR_BRANCH_ID",
    "type": "new_instruction",
    "property": {
      "house_name_or_number": "42",
      "street_name": "High Street",
      "town_or_city": "London",
      "post_code": "SW1A 1AA",
      "property_type": "terraced",
      "number_of_bedrooms": "3",
      "number_of_bathrooms": "2",
      "tenure": "freehold",
      "vacant": true
    },
    "customers": [{
      "name": "Jane Smith",
      "email": "jane@example.com",
      "phone": "07700 900000",
      "house_name_or_number": "10",
      "street_name": "Oak Road",
      "town_or_city": "London",
      "post_code": "SW1A 2BB"
    }],
    "notes": "Seller wants a quick sale"
  }'

Response (201):

{
  "data": {
    "referral_id": "uuid",
    "status": "new",
    "created_at": "2026-05-23T14:00:00Z"
  },
  "request_id": "uuid"
}

7. Track Progress

Once a referral is instructed and a property is created, you can track buyers and offers:

# Get property details (includes milestones)
curl "https://api.thebid.uk/v1/properties?property_id=PROPERTY_UUID" \
  -H "Authorization: Bearer bid_at_..."

# Get buyers for a property
curl "https://api.thebid.uk/v1/buyers?property_id=PROPERTY_UUID" \
  -H "Authorization: Bearer bid_at_..."

# Get a single buyer with full offer history
curl "https://api.thebid.uk/v1/buyers?buyer_id=BUYER_UUID" \
  -H "Authorization: Bearer bid_at_..."

8. Set Up Webhooks (Optional)

Receive real-time notifications instead of polling:

curl -X POST https://api.thebid.uk/v1/webhooks \
  -H "Authorization: Bearer bid_at_..." \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://your-server.com/webhooks/bid",
    "events": ["referral.status_changed", "property.status_changed", "buyer.offer_updated"]
  }'

Store the returned secret immediately — it's shown once and used to verify payload signatures.

Authentication

The Bid API uses OAuth 2.0 Client Credentials for machine-to-machine authentication.

How It Works

  1. You receive a client_id and client_secret from Bid
  2. Exchange them for a short-lived access token (1 hour)
  3. Use the access token as a Bearer token on all API requests
  4. When it expires, exchange again — no refresh tokens needed

Token Exchange

cURL:

curl -X POST https://api.thebid.uk/v1/oauth/token \
  -H "Content-Type: application/json" \
  -d '{
    "client_id": "your-uuid",
    "client_secret": "bid_cs_your_secret",
    "grant_type": "client_credentials"
  }'

Node/TypeScript:

const res = await fetch("https://api.thebid.uk/v1/oauth/token", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    client_id: process.env.BID_CLIENT_ID,
    client_secret: process.env.BID_CLIENT_SECRET,
    grant_type: "client_credentials",
  }),
});
const token = await res.json();
// token.access_token, token.expires_in

C#:

var client = new HttpClient();
var response = await client.PostAsync(
    "https://api.thebid.uk/v1/oauth/token",
    new StringContent(JsonSerializer.Serialize(new {
        client_id = Environment.GetEnvironmentVariable("BID_CLIENT_ID"),
        client_secret = Environment.GetEnvironmentVariable("BID_CLIENT_SECRET"),
        grant_type = "client_credentials"
    }), Encoding.UTF8, "application/json"));
var result = await response.Content.ReadFromJsonAsync<TokenResponse>();

Handling Token Expiry

Tokens expire after 3600 seconds (1 hour). On receiving a 401 response, re-authenticate:

async function apiCall(url: string, options: RequestInit = {}) {
  let token = await getToken(); // cached token
  let res = await fetch(url, {
    ...options,
    headers: { ...options.headers, Authorization: `Bearer ${token}` },
  });
  if (res.status === 401) {
    token = await refreshToken(); // exchange credentials again
    res = await fetch(url, {
      ...options,
      headers: { ...options.headers, Authorization: `Bearer ${token}` },
    });
  }
  return res;
}

Storing Credentials

  • Never commit client_secret to source control
  • Use environment variables or a secrets manager
  • The client_secret starts with bid_cs_ — if you see this in logs, rotate immediately

Rate Limits

  • 60 requests per minute per client (configurable by Bid)
  • Token exchange: 10 requests per minute
  • On 429 Too Many Requests: back off and retry after the rate window resets (1 minute)

Permissions

Your client is issued with specific permissions. Calling an endpoint you don't have permission for returns 403 FORBIDDEN:

Permission Endpoints
referrals:read GET /v1/referrals
referrals:write POST /v1/referrals
properties:read GET /v1/properties
buyers:read GET /v1/buyers
documents:read POST /v1/documents/{id}/access-url
webhooks:manage GET/POST/DELETE /v1/webhooks

The following endpoints require a valid token but no specific permission:

  • GET /v1/me — your account info
  • GET /v1/branches — your branch list

The following endpoints require no authentication:

  • GET /v1/health — API health check
  • GET /v1/enums — reference data / enum values

Buyer Tracking

Attribute marketplace buyers to your own campaigns, channels, or internal IDs using the optional external_ref reference on every buyer.

Append ?external_ref=YOUR_REF to any marketplace or branded microsite URL. A property URL works best, because the reference is attributed to that specific property:

https://marketplace.thebid.uk/{slug}/property/{id}?external_ref=YOUR_REF

YOUR_REF is any free-text value you control (for example a campaign code or your own internal customer ID). It is treated as opaque text, trimmed, and capped at 128 characters.

2. What happens when a visitor arrives

  • The reference is captured on landing and removed from the URL, so if the visitor copies or shares the link it will not carry your reference and tag other people.
  • It is attributed to the property the visitor was linked to (when the link points at a property page).
  • It persists for up to 5 days and uses last-write-wins — if the visitor later arrives via a different tracking link, the newer reference replaces the older one.
  • When the visitor enquires, bids, registers interest, or likes the property, their Buyer record is stamped with your reference.

3. Read it back

The external_ref is returned on every buyer response, and you can list all buyers carrying a reference:

curl https://api.thebid.uk/v1/buyers?external_ref=YOUR_REF \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"

Lookups are scoped to your own agent — you only ever see your own buyers, even if another partner happens to use the same reference value.

You can also set external_ref directly when creating a referral via the API; see the Referrals endpoint.

Webhooks

Receive real-time notifications when referral status changes, properties reach milestones, or new buyers are registered.

Registering a Subscription

curl -X POST https://api.thebid.uk/v1/webhooks \
  -H "Authorization: Bearer bid_at_..." \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://your-server.com/webhooks/bid",
    "events": ["referral.created", "referral.status_changed", "property.status_changed"]
  }'

Requirements:

  • URL must use https://
  • No localhost, private IPs (10.*, 172.16-31.*, 192.168.*), or .local domains
  • The secret is returned once on creation — store it immediately

Available Events

Event Trigger
referral.created New referral submitted
referral.status_changed Referral status updated
property.status_changed Property status category changes
property.milestone_completed A milestone is marked complete
property.document_added A document is added to a property
property.marketplace_updated A property's marketplace listing is published/unpublished or its sale status changes
buyer.created New buyer registered on a property
buyer.offer_updated Buyer's offer status changes

Payload Envelope

Every delivery uses this structure:

{
  "version": "1",
  "event_id": "uuid",
  "event_type": "referral.status_changed",
  "created_at": "2026-05-23T14:30:00Z",
  "data": {
    "referral_id": "uuid",
    "old_status": "new",
    "new_status": "in_progress"
  }
}

Branch on version for forward compatibility. Treat missing version as "1".

No PII in payloads — only IDs and status values. Fetch full details via the GET endpoints.

Verifying Signatures

Every delivery includes three headers:

  • X-Bid-Event — the event type
  • X-Bid-Timestamp — Unix epoch seconds at delivery time
  • X-Bid-Signaturesha256=<hex HMAC>

The signed string is: timestamp + "." + raw_body

Node/TypeScript:

import crypto from "crypto";

function verifyWebhook(body: string, headers: Headers, secret: string): boolean {
  const timestamp = headers.get("x-bid-timestamp")!;
  const signature = headers.get("x-bid-signature")!;

  // Reject stale deliveries (replay protection)
  if (Math.abs(Date.now() / 1000 - parseInt(timestamp)) > 300) {
    return false;
  }

  const expected = "sha256=" + crypto
    .createHmac("sha256", secret)
    .update(`${timestamp}.${body}`)
    .digest("hex");

  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expected),
  );
}

C#:

bool VerifyWebhook(string body, string timestamp, string signature, string secret) {
    if (Math.Abs(DateTimeOffset.UtcNow.ToUnixTimeSeconds() - long.Parse(timestamp)) > 300)
        return false;

    using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(secret));
    var hash = hmac.ComputeHash(Encoding.UTF8.GetBytes($"{timestamp}.{body}"));
    var expected = "sha256=" + BitConverter.ToString(hash).Replace("-", "").ToLower();
    return CryptographicOperations.FixedTimeEquals(
        Encoding.UTF8.GetBytes(signature),
        Encoding.UTF8.GetBytes(expected));
}

Retry Behaviour

Failed deliveries (non-2xx or timeout) are retried with exponential backoff:

Attempt Retry after
1 30 seconds
2 2 minutes
3 15 minutes
4 1 hour
5 4 hours

After 5 failed attempts, the event is marked as dead-letter (no further retries).

[Bid Webhook][Your HTTPS endpoint][Message Queue][Worker]

Accept the delivery immediately (return 200), queue it for processing. This avoids timeouts if your processing is slow.

Documents

Access property documents (legal packs, AML reports, etc.) via short-lived signed URLs.

Listing Documents

Documents are included in property responses when you pass ?include=documents:

curl "https://api.thebid.uk/v1/properties?property_id=UUID&include=documents" \
  -H "Authorization: Bearer bid_at_..."

Response includes:

{
  "data": {
    "property_id": "...",
    "documents": [
      {
        "id": "document-uuid",
        "name": "1716480000-abc123-legal-pack.pdf",
        "type": "Legal Pack",
        "uploaded_at": "2026-05-23T10:00:00Z",
        "modified_at": "2026-05-23T10:00:00Z"
      }
    ]
  }
}

Notes:

  • name is the verbatim last path segment from storage — do not assume a stable format
  • file_size and mime_type are intentionally not included (not stored on the document record)
  • Requires both properties:read and documents:read permissions

Getting a Signed URL

curl -X POST "https://api.thebid.uk/v1/documents/DOCUMENT_ID/access-url" \
  -H "Authorization: Bearer bid_at_..."

Response:

{
  "data": {
    "url": "https://storage.example.com/sign/documents/...",
    "expires_in": 120
  }
}

Download Pattern

The signed URL expires after 120 seconds. Download immediately:

const { data } = await bidApi.post(`/v1/documents/${docId}/access-url`);
const fileResponse = await fetch(data.url);
const buffer = await fileResponse.arrayBuffer();
fs.writeFileSync(`downloads/${filename}`, Buffer.from(buffer));

Security Considerations

  • Do not cache signed URLs — they expire in 120 seconds
  • Do not log signed URLs — they grant temporary access to the file
  • Each access is audit-logged (agent, client, document, IP, timestamp)
  • Cross-agent access returns 404 (not 403) to prevent information leakage

Errors

All errors return a JSON envelope with an error object containing code, message, and optionally details.

Error Codes

HTTP Code Description
400 VALIDATION_ERROR Request body failed validation or missing required header
401 UNAUTHORIZED Missing, invalid, or expired token
403 FORBIDDEN Token lacks required permission
404 NOT_FOUND Resource not found (or cross-agent access)
405 METHOD_NOT_ALLOWED HTTP method not supported on this endpoint
409 CONFLICT Duplicate referral detected
429 RATE_LIMITED Too many requests — wait and retry
500 INTERNAL_ERROR Server error (message is always "Internal server error")

Error Response Shape

{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Missing required field: branch_id",
    "details": ["Missing required field: branch_id", "Missing required field: post_code"],
    "request_id": "uuid"
  }
}
  • details is only present on 4xx errors
  • 5xx errors never expose internal messages — always "Internal server error"

CONFLICT (409) — Duplicate Referrals

When submitting a referral that matches an existing address:

Open referral exists:

{
  "error": {
    "code": "CONFLICT",
    "message": "An open referral already exists for this address",
    "details": {
      "reason": "open_referral",
      "referral_id": "existing-uuid",
      "status": "in_progress"
    }
  }
}

Active property at address:

{
  "error": {
    "code": "CONFLICT",
    "message": "An active property exists for this address",
    "details": {
      "reason": "active_property",
      "property_id": "property-uuid",
      "referral_id": "referral-uuid",
      "status_category": "for_sale"
    }
  }
}

Idempotency

POST /referrals requires an Idempotency-Key header (max 255 characters, UUID recommended):

curl -X POST .../v1/referrals \
  -H "Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000" \
  ...
  • Same key + same client = replayed original response (no duplicate created)
  • Keys expire after 24 hours
  • Missing key on POST /referrals = 400 VALIDATION_ERROR
  • Other POST endpoints (/webhooks, /documents) do not require idempotency keys

Handling Errors Programmatically

const res = await fetch(url, options);

if (res.status === 401) {
  // Re-authenticate and retry
  token = await getNewToken();
  return retry(url, options, token);
}

if (res.status === 429) {
  // Back off 60 seconds (rate window)
  await sleep(60_000);
  return retry(url, options, token);
}

if (res.status === 409) {
  const { error } = await res.json();
  // Use error.details.reason to decide: link to existing or alert user
}

if (res.status >= 500) {
  // Retry with exponential backoff (Bid is having issues)
}

Health

Health check endpoint

Health check

Unauthenticated endpoint to verify the API is reachable.

Responses

Response samples

Content type
application/json
{
  • "status": "ok",
  • "version": "1",
  • "timestamp": "2019-08-24T14:15:22Z"
}

Account

Agent account and branch information

Get current agent info

Returns the authenticated agent's details, branches, API key name, and permissions. Use this to verify your credentials and discover branch IDs.

Authorizations:
BearerAuth

Responses

Response samples

Content type
application/json
{
  • "data": {
    },
  • "request_id": "266ea41d-adf5-480b-af50-15b940c2b846"
}

List branches

Returns all branches for the authenticated agent. Use the branch_id values when creating referrals. No specific permission required — any valid token works.

Authorizations:
BearerAuth

Responses

Response samples

Content type
application/json
{
  • "data": [
    ],
  • "request_id": "266ea41d-adf5-480b-af50-15b940c2b846"
}

OAuth

Token exchange endpoints

Exchange credentials for access token

OAuth 2.0 Client Credentials grant (RFC 6749 §4.4).

Client credentials can be provided via:

  • HTTP Basic auth (recommended): Authorization: Basic base64(client_id:client_secret)
  • Request body: client_id and client_secret in JSON or form-urlencoded body

Do not use both methods simultaneously.

Request Body schema:
required
grant_type
required
string
Value: "client_credentials"
client_id
string <uuid>

Required unless using HTTP Basic auth

client_secret
string

Required unless using HTTP Basic auth

Responses

Request samples

Content type
No sample

Response samples

Content type
application/json
{
  • "access_token": "bid_at_a1b2c3d4e5f6...",
  • "token_type": "Bearer",
  • "expires_in": 3600,
  • "permissions": [
    ]
}

Referrals

Referral management endpoints

List referrals

Returns referrals for the authenticated agent with pagination.

Authorizations:
BearerAuth
query Parameters
page
integer
Default: 1
per_page
integer <= 200
Default: 50
referral_id
string <uuid>

Return a single referral by ID

status
string
branch_id
string <uuid>
created_after
string <date-time>
external_ref
string

Filter by exact external reference match.

Responses

Response samples

Content type
application/json
{
  • "data": {
    },
  • "pagination": {
    },
  • "request_id": "string"
}

Create a referral

Submit a new property referral. Requires the Idempotency-Key header. Returns 409 CONFLICT if a duplicate address is found.

The request body must contain property (object) and customers (array of 1–5 entries).

Authorizations:
BearerAuth
header Parameters
Idempotency-Key
required
string <= 255 characters

Unique key to prevent duplicate submissions (UUID recommended)

Request Body schema: application/json
required
branch_id
required
string <uuid>
type
required
string
Enum: "new_instruction" "on_the_market_with_you_already" "sale_fallen_through" "valuation"
required
object
required
Array of objects [ 1 .. 5 ] items

Array of customers (1-5 entries). Each entry requires name, phone, email, and address fields.

notes
string
valuation_notes
string
external_ref
string

Optional external reference string (e.g. your own internal ID).

referral_agent_email
string

Optional referral agent email. Captured and stored for future use.

Responses

Request samples

Content type
application/json
{
  • "branch_id": "7a4e8e99-89f2-4a0f-b66c-fc595dda2dbc",
  • "type": "new_instruction",
  • "property": {
    },
  • "customers": [
    ],
  • "notes": "string",
  • "valuation_notes": "string",
  • "external_ref": "string",
  • "referral_agent_email": "string"
}

Response samples

Content type
application/json
{
  • "data": {
    },
  • "request_id": "string"
}

Properties

Property data endpoints

List or get properties

Authorizations:
BearerAuth
query Parameters
page
integer
Default: 1
per_page
integer <= 200
Default: 50
property_id
string <uuid>
status_category
string
branch_id
string <uuid>
external_ref
string

Filter by exact external reference match.

include
string
Example: include=documents

Comma-separated includes. Supports "documents" (requires documents:read permission).

Responses

Response samples

Content type
application/json
{
  • "data": {
    },
  • "pagination": {
    },
  • "request_id": "string"
}

Buyers

Buyer and offer data endpoints, including the optional external_ref tracking reference (see the Buyer Tracking guide)

Get buyers

Three modes (provide exactly one parameter):

  • ?property_id=UUID — all buyers for a property (with latest offer for each)
  • ?buyer_id=UUID — single buyer with full offer history, address, company, and co-buyers
  • ?external_ref=STRING — all buyers carrying that external reference (with latest offer for each)

In list modes (property_id or external_ref) you can additionally pass ?offer_status= to return only buyers whose latest offer has that status. Not applicable to buyer_id (single-buyer) mode.

A buyer may be purchasing through a company and alongside additional buyers (co-buyers). List modes include the company and a co_buyer_count; the single-buyer mode additionally returns the full co_buyers array.

Agent-scoped: only returns buyers for properties belonging to your agent. Requires buyers:read permission.

Authorizations:
BearerAuth
query Parameters
property_id
string <uuid>

List all buyers for this property

buyer_id
string <uuid>

Get a single buyer with full offer history

external_ref
string

List all your buyers carrying this exact external reference. See the Buyer Tracking guide.

offer_status
string
Enum: "pending" "accepted" "declined" "fee_paid"

Optional. In list modes only, return only buyers whose latest offer has this status. Buyers with no offers are excluded when this filter is set.

Responses

Response samples

Content type
application/json
{
  • "data": [
    ],
  • "request_id": "266ea41d-adf5-480b-af50-15b940c2b846"
}

Enums

Reference data and enum values

List all enum values

Returns every enum/list value the API accepts or returns. Use this to populate dropdowns, validate inputs, or stay in sync with supported values. No authentication required.

Responses

Response samples

Content type
application/json
{
  • "data": {
    },
  • "request_id": "266ea41d-adf5-480b-af50-15b940c2b846"
}

Document Access

Document signed URL endpoints

Generate a signed document URL

Returns a short-lived signed URL (120 seconds) for downloading the document. Each access is audit-logged.

The document_id must be provided as a path parameter.

Authorizations:
BearerAuth
path Parameters
document_id
required
string <uuid>

Responses

Response samples

Content type
application/json
{}

Webhook Subscriptions

Webhook subscription management endpoints

List webhook subscriptions

Returns subscriptions scoped to the authenticated OAuth client. Each client manages its own independent set of webhook subscriptions.

Authorizations:
BearerAuth

Responses

Response samples

Content type
application/json
{
  • "data": [
    ],
  • "request_id": "string"
}

Create a webhook subscription

Register a URL to receive event notifications. The secret (prefixed whsec_) is returned once on creation — store it securely for signature verification.

Subscriptions are scoped per OAuth client.

Authorizations:
BearerAuth
Request Body schema: application/json
required
url
required
string <uri>

Must be a public HTTPS URL (no localhost, private IPs, .local hostnames)

events
required
Array of strings
Items Enum: "referral.created" "referral.status_changed" "property.status_changed" "property.milestone_completed" "property.document_added" "property.marketplace_updated" "buyer.created" "buyer.offer_updated"

Responses

Request samples

Content type
application/json
{}

Response samples

Content type
application/json
{
  • "data": {
    },
  • "request_id": "string"
}

Delete a webhook subscription

Authorizations:
BearerAuth
query Parameters
subscription_id
required
string <uuid>

Responses

Response samples

Content type
application/json
{
  • "data": {
    },
  • "request_id": "string"
}