The federal contracting data layer

Federal Contract Vehicles API

See the IDIQs, GWACs, and schedules that task orders ride on. Know who holds each vehicle and who is actually earning through it. One row per vehicle, searchable back to 1982.

Most federal services work is not bought as a standalone contract. It is a task order placed against a vehicle the winner already holds: an IDIQ, a GWAC, an FSS schedule, a BPA, or a BOA. So the actionable question is often not "who won this order" but "what would I have needed to hold to bid at all." FPDS records these vehicles as Indefinite Delivery Vehicle (IDV) records, but a vehicle's base award and each of its modifications are separate FPDS rows, so a raw pull buries the vehicles under the thousands of modification records behind them. This API collapses that: one row per vehicle, carrying its latest transaction, with pagination.total counting distinct vehicles. Vehicles are long-lived, so the data is not date-windowed the way /contracts/* is; the full history we carry is searchable on every plan, back to 1982. Search and single-vehicle detail are free; the Pro holders block adds who holds each vehicle and who is actually earning through it.

1982
Earliest searchable vehicle (May 1982)
5 types
IDC, FSS, BPA, GWAC, BOA
3,016
GWACs in the index (idv_type=GWAC)
One row
Per vehicle, not per transaction
On this page:

What a contract vehicle actually is

A contract vehicle is the indefinite-delivery instrument that individual orders are placed against. FPDS calls them Indefinite Delivery Vehicles (IDVs), and there are five kinds:

CodeWhat it is
IDCIndefinite Delivery Contract, the IDIQ family. The largest group by far.
FSSFederal Supply Schedule, the GSA schedules.
BPABlanket Purchase Agreement.
GWACGovernment-Wide Acquisition Contract (SEWP, Alliant, and similar).
BOABasic Ordering Agreement.

The distinction that matters for integration: a vehicle is not an obligation. It is a ceiling and an ordering period. The dollars move on the orders placed against it, which live in the contracts table. A vehicle's own record tells you the most that can be ordered through it (the ceiling) and until when (the ordering period). It does not tell you how much has actually flowed. That is a different query, and this API carries both.

This is the layer that /contracts/* does not answer. Contract award data tells you who won a given order. It does not tell you which vehicle that order rode, who else holds that vehicle and could have bid, or what the vehicle's remaining headroom is. FPDS has all of it; the vehicle records are just buried under their own modification history until you collapse them.

Why this matters (the order is not where it is decided)

If you only have order-level award data, three real questions go unanswered:

  1. Eligibility: could I even have bid. Holding a vehicle is what makes you eligible for the orders placed against it. If you do not hold the IDIQ, you are not in the room when the task order competes. The vehicle layer is where "what would I have needed to hold" lives, and it is knowable in advance.
  2. The competed pool: who am I up against. On a multiple-award IDIQ, the competition for every order is the exact pool of firms that hold the vehicle. That pool is a fixed, public list. Pull it and you know your field before the order posts, not after you lose it.
  3. Where the money actually flows. A vehicle's ceiling is potential, not spent. A firm can hold a vehicle for years and earn nothing on it. The orders roll-up separates the firms that hold a vehicle from the firms actually being paid through it, so you can tell a live vehicle from a dormant one.

The through-line: the vehicle is often where the real gate is. By the time an order posts, the set of firms that can win it was decided when the vehicle was awarded. Reading vehicles is reading that gate.

The three endpoints

Search and single-vehicle detail are on every plan. The holders block is Pro. Same Authorization: Bearer <key> header as everything else.

Filter-based search (at least one of uei, parent_uei, piid, agency, naics, idv_type is required). Free:

GET https://govconapi.com/api/v1/vehicles/search

Single vehicle by PIID, plus a roll-up of everything ordered through it. Free:

GET https://govconapi.com/api/v1/vehicles/{piid}

Who holds a vehicle and who is actually earning on it (two separate populations). Pro:

GET https://govconapi.com/api/v1/vehicles/{piid}/holders

Search paginates the same way as the rest of the API (limit 1-250, offset ≥ 0, exact total) and returns the same envelope (data, pagination, filters_applied, window, _sources). Sort keys: action_date (default), potential_total_value_of_award, ordering_period_end_date, recipient_name. Field projections: lean (default), expanded, full, or a comma-separated column list via ?fields=. Full parameter and field reference in the API guide.

How a vehicle connects to its orders and holders

A vehicle by itself is a stub: a ceiling and a date. The value is in joining it to the orders that ran through it. The API does that join in three places, so you do not have to stitch FPDS tables yourself.

/vehicles/{piid} carries the orders roll-up inline

The single-vehicle response returns the vehicle's latest transaction plus two roll-ups: transaction_rollup across the vehicle's own base-plus-modifications history (where the ceiling lives), and orders_rollup across the orders placed against it (order_count, distinct_orders, total_obligated, distinct_holders, first and latest order dates). One call gives you the ceiling and what has actually been obligated against it.

Live example. GET /api/v1/vehicles/N0003920D0054 (the Navy's SMIT IDIQ, held by Leidos). The vehicle carries a potential_total_value_of_award of $7.73B and an ordering period running to 2028-08-04. Its orders_rollup shows $1.26B obligated across 5,988 distinct orders. The "how much of this ceiling is real" question is one HTTP call.

/vehicles/{piid}/holders is the composed layer across vehicles and orders (Pro)

Two populations, deliberately not merged: awardees (firms holding the vehicle, an FPDS IDV record in their name, the competed pool on a multiple-award IDIQ) and earners (firms actually paid through it, ranked by dollars obligated, with a share_pct). A firm can hold a vehicle for years and earn nothing, so collapsing the two would tell you a firm is a competitor when it has never won an order.

/contracts/{piid}/vehicle resolves an order back to the vehicle it rides

If you are starting from an order PIID rather than a vehicle PIID, this resolves it to the parent vehicle it was placed against. That is the join in the other direction: from a single order you found in /contracts/* to the vehicle whose holders you now want to read.

Response shape and field reference

All three endpoints are sourced from FPDS via USAspending ("_sources": ["usaspending_fpds"]). Search returns a lean row per vehicle by default; ?fields=full widens it. Full field-by-field reference in the API guide.

Search response (the largest GWAC by ceiling, real prod query):

{
  "data": [
    {
      "award_id_piid": "47QTCB26D0001",
      "contract_award_unique_key": "CONT_IDV_47QTCB26D0001_4732",
      "idv_type": "GWAC",
      "idv_type_code": "A",
      "prime_award_base_transaction_description": "THE GSA ALLIANT 3 GWAC PROVIDES IT SERVICES-BASED SOLUTIONS THROUGH PERFORMANCE OF A BROAD RANGE OF IT SERVICES THROUGH FLEXIBLE TASK ORDER CONTRACTING TO ELIGIBLE FEDERAL AGENCIES IN ACCORDANCE WITH GSA ORDER OGP 4800.2I.",
      "action_date": "2026-05-06",
      "period_of_performance_start_date": "2026-02-25",
      "ordering_period_end_date": "2036-03-09",
      "potential_total_value_of_award": 999999999999.99,
      "awarding_agency_name": "General Services Administration",
      "awarding_sub_agency_name": "Federal Acquisition Service",
      "recipient_uei": "JCBMLGPE6Z71",
      "recipient_name": "BOOZ ALLEN HAMILTON INC",
      "naics_code": "541512",
      "naics_description": "COMPUTER SYSTEMS DESIGN SERVICES",
      "multiple_or_single_award_idv": "MULTIPLE AWARD",
      "usaspending_permalink": "https://www.usaspending.gov/award/CONT_IDV_47QTCB26D0001_4732/"
    }
  ],
  "pagination": { "limit": 1, "offset": 0, "total": 3016, "total_is_estimate": false, "has_next": true },
  "filters_applied": { "idv_type": "GWAC", "sort_by": "potential_total_value_of_award", "sort_order": "desc" },
  "window": { "plan_window_days": null, "earliest_searchable": "1982-05-15", "clamped": false },
  "_sources": ["usaspending_fpds"]
}

The 999999999999.99 ceiling above is real FPDS data. A thin tail of vehicles carries placeholder ceilings (all-nines values, and GSA schedules recorded in the trillions). Those are the source's values and are passed through unchanged rather than rewritten. See Data quirks.

Single-vehicle response (a $7.73B Navy IDIQ plus its orders):

{
  "vehicle": {
    "award_id_piid": "N0003920D0054",
    "idv_type": "IDC",
    "prime_award_base_transaction_description": "SERVICE MANAGEMENT, INTEGRATION, AND TRANSPORT (SMIT)",
    "action_date": "2025-12-08",
    "ordering_period_end_date": "2028-08-04",
    "potential_total_value_of_award": 7729639286.25,
    "awarding_agency_name": "Department of Defense",
    "awarding_sub_agency_name": "Department of the Navy",
    "recipient_uei": "UE9QJD4KK1L6",
    "recipient_name": "LEIDOS, INC.",
    "naics_code": "541512",
    "multiple_or_single_award_idv": "SINGLE AWARD"
  },
  "transaction_rollup": {
    "transaction_count": 70,
    "max_ceiling": 7729639286.25,
    "first_action_date": "2020-02-05",
    "latest_action_date": "2025-12-08",
    "ordering_period_end_date": "2028-08-04",
    "distinct_awardees": 1
  },
  "orders_rollup": {
    "order_count": 16056,
    "distinct_orders": 5988,
    "total_obligated": 1257164494.67,
    "distinct_holders": 1,
    "first_order_date": "2024-10-01",
    "latest_order_date": "2026-02-27"
  },
  "_sources": ["usaspending_fpds"]
}

Read that as: the Navy's SMIT vehicle has a $7.73 billion ceiling, its ordering period runs to August 2028, and $1.26 billion has actually been obligated across 5,988 distinct orders.

Holders response (a single-award IDIQ, so one holder taking 100 percent):

{
  "piid": "N0003920D0054",
  "awardees": [
    {
      "recipient_uei": "UE9QJD4KK1L6",
      "recipient_name": "LEIDOS, INC.",
      "ceiling": 7729639286.25,
      "latest_action_date": "2025-12-08"
    }
  ],
  "awardee_count": 1,
  "earners": [
    {
      "recipient_uei": "UE9QJD4KK1L6",
      "recipient_name": "LEIDOS, INC.",
      "orders": 5988,
      "obligated": 1257164494.67,
      "share_pct": 100.0,
      "latest_order_date": "2026-02-27"
    }
  ],
  "earner_count": 1,
  "_sources": ["usaspending_fpds"]
}

On a multiple-award vehicle you would see several awardees and a share_pct distribution across the earners.

Data quirks worth knowing before integration

Same passthrough philosophy as the rest of our FPDS data: we do not sanitize source-data weirdness because the weirdness is sometimes the signal. Quirks that have surprised callers in testing:

The ceiling is potential_total_value_of_award, not the column that looks right. FPDS populates different columns on a vehicle than on an order. base_and_all_options_value is populated on only a minority of vehicles and current_total_value_of_award is never populated on one, so neither is a usable filter or sort key. A $7.7 billion Navy IDIQ reads 0.0 in base_and_all_options_value. ceiling_min / ceiling_max and the ceiling sort key all target potential_total_value_of_award.
The end date is ordering_period_end_date, not the period-of-performance pair. The period_of_performance_*_end_date columns are not populated on vehicles, so rather than return them as columns that are always null, they are excluded from the default projection. The date that means "the ordering period closes" is ordering_period_end_date, and active_only=true reads it.
A thin tail carries placeholder ceilings. All-nines values like 999999999999.99, and GSA schedules recorded in the trillions, are real FPDS entries passed through unchanged. Do not treat a giant ceiling as a data error, and do not let one dominate a sum or an average without filtering it first.
One row per vehicle, not per transaction. A vehicle's base award and each of its modifications are separate rows in FPDS. Search collapses them: you get one row per vehicle carrying its latest transaction, and pagination.total counts distinct vehicles. This is the opposite of /contracts/search, where a transaction is the unit of interest because each one is a distinct obligation.
Vehicles are not date-windowed. A GWAC awarded in 2015 is still the thing you must hold in 2026, and its award date says nothing about its relevance. date_from / date_to filter action_date if you want them, but the full vehicle history is searchable on every plan, back to 1982. Use active_only=true to restrict to vehicles whose ordering period is still open.
orders_rollup covers FY2025 onward; the vehicle can predate it by decades. Orders are carried from FY2025 onward, so orders_rollup counts orders placed in that window while the vehicle record itself shows its full history in transaction_rollup. On an older vehicle, transaction_count reaches back years and orders_rollup reflects only recent activity. Do not read a low obligated total on a decades-old vehicle as "dormant" without accounting for the window.
share_pct can be null, and shares can sum to less than 100. share_pct is null when the vehicle's net obligation flow is zero or negative: FPDS records de-obligations as negative amounts, so a heavily de-obligated vehicle can produce a ratio outside 0 to 100, and rather than ship a nonsensical percentage it is reported as null. Separately, earners is ranked by dollars and capped by limit, so on a large vehicle the shares shown will sum to less than 100.

Three end-to-end recipes

Each recipe is the actual workflow for one of the three use cases in Why this matters. Copy-paste ready; the Python uses plain requests with no custom client.

Recipe 1: Who holds this vehicle, and who is actually winning

Capture planning, competitive field mapping. Two calls give you the ceiling, what has been obligated, the competed pool, and the ranked earners.

import requests
KEY  = "gca_…"
H    = {"Authorization": f"Bearer {KEY}"}
BASE = "https://govconapi.com/api/v1"

def who_holds_this(piid):
    v = requests.get(f"{BASE}/vehicles/{piid}", headers=H).json()
    h = requests.get(f"{BASE}/vehicles/{piid}/holders",
                     headers=H, params={"limit": 250}).json()
    return {
        "vehicle":          v["vehicle"]["prime_award_base_transaction_description"],
        "ceiling":          v["vehicle"]["potential_total_value_of_award"],
        "ordering_ends":    v["vehicle"]["ordering_period_end_date"],
        "obligated_so_far": v["orders_rollup"]["total_obligated"],
        "distinct_orders":  v["orders_rollup"]["distinct_orders"],
        "competed_pool":    h["awardee_count"],   # firms eligible to bid
        "actually_earning": h["earner_count"],    # firms that have won an order
        "top_earners":      h["earners"][:5],
    }

# >>> who_holds_this("N0003920D0054")  # Navy SMIT, Leidos
# {'vehicle': 'SERVICE MANAGEMENT, INTEGRATION, AND TRANSPORT (SMIT)',
#  'ceiling': 7729639286.25, 'ordering_ends': '2028-08-04',
#  'obligated_so_far': 1257164494.67, 'distinct_orders': 5988,
#  'competed_pool': 1, 'actually_earning': 1, …}

Single-award here, so the pool is one firm taking all of it. On a multiple-award IDIQ, competed_pool is the list of firms you would be bidding against on every order.

Recipe 2: Find the active vehicles in my lane

Pipeline building. Search by NAICS and type, restrict to open ordering periods, rank by ceiling so the vehicles worth positioning for surface first.

def vehicles_in_lane(naics, idv_type="IDC"):
    r = requests.get(f"{BASE}/vehicles/search", headers=H, params={
        "naics":      naics,
        "idv_type":   idv_type,
        "active_only": "true",                       # ordering period still open
        "sort_by":    "potential_total_value_of_award",
        "sort_order": "desc",
        "limit":      50,
    }).json()
    return [
        {"piid":    v["award_id_piid"],
         "holder":  v["recipient_name"],
         "ceiling": v["potential_total_value_of_award"],
         "ends":    v["ordering_period_end_date"]}
        for v in r["data"]
    ]

# vehicles_in_lane("541512")  → active IT-services IDIQs, largest ceiling first
# (swap idv_type="GWAC" or "FSS" to change the instrument)

Because vehicles are not date-windowed, this returns the standing set of instruments in the NAICS, not just the ones awarded recently. Drop active_only to include vehicles whose ordering period has closed.

Recipe 3: Ceiling vs. actually obligated (is this vehicle live)

A vehicle's ceiling is potential; the obligated total is what has flowed. The gap is a signal, but read it with the coverage window in mind.

def utilization(piid):
    v = requests.get(f"{BASE}/vehicles/{piid}", headers=H).json()
    ceiling   = v["vehicle"]["potential_total_value_of_award"]
    obligated = v["orders_rollup"]["total_obligated"]   # FY2025 orders onward
    return {
        "ceiling":       ceiling,
        "obligated":     obligated,
        "distinct_orders": v["orders_rollup"]["distinct_orders"],
        "ordering_ends": v["vehicle"]["ordering_period_end_date"],
        "note": "obligated counts FY2025 orders onward; the vehicle may predate that",
    }

# utilization("N0003920D0054")
#   ceiling=$7.73B  obligated=$1.26B  distinct_orders=5988  ordering_ends=2028-08-04

The obligated total counts orders from FY2025 onward, so on a vehicle awarded before then it understates lifetime spend. Treat it as "recent throughput," not "everything ever ordered."

Quick-reference cURL examples

The recipes above use Python for clarity. For one-shot lookups, the same calls in curl:

The largest GWAC by ceiling:

curl -H "Authorization: Bearer $KEY" \
  "https://govconapi.com/api/v1/vehicles/search?idv_type=GWAC&sort_by=potential_total_value_of_award&limit=1"

One vehicle plus its orders roll-up:

curl -H "Authorization: Bearer $KEY" \
  "https://govconapi.com/api/v1/vehicles/N0003920D0054"

Who holds a vehicle and who is earning on it (Pro):

curl -H "Authorization: Bearer $KEY" \
  "https://govconapi.com/api/v1/vehicles/N0003920D0054/holders?limit=3"

Active IT-services IDIQs in one NAICS:

curl -H "Authorization: Bearer $KEY" \
  "https://govconapi.com/api/v1/vehicles/search?naics=541512&idv_type=IDC&active_only=true"

Every vehicle a firm holds (by UEI):

curl -H "Authorization: Bearer $KEY" \
  "https://govconapi.com/api/v1/vehicles/search?uei=UE9QJD4KK1L6"

vs. USAspending and the paid tools

USAspending publishes the same FPDS source data this API serves. Its IDV records are there, but each vehicle's base award and every modification are separate rows, so you build the one-row-per-vehicle collapse yourself, and there is no "who holds this vehicle, and who is earning on it" cursor. Tango, HigherGov, and GovTribe carry contract-vehicle data behind their paid tiers; pricing on those products typically starts above $500/month with seat-based licensing.

Approach One row per vehicle Search by holder UEI Holders vs. earners split Full history (not date-windowed) Starting price
USAspending / raw FPDS No (base + every mod are separate rows) Manual No Yes (raw) Free
Tango / HigherGov / GovTribe Yes (web UI) Yes (web UI) Partial Varies $500+ / mo
GovCon API Yes (REST) Yes (REST) Yes (awardees + earners) Yes, back to 1982 Free (holders on Pro, $39 / mo)

The fit: developers and analysts who want vehicle data programmatically alongside the rest of the SAM and FPDS surface (opportunities, orders, entities, exclusions, vendor risk) without building the IDV collapse and the vehicle-to-orders join by hand. If you only need one bulk pull of raw FPDS and have a warehouse to load it into, USAspending is the right tool. If you are building a product that needs the standing set of vehicles, their holders, and their utilization at request time, this is the shortcut.

Pricing and access

Vehicle search (/vehicles/search) and single-vehicle detail (/vehicles/{piid}) are on every plan, including Free. They carry the ceiling (potential_total_value_of_award), the ordering period, the transaction roll-up, and the orders roll-up. The holders endpoint (/vehicles/{piid}/holders), which returns the competed pool of awardees and the ranked earners, is Pro ($39/month); Developer and Free keys receive a 402 on that route. Same key, same Authorization: Bearer header across the surface. Rate limit: 1,000 requests/hour per key plus a 60/minute burst cap per IP.

Vehicles are not date-windowed: the full history we carry is searchable on every plan, back to 1982. Orders roll-ups reflect orders placed from FY2025 onward, so a vehicle's own record can date back decades while its obligated total counts only recent throughput.

Subscribe →

FAQ

Where does this data come from?
FPDS, the Federal Procurement Data System, republished through USAspending.gov. Every response carries "_sources": ["usaspending_fpds"].

What counts as a contract vehicle here?
The five FPDS Indefinite Delivery Vehicle types: IDC (the IDIQ family, the largest group by far), FSS (GSA Federal Supply Schedules), BPA (Blanket Purchase Agreements), GWAC (Government-Wide Acquisition Contracts like SEWP and Alliant), and BOA (Basic Ordering Agreements). Filter with idv_type.

Why one row per vehicle instead of one per transaction?
A vehicle's base award and each modification are separate FPDS rows. Search collapses them so you get one row per vehicle carrying its latest transaction, and pagination.total counts distinct vehicles. Searching idv_type=GWAC returns the GWACs, not the thousands of modification records behind them. This is the opposite of /contracts/search, where each transaction is a distinct obligation and therefore the unit.

Which column is the ceiling?
potential_total_value_of_award. On a vehicle, base_and_all_options_value is populated on only a minority and current_total_value_of_award is never populated, so neither is a usable filter or sort key. ceiling_min / ceiling_max and the ceiling sort all target potential_total_value_of_award. See Data quirks.

Why isn't there a date window on vehicles?
Vehicles are long-lived. A GWAC awarded in 2015 is still the thing you must hold in 2026, and its award date says nothing about its relevance. The full vehicle history is searchable on every plan, back to 1982. Use active_only=true to restrict to vehicles whose ordering period is still open, and date_from / date_to to filter action_date if you want it.

What's the difference between awardees and earners?
awardees are the firms that hold the vehicle, an FPDS IDV record in their name. On a multiple-award IDIQ, that is the competed pool. earners are the firms that have actually been paid through it, ranked by dollars. They are reported separately because a firm can hold a vehicle for years and earn nothing on it; collapsing them would misstate the competitive picture.

Why is share_pct sometimes null?
FPDS records de-obligations as negative amounts, so a vehicle with heavy de-obligations can have a zero or negative net obligation flow. Rather than ship a nonsensical percentage, share_pct is reported as null in that case. Also note that earners is capped by limit and ranked by dollars, so on a large vehicle the shares shown sum to less than 100.

Do the orders roll-ups cover the vehicle's whole life?
No. Orders are carried from FY2025 onward, so orders_rollup and the earners obligations reflect that window. The vehicle's own record and its transaction_rollup can date back decades. On an older vehicle, treat the obligated total as recent throughput, not lifetime spend.

I have an order PIID, not a vehicle PIID. How do I find its vehicle?
Use /contracts/{piid}/vehicle, which resolves an order to the vehicle it rides. /vehicles/{piid} returns 404 if the PIID you pass is an order rather than a vehicle we carry.

Which parts are free and which are Pro?
/vehicles/search and /vehicles/{piid} are on every plan, including Free. /vehicles/{piid}/holders is Pro ($39/month); Developer and Free keys receive a 402. The vehicle record, its ceiling, its ordering period, and its orders roll-up are all available without Pro; the awardees-and-earners breakdown is the Pro layer.

Can I redistribute the data?
The underlying data is public-record federal data. The Pro license covers internal use and product integration. For redistribution as a commercial product, contact us for a custom arrangement.

Ready to integrate? Search and detail are free; Pro adds the holders block at $39/month. Or read the full API reference. Questions about fit: [email protected].