Skip to content
GovBidReadyNow
Developer API

Use GovBidReadyNow from your own applications.

The GovBidReadyNow API gives your systems server-to-server access to your organization’s workspace: search opportunities with the same criteria as the app, keep pursuits in sync with your CRM, and read proposals and contracts. Keys are created by your owners and administrators, scoped to the access you choose, and limited to their creator’s current role.

Authentication

Base URL: https://app.govbidreadynow.com/api/v1/gov/public/v1. Version 1.0.0; breaking changes will ship under a new /v2 path.

Owners and administrators create keys in Settings → API keys. Send the key as Authorization: Bearer fbk_… (or X-API-Key: fbk_…). The full key is shown once; GovBidReadyNow stores only a keyed hash. Every key has an expiry of at most one year and can be rotated or revoked at any time.

A key acts for the person who created it. It never has more access than that person’s current role, and it stops working if they leave the organization or their account or the organization is suspended. Each request needs both the operation’s scope and a role that permits the action.

Browsers are not supported. The API sends no CORS headers; call it from your server, scripts or terminal and keep keys out of web pages, mobile apps and source control.

Scopes

ScopeAllows
opportunities:readSearch opportunities and read opportunity details.
pursuits:readList pursuits and their stages.
pursuits:writeSave, unsave, add public opportunities, start responses, change capture stages and record submissions or outcomes.
proposals:readRead proposal metadata and section text. Pricing is never included.
contracts:readRead awarded contracts.
comments:readRead opportunity comments.
comments:writeAdd opportunity comments.

The API does not provide AI generation, agents, billing, member management or document uploads.

Rate limits

60 requests per minute per key and 600 per organization, measured over a sliding minute. Every response includes X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset (seconds until the window resets). A 429 response includes Retry-After; back off and retry after that many seconds. Repeated failed authentication from one address is also limited.

Pagination

List and search operations take offset (≤ 10,000) and limit (1–100, default 25) and return total and nextOffset, which is null on the last page.

Errors

Errors share one shape: {"error": {"code": "not_found", "message": "Record not found in this workspace."}}. Branch on code; messages are for people.

StatusCodeMeaning
400bad_requestMalformed header such as an invalid Idempotency-Key.
401invalid_api_keyMissing, malformed, expired or revoked key, or its creator no longer has access. The response never says which.
402payment_requiredThe organization is read-only: reads and search still work, writes do not.
403insufficient_scope · forbidden · role_exceeds_creator · organization_suspendedThe key lacks the scope, the role does not permit the action, or the organization is suspended.
404not_foundNot in this organization. Records in other organizations are always 404.
409conflict · version_conflictThe record changed, a recorded stage needs its dedicated action, or an idempotent request is still running.
412precondition_failedIf-Match did not match the current ETag.
413payload_too_largeRequest bodies are limited to 256 KB.
422validation_errorThe body or query is invalid; details list the failing fields (values are never echoed).
429rate_limitedToo many requests. Wait for Retry-After seconds.

Idempotency and versions

Send an Idempotency-Key header (1–200 letters, digits, - _ . :) with any POST or PUT. Retrying with the same key and body within 24 hours returns the stored result with Idempotent-Replayed: true instead of acting twice; reusing a key for a different body returns 422.

GET /opportunities/{id} returns an ETag. Send it back as If-Match (or send version in the body) so a write fails with 412/409 instead of overwriting someone else’s change. Submitted, Lost and Cancelled are recorded only with /submission and /outcome, which keep their dates, receipts and reasons; awards are recorded in the app.

Examples

curl
export FED_BIDDER_API_KEY='fbk_…'   # from Settings → API keys

curl -sS https://app.govbidreadynow.com/api/v1/gov/public/v1/opportunities/search \
  -H "Authorization: Bearer $FED_BIDDER_API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"criteria": {"q": "cloud migration", "naics": ["541512"], "response": "open", "sort": "deadline"}, "limit": 10}'
Python (standard library)
import json, os, urllib.request

BASE = "https://app.govbidreadynow.com/api/v1/gov/public/v1"
KEY = os.environ["FED_BIDDER_API_KEY"]

def call(method, path, body=None, idempotency_key=None):
    headers = {"Authorization": f"Bearer {KEY}", "Content-Type": "application/json"}
    if idempotency_key:
        headers["Idempotency-Key"] = idempotency_key
    data = json.dumps(body).encode() if body is not None else None
    request = urllib.request.Request(BASE + path, data=data, method=method, headers=headers)
    with urllib.request.urlopen(request, timeout=30) as response:
        return json.load(response)

# Page through every open opportunity that matches your criteria.
offset = 0
while offset is not None:
    page = call("POST", "/opportunities/search", {"criteria": {"q": "zero trust", "response": "open"}, "offset": offset, "limit": 100})
    for item in page["items"]:
        print(item["id"], item["deadline"], item["title"])
    offset = page["nextOffset"]

