Skip to content

Protests

Protests are bid protest records exposed at /api/protests/. For field definitions, see the Protests Data Dictionary.

Authentication: Protests endpoints require authentication (API key or OAuth2). Unauthenticated requests receive HTTP 401.

Endpoints

  • GET /api/protests/ (list + filtering + search + pagination)
  • GET /api/protests/{case_id}/ (detail by deterministic case UUID)

Both list and detail return case-level objects identified by case_id (a deterministic UUID derived from source_system + base_case_number). Subordinate dockets are available via ?shape=...,dockets(...) expansion. The path segment must be a valid RFC 4122 UUID (version 1–5, variant 8/9/a/b).

Filtering

Core filters:

Param What it does
source_system Filter by source system (for example gao). Multi-value: use | for OR.
outcome Filter by protest outcome (for example Denied, Dismissed, Withdrawn, Sustained). Multi-value: use | for OR.
case_type Filter by case type (for example Bid Protest, Bid Protest: Cost). Multi-value: use | for OR.
agency Filter by protested agency text. Multi-value: use | for OR.
case_number Filter by case number (B-number, for example b-423274). Multi-value: use | for OR.
solicitation_number Filter by exact solicitation number.
protester Filter by protester name text. Multi-value: use | for OR.
filed_date_after, filed_date_before Filed date range filters.
decision_date_after, decision_date_before Decision date range filters.
search Full-text search over protest searchable fields.

Ordering

Protests do not support custom ordering via ordering=.

Results are returned in a fixed timeline order:

  • decision_date when present, otherwise filed_date (newest first)
  • UUID descending as a deterministic tie-breaker

If ordering is provided, the API returns HTTP 400.

Pagination

Protests use standard page-number pagination:

  • page (default 1)
  • limit (max 100)

List response: one result per case

The list endpoint returns one result per case (distinct base_case_number), not one per docket. Each result includes case-level fields from the most recent docket in the case. case_id is a deterministic UUID from (source_system, base_case_number) used for grouping and detail lookup. Pagination and filters apply to dockets first; results are then grouped, so count is the total number of matching cases across all pages.

The default (unshaped) response does not include nested dockets. To get dockets, use the dockets expansion via ?shape=...,dockets(...).

Detail response: case-level

The detail endpoint (GET /api/protests/{case_id}/) returns a single case-level object, the same structure as a list item. Use ?shape=...,dockets(...) to include nested dockets.

Shaping

Protests support shape on both list and detail endpoints:

  • GET /api/protests/?shape=...
  • GET /api/protests/{case_id}/?shape=...

Both list and detail return case-level objects. You can request case-level fields and the dockets expansion (e.g. shape=case_id,case_number,title,dockets(case_number,docket_number,filed_date)).

Allowed shape fields (case-level for both list and detail):

  • case_id (deterministic case UUID; use for detail lookup)
  • source_system, case_number (base B-number), title, protester, agency
  • solicitation_number, case_type, outcome
  • filed_date, posted_date, decision_date, due_date
  • docket_url, decision_url
  • digest (opt-in only; from raw_data.digest, e.g. decision summary text)
  • dockets (expand with docket fields, e.g. dockets(case_number,docket_number,filed_date))
  • organization (expand: deterministic resolution of the protested agency to a canonical Tango organization — included in the default response shape; canonical 7-key office payload)
  • resolved_protester (expand: Bayesian entity resolution for the protester name; Pro+ tier)
  • resolved_agency (expand: Bayesian organization resolution for the agency name; Pro+ tier)

Allowed shape fields inside dockets(...):

  • source_system, case_number (base B-number), docket_number (specific docket, e.g. b-424046.1)
  • title, protester, agency, solicitation_number, case_type, outcome
  • filed_date, posted_date, decision_date, due_date
  • docket_url, decision_url
  • digest

The case_number field (e.g. b-424214) identifies the case. The docket_number field (e.g. b-424214.1, b-424214.2) identifies the specific sub-docket within a case and is only available inside dockets(...).

Organization (deterministic): organization(...)

The default response shape includes an organization(*) expansion: the canonical 7-key office payload (organization_id, office_code, office_name, agency_code, agency_name, department_code, department_name) resolved deterministically at ingest time to a canonical Tango organization. This is the same payload used on awards, opportunities, vehicles, forecasts, grants, and IT Dashboard responses.

The deterministic organization expand and the Bayesian resolved_agency expand coexist intentionally:

  • organization is the canonical "what does our matching pipeline think?" answer — fast, cached, and suitable for joining against the rest of your data.
  • resolved_agency (Pro+) carries match_confidence ("confident" / "review") and a human-readable rationale. Useful when you need to know why a particular org was chosen or to surface review-worthy matches to a human.

When the two disagree, prefer organization for canonical use and consult resolved_agency.match_confidence / .rationale to understand the discrepancy.

Entity Resolution: resolved_protester(...)

When a protester name has been resolved to a canonical entity in Tango, you can expand the match via resolved_protester(...). Only high-confidence (confident) and medium-confidence (review) matches are returned; unresolved or low-confidence names return null.

Allowed fields inside resolved_protester(...):

  • uei — Unique Entity Identifier of the matched entity
  • name — Display name of the matched entity
  • match_confidence"confident" (auto-linkable) or "review" (needs human review)
  • rationale — Human-readable explanation of why the match was made

Organization Resolution: resolved_agency(...)

When an agency name has been resolved to a canonical organization in Tango, you can expand the match via resolved_agency(...). Same confidence rules as resolved_protester.

Allowed fields inside resolved_agency(...):

  • key — UUID key of the matched organization
  • name — Display name of the matched organization
  • match_confidence"confident" or "review"
  • rationale — Human-readable explanation

Not exposed in the API (internal only):

  • raw_data, field_provenance, external_id, data_quality, source_last_updated, created, modified
  • internal search/index fields (for example search_vector)

Examples:

# Case-level fields and nested dockets
GET /api/protests/?shape=case_id,case_number,title,dockets(case_number,docket_number,filed_date,outcome)

# Case-level only
GET /api/protests/?shape=case_id,source_system,case_number,title,outcome,filed_date

More examples:

# Timeline-focused list payload
GET /api/protests/?source_system=gao&shape=case_id,title,outcome,filed_date,decision_date

# Link-focused payload
GET /api/protests/?shape=case_id,docket_url,decision_url

# Detail by case UUID with shaped response
GET /api/protests/550e8400-e29b-41d4-a716-446655440000/?shape=case_id,source_system,title,agency,protester,filed_date,decision_date

# Detail with nested dockets
GET /api/protests/550e8400-e29b-41d4-a716-446655440000/?shape=case_id,title,dockets(docket_number,filed_date,outcome)

# Entity resolution for protester
GET /api/protests/?shape=case_id,protester,resolved_protester(*)

# Organization resolution for agency
GET /api/protests/?shape=case_id,agency,resolved_agency(*)

# Selective resolution fields
GET /api/protests/?shape=case_id,resolved_protester(uei,match_confidence)

Notes:

  • Use the dockets expansion to request nested docket fields (e.g. dockets(docket_number,case_number)).
  • * is only valid inside expansions. Use explicit field lists at the root.
  • Invalid shape fields return HTTP 400 with structured validation errors.