# Save an opportunity exactly once, even if this script is retried.
call("PUT", "/opportunities/OPPORTUNITY_ID/saved", {"saved": True}, idempotency_key="crm-sync-OPPORTUNITY_ID")
Terminal with jq
# Who am I? Shows the organization, key scopes and expiry.
curl -sS https://app.govbidreadynow.com/api/v1/gov/public/v1/me -H "Authorization: Bearer $FED_BIDDER_API_KEY" | jq

# Pursuits due soonest, one per line.
curl -sS "https://app.govbidreadynow.com/api/v1/gov/public/v1/pursuits?sort=deadline&limit=20" \
  -H "Authorization: Bearer $FED_BIDDER_API_KEY" \
  | jq -r '.items[] | [.deadline, .stage, .title] | @tsv'

# Move a pursuit to Go decision only if nobody changed it since you read it.
ETAG=$(curl -sS -D - -o /dev/null https://app.govbidreadynow.com/api/v1/gov/public/v1/opportunities/OPPORTUNITY_ID \
  -H "Authorization: Bearer $FED_BIDDER_API_KEY" | awk -F': ' 'tolower($1)=="etag"{print $2}' | tr -d '\r')
curl -sS https://app.govbidreadynow.com/api/v1/gov/public/v1/opportunities/OPPORTUNITY_ID/stage \
  -H "Authorization: Bearer $FED_BIDDER_API_KEY" -H "If-Match: $ETAG" \
  -H 'Content-Type: application/json' -d '{"stage": "Go decision"}'

Endpoints

Account

GET /me

Describe the calling API key · any valid key

Example 200 response
{
  "organization": {
    "id": "org-1",
    "name": "Example Federal"
  },
  "key": {
    "id": "3f9a1c2b4d5e6f70",
    "prefix": "fbk_3f9a1c2b4d5e6f70_",
    "name": "CRM sync",
    "scopes": [
      "opportunities:read"
    ],
    "expires": "2027-01-20T00:00:00+00:00"
  },
  "actingFor": {
    "uid": "u-1",
    "role": "admin"
  }
}

Opportunities

POST /opportunities/search

Search opportunities · scope opportunities:read

Body: SearchBody

Example 200 response
{
  "items": [
    {
      "id": "opp-8f2c",
      "catalogId": null,
      "inWorkspace": true,
      "title": "Cloud modernization support",
      "agency": "Department of Example",
      "solicitation": "EX-26-0042",
      "type": "Solicitation",
      "naics": "541512",
      "setAside": "Small business",
      "value": 4200000,
      "deadline": "2026-12-01T17:00:00Z",
      "posted": "2026-10-01T00:00:00Z",
      "stage": "Qualifying",
      "score": 78,
      "saved": true,
      "responseState": "Open",
      "sourceUrl": "https://sam.gov/opp/example/view",
      "version": 7
    }
  ],
  "counts": {
    "all": 1,
    "strong": 1,
    "saved": 1
  },
  "offset": 0,
  "limit": 25,
  "total": 1,
  "nextOffset": null
}

GET /opportunities/{id}

Get a workspace opportunity · scope opportunities:read

ParameterInTypeNotes
id requiredpathstringIdentifier from a list or search result. · 1–128 chars
Example 200 response
{
  "id": "opp-8f2c",
  "catalogId": null,
  "inWorkspace": true,
  "title": "Cloud modernization support",
  "agency": "Department of Example",
  "solicitation": "EX-26-0042",
  "type": "Solicitation",
  "naics": "541512",
  "setAside": "Small business",
  "value": 4200000,
  "deadline": "2026-12-01T17:00:00Z",
  "posted": "2026-10-01T00:00:00Z",
  "stage": "Qualifying",
  "score": 78,
  "saved": true,
  "responseState": "Open",
  "sourceUrl": "https://sam.gov/opp/example/view",
  "version": 7
}

GET /catalog/{id}

Get a public notice not yet in your workspace · scope opportunities:read

ParameterInTypeNotes
id requiredpathstringIdentifier from a list or search result. · 1–128 chars

Pursuits

GET /pursuits

List pursuits · scope pursuits:read

ParameterInTypeNotes
stagequery"Identified" | "Qualifying" | "Go decision" | "In proposal" | "Submitted" | "Awarded" | "Lost" | "Cancelled" | "No go"[]≤ 9 items
qquerystring0–1000 chars · default ""
sortquery"fit" | "deadline" | "newest" | "value" | "title"default "deadline"
offsetqueryinteger0–10000 · default 0
limitqueryinteger1–100 · default 25
Example 200 response
{
  "items": [
    {
      "id": "opp-8f2c",
      "catalogId": null,
      "inWorkspace": true,
      "title": "Cloud modernization support",
      "agency": "Department of Example",
      "solicitation": "EX-26-0042",
      "type": "Solicitation",
      "naics": "541512",
      "setAside": "Small business",
      "value": 4200000,
      "deadline": "2026-12-01T17:00:00Z",
      "posted": "2026-10-01T00:00:00Z",
      "stage": "Qualifying",
      "score": 78,
      "saved": true,
      "responseState": "Open",
      "sourceUrl": "https://sam.gov/opp/example/view",
      "version": 7
    }
  ],
  "offset": 0,
  "limit": 25,
  "total": 1,
  "nextOffset": null
}

PUT /opportunities/{id}/saved

Save or unsave an opportunity · scope pursuits:write

ParameterInTypeNotes
id requiredpathstringIdentifier from a list or search result. · 1–128 chars

Optional headers: Idempotency-Key, If-Match.

Body: SavedInput

Example 200 response
{
  "id": "opp-8f2c",
  "saved": true,
  "version": 8
}

POST /opportunities/{id}/stage

Change a capture stage · scope pursuits:write

ParameterInTypeNotes
id requiredpathstringIdentifier from a list or search result. · 1–128 chars

Optional headers: Idempotency-Key, If-Match.

Body: StageInput

Example 200 response
{
  "id": "opp-8f2c",
  "stage": "Go decision",
  "version": 8
}

POST /opportunities/{id}/pursue

Start a response (proposal workspace) · scope pursuits:write

ParameterInTypeNotes
id requiredpathstringIdentifier from a list or search result. · 1–128 chars

Optional headers: Idempotency-Key.

Example 200 response
{
  "proposalId": "0d9e6c1a-1b2c-4d5e-8f90-123456789abc",
  "created": true
}

POST /catalog/{id}/adopt

Add a public notice to your workspace · scope pursuits:write

ParameterInTypeNotes
id requiredpathstringIdentifier from a list or search result. · 1–128 chars

Optional headers: Idempotency-Key.

Example 200 response
{
  "id": "org-1:EX-26-0042",
  "catalogId": "sam:abc123",
  "created": true
}

GET /opportunities/{id}/submission-check

Pre-submission check for an opportunity · scope pursuits:read

ParameterInTypeNotes
id requiredpathstringIdentifier from a list or search result. · 1–128 chars

POST /opportunities/{id}/submission

Record a proposal submission · scope pursuits:write

ParameterInTypeNotes
id requiredpathstringIdentifier from a list or search result. · 1–128 chars

Optional headers: Idempotency-Key, If-Match.

Body: SubmissionInput

Example 200 response
{
  "id": "opp-8f2c",
  "version": 9,
  "stage": "Submitted",
  "submission": {
    "submittedAt": "2026-11-30",
    "reference": "eBuy receipt 8812"
  }
}

POST /opportunities/{id}/outcome

Record a loss or cancellation · scope pursuits:write

ParameterInTypeNotes
id requiredpathstringIdentifier from a list or search result. · 1–128 chars

Optional headers: Idempotency-Key, If-Match.

Body: OutcomeInput

Example 200 response
{
  "id": "opp-8f2c",
  "version": 10,
  "stage": "Lost",
  "outcome": {
    "stage": "Lost",
    "reason": "Price",
    "awardee": ""
  }
}

Comments

GET /opportunities/{id}/comments

List opportunity comments · scope comments:read

ParameterInTypeNotes
id requiredpathstringIdentifier from a list or search result. · 1–128 chars
offsetqueryinteger0–10000 · default 0
limitqueryinteger1–100 · default 25
Example 200 response
{
  "items": [
    {
      "id": "c-1",
      "opportunityId": "opp-8f2c",
      "author": "Dana (API key: CRM sync)",
      "authorUid": "u-1",
      "text": "Customer call scheduled.",
      "parentId": null,
      "created": "2026-10-21T15:04:05+00:00"
    }
  ],
  "offset": 0,
  "limit": 25,
  "total": 1,
  "nextOffset": null
}

POST /opportunities/{id}/comments

Add a comment · scope comments:write

ParameterInTypeNotes
id requiredpathstringIdentifier from a list or search result. · 1–128 chars

Optional headers: Idempotency-Key.

Body: CommentInput

Proposals

GET /proposals

List proposals · scope proposals:read

ParameterInTypeNotes
opportunityIdquerystring | null0–128 chars
offsetqueryinteger0–10000 · default 0
limitqueryinteger1–100 · default 25
Example 200 response
{
  "items": [
    {
      "id": "p-1",
      "opportunityId": "opp-8f2c",
      "title": "Cloud modernization support — response",
      "status": "Draft",
      "updated": "2026-10-21T15:04:05+00:00",
      "ownerUid": "u-1",
      "version": 3,
      "volumes": [],
      "sectionCount": 1
    }
  ],
  "offset": 0,
  "limit": 25,
  "total": 1,
  "nextOffset": null
}

GET /proposals/{id}

Get a proposal with section text · scope proposals:read

ParameterInTypeNotes
id requiredpathstringIdentifier from a list or search result. · 1–128 chars
Example 200 response
{
  "id": "p-1",
  "opportunityId": "opp-8f2c",
  "title": "Cloud modernization support — response",
  "status": "Draft",
  "version": 3,
  "sections": [
    {
      "title": "Executive summary",
      "volumeId": "",
      "text": "Our approach..."
    }
  ]
}

Contracts

GET /contracts

List contracts · scope contracts:read

ParameterInTypeNotes
offsetqueryinteger0–10000 · default 0
limitqueryinteger1–100 · default 25
Example 200 response
{
  "items": [
    {
      "id": "k-1",
      "title": "Cloud operations",
      "awardNumber": "EX-26-C-0001",
      "customer": "Department of Example",
      "status": "Active",
      "value": 4200000,
      "start": "2026-01-01",
      "end": "2027-12-31",
      "version": 2
    }
  ],
  "offset": 0,
  "limit": 25,
  "total": 1,
  "nextOffset": null
}

GET /contracts/{id}

Get a contract · scope contracts:read

ParameterInTypeNotes
id requiredpathstringIdentifier from a list or search result. · 1–128 chars

Request schemas

SearchBody

FieldTypeNotes
criteriaCriteriaThe same search criteria used by the GovBidReadyNow app.
includePublicbooleanInclude public notices not yet added to your workspace (inWorkspace=false). · default true
offsetinteger0–10000 · default 0
limitinteger1–100 · default 25

Criteria

FieldTypeNotes
qstring0–1000 chars · default ""
match"all" | "any"default "all"
scope"all" | "workspace" | "pursuits"default "all"
collection"all" | "strong" | "saved"default "all"
agenciesstring[]≤ 30 items
sourcesstring[]≤ 30 items
noticeTypesstring[]≤ 30 items
setAsidesstring[]≤ 30 items
stages"Identified" | "Qualifying" | "Go decision" | "In proposal" | "Submitted" | "Awarded" | "Lost" | "Cancelled" | "No go"[]≤ 9 items
ownersstring[]≤ 30 items
naicsstring[]≤ 30 items
vehiclesstring[]≤ 30 items
vehicleTypesstring[]≤ 4 items
vehicleMatch"any" | "field" | "unidentified"default "any"
locationstring0–200 chars · default ""
response"active" | "all" | "open" | "closed" | "unknown"default "active"
dueFromstringdefault ""
dueTostringdefault ""
dueWithinDays0 | 7 | 14 | 30 | 60 | 90default 0
postedFromstringdefault ""
postedTostringdefault ""
postedWithinDays0 | 7 | 14 | 30 | 60 | 90default 0
releaseFromstringdefault ""
releaseTostringdefault ""
awardFromstringdefault ""
awardTostringdefault ""
minValuenumber | null0–1000000000000000
maxValuenumber | null0–1000000000000000
unknownValue"include" | "exclude" | "only"default "include"
minFitinteger0–100 · default 0
amendedOnlybooleandefault false
includeSparsebooleandefault false
sort"fit" | "relevance" | "match" | "deadline" | "newest" | "value" | "title" | "upcoming"default "fit"
minMatch"" | "possible" | "good" | "strong"default ""
includeDismissedbooleandefault false
applyExclusionsbooleandefault true

SavedInput

FieldTypeNotes
versioninteger | nullOptional current version; a stale value returns 409. · 1–∞
saved requiredboolean

StageInput

FieldTypeNotes
versioninteger | nullOptional current version; a stale value returns 409. · 1–∞
stage required"Identified" | "Qualifying" | "Go decision" | "No go"Capture stages only. Start a response with /pursue; record Submitted, Lost or Cancelled with /submission and /outcome.

SubmissionInput

FieldTypeNotes
versioninteger | null
acknowledgedWarningsstring[]≤ 2000 items
packageSha256stringdefault ""
reference requiredstring1–1000 chars
submittedOnstring | null
notestring0–4000 chars · default ""

OutcomeInput

FieldTypeNotes
versioninteger | null
outcome required"Lost" | "Cancelled"
reason requiredstring3–4000 chars
awardeestring0–500 chars · default ""
debriefstring0–10000 chars · default ""

CommentInput

FieldTypeNotes
text requiredstring1–20000 chars
parentIdstring | null0–128 chars

Connect discovery, proposals and delivery

Create your workspace