Federal contract opportunities API. Clean, queryable data from SAM.gov with daily updates.
Endpoints
Search federal contract notices with filters. Returns paginated results.
Basic filters (all plans)
naics – Single NAICS code (e.g. 541330)
psc – Product Service Code (e.g. D302)
state – State code (e.g. CA, TX) or full name (e.g. California)
keywords – Search in title, agency name, or solicitation number
notice_type – Filter by notice type (e.g. Solicitation, Award Notice, Presolicitation)
solicitation_number – Exact solicitation number (e.g. W52P1J-25-R-0001)
limit – Results per page (default 20, max: Free=50, Developer=1000)
offset – Pagination offset (default 0)
Important Data Patterns:
- Combined notices are very common - About a third of opportunities are "Combined Synopsis/Solicitation". When searching for bidding opportunities, filter for both
notice_type=Solicitation and notice_type=Combined%20Synopsis/Solicitation to catch everything.
- Award amounts only exist on awards - "Award Notice" records have award_amount, awardee_name, etc. Solicitations don't have these fields yet.
- Amendments create duplicate solicitation_numbers - Same solicitation can appear multiple times as it's modified. Use posted_date or notice_id to track versions.
- Most are for small business - Majority of set-asides are type "SBA".
Advanced filters (Developer plan)
agency – Agency name (partial match, e.g. defense)
set_aside – Set-aside type (e.g. SBA, 8(a), SDVOSB)
posted_after – Posted after date (YYYY-MM-DD format)
due_before – Response deadline before date (YYYY-MM-DD format)
value_min – Minimum award value in dollars (only for Award Notice records)
value_max – Maximum award value in dollars (only for Award Notice records)
location – Performance location (city or state)
competition_type – Competition type filter
date_from – Posted date from (YYYY-MM-DD format)
date_to – Posted date to (YYYY-MM-DD format)
sort_by – Sort field (posted_date, response_deadline, award_amount, title, agency)
sort_order – Sort order: asc or desc (default: desc)
naics_multiple – Comma-separated NAICS codes for multiple search
Response
{
"data": [
{
"notice_id": "abc123def456",
"title": "IT Services Contract",
"agency": "Department of Defense",
"posted_date": "2025-11-01",
"response_deadline": "2025-12-15",
"naics": ["541330"],
"set_aside_type": "Small Business",
"solicitation_number": "W52P1J-25-R-0001",
"description_text": "Full contract requirements...",
"award_amount": 1500000.00,
"awardee_name": "Tech Solutions Inc",
"contact_name": "John Smith",
"contact_email": "[email protected]",
"sam_url": "https://sam.gov/opp/...",
"performance_city_name": "Washington",
"performance_state_code": "DC"
// ... 59 total fields available
}
],
"pagination": {
"limit": 20,
"offset": 0,
"total": 646,
"has_next": true
},
"filters_applied": {
"naics": "541330",
"state": "CA",
"agency": null,
"keywords": null
}
}
Examples
Basic search (all plans):
curl -H "Authorization: Bearer YOUR_API_KEY" \
"https://govconapi.com/api/v1/opportunities/search?naics=541330&state=CA&limit=10"
NAICS 541330 (IT services) has 646 total opportunities nationwide
Paid plans - Agency filter:
curl -H "Authorization: Bearer YOUR_API_KEY" \
"https://govconapi.com/api/v1/opportunities/search?agency=defense&limit=20"
Department of Defense search returns 49,527 opportunities
Paid plans - High-value awards:
curl -H "Authorization: Bearer YOUR_API_KEY" \
"https://govconapi.com/api/v1/opportunities/search?notice_type=Award%20Notice&value_min=1000000&sort_by=award_amount"
Note: value_min only works with Award Notice records (completed contracts)
Paid plans - Multiple NAICS codes:
curl -H "Authorization: Bearer YOUR_API_KEY" \
"https://govconapi.com/api/v1/opportunities/search?naics_multiple=541330,541511&limit=10"
GET /demo/opportunities No Auth Required
Public demo endpoint that returns 2 sample contract notices. Perfect for testing integration without an API key.
Performance: Responses are cached for 6 hours since data updates daily at 6:00 AM UTC.
Response Format
{
"data": [
{
"notice_id": "abc123...",
"title": "Example Contract",
// ... 62 fields total
}
],
"total": 2,
"message": "Demo data - sign up for full API access"
}
Example
curl "https://govconapi.com/demo/opportunities"
GET /api/agency-crosswalk No Auth Required
Free agency name standardization utility. Returns mappings between agency variants and canonical names, with department hierarchy and USASpending codes.
Data: Generated fresh from current contract database. Includes 1,300+ agency mappings with confidence scores.
Response Format
{
"data": [
{
"raw_agency_text": "Dept. of Defense",
"canonical_agency": "Department of Defense",
"canonical_acronym": "DOD",
"department": "Department of Defense",
"department_acronym": "9700",
"fh_level": "sub_tier",
"usaspending_toptier_code": "097",
"confidence": 0.95,
"source": "federal_register",
"frequency": 1250,
"first_seen": "2025-08-07",
"last_seen": "2025-11-21"
}
],
"count": 1563,
"message": "Free agency name standardization utility - updated daily"
}
Example
curl "https://govconapi.com/api/agency-crosswalk"
GET /api/v1/opportunities/{notice_id} Requires Auth
Get complete details for a specific contract notice by its ID.
Response Format
{
"opportunity": {
"notice_id": "abc123def456",
"title": "IT Services Contract",
"agency": "Department of Defense",
// ... all 60+ opportunity fields
},
"has_raw_data": true,
"last_updated": "2025-11-22T02:06:35.259767+00:00"
}
Example
curl -H "Authorization: Bearer YOUR_API_KEY" \
"https://govconapi.com/api/v1/opportunities/abc123def456"
GET /api/v1/stats Requires Auth
Get comprehensive database statistics including total records, recent activity, unique agencies and NAICS codes.
Response
{
"database_stats": {
"total_opportunities": 62550,
"recent_opportunities": 6561,
"unique_agencies": 1563,
"unique_naics_codes": 849,
"last_updated": "2025-11-22T02:06:52.095708+00:00"
},
"data_freshness": {
"last_collection": "2025-11-07T06:15:23Z",
"collection_frequency": "Daily at 6:00 AM UTC",
"retention_period": "730 days"
}
}
Note: All values shown are examples. Use the endpoint to get current statistics.
Example
curl -H "Authorization: Bearer YOUR_API_KEY" \
"https://govconapi.com/api/v1/stats"
GET /api/stats No Auth Required
Public statistics endpoint for landing page. Returns real-time counts computed from database.
Response
{
"total_notices": 62550,
"currently_open": 8757,
"award_records": 17001,
"full_descriptions": 37452
}
Note: All values shown are examples. Use the endpoint to get current statistics.
Field Descriptions
total_notices - Total contract notices in database (all types, never archived)
currently_open - Notices still accepting bids (deadline > now, not yet awarded)
award_records - Notices with award information (winner, amount, date)
full_descriptions - Notices with complete scraped requirement text from SAM.gov
Example
curl "https://govconapi.com/api/stats"
GET /meta No Auth Required
Get metadata about the dataset including last collection time and total record count.
Response
{
"last_delta_utc": "2025-11-22T02:06:52.095708+00:00",
"records": 62550,
"description": "Federal contract opportunities from SAM.gov"
}
Example
curl "https://govconapi.com/meta"
Note: Use this endpoint to check data freshness. Collection runs daily at 6:00 AM UTC. The records value shown is an example - use the endpoint to get the current count.
GET /api/v1/me Requires Auth
Get information about your API key including plan type and email address.
Response
Example
curl -H "Authorization: Bearer YOUR_API_KEY" \
"https://govconapi.com/api/v1/me"
GET /health No Auth Required
Health check endpoint showing system status and database connection.
Response
{
"status": "healthy",
"database": "connected",
"last_sync": "2025-11-07T06:15:23Z"
}
Example
curl "https://govconapi.com/health"
GET /export.csv Requires Auth Developer
Export contract notices as CSV file. Free plan does not have CSV export access.
Query Parameters
Supports the same filters as /opportunities/search, plus:
limit - Max records to export (default: 100, max: 1000)
Plan Restrictions:
- Developer: All filters available. 15 exports/day.
- Free: No CSV export access.
CSV Format
CSV files include these columns (58 total fields):
notice_id,title,agency,naics,psc,posted_date,response_deadline,set_aside_type,
award_amount,awardee_name,contact_name,contact_email,contact_phone,
solicitation_number,description_text,sam_url,archive_date,active,
award_number,award_date,award_uei_sam,award_cage_code,
performance_city_name,performance_state_code,performance_country_code,
organization_type,notice_base_type,source_version,last_seen...
Note: All 62 structured fields are included. Empty fields show as blank cells in CSV.
Examples
Export high-value awards:
curl -H "Authorization: Bearer YOUR_API_KEY" \
"https://govconapi.com/export.csv?notice_type=Award%20Notice&agency=defense&value_min=1000000&limit=1000" \
-o defense_awards.csv
Export by NAICS and state:
curl -H "Authorization: Bearer YOUR_API_KEY" \
"https://govconapi.com/export.csv?naics=541330&state=CA&limit=500" \
-o ca_it_contracts.csv
Free plan - Not available:
{"detail":"CSV export requires a paid plan. Upgrade at https://govconapi.com/#pricing"}
POST /api/v1/keys No Auth Required
Create a new API key. The key will be emailed to you instantly. Generating a new key deactivates the previous one for that email.
Query Parameters
email - Your email address (required)
plan - Plan type: "free" (default) or "developer"
Example
curl -X POST \
"https://govconapi.com/api/v1/keys?email=YOUR_EMAIL&plan=free"
Note: For the Developer plan, use the /api/v1/checkout endpoint to create a Stripe checkout session.
POST /api/v1/checkout No Auth Required
Create a Stripe checkout session for Developer plan subscription. Redirects to Stripe payment page.
Query Parameters
email - Your email address (required)
Response
{
"status": "success",
"checkout_url": "https://checkout.stripe.com/c/pay/cs_...",
"plan": "Developer Plan - $19/month",
"session_id": "cs_..."
}
Example
curl -X POST \
"https://govconapi.com/api/v1/checkout?email=YOUR_EMAIL"
Response Times (US West Coast)
- Search Queries: ~1.1s (measured average)
- Single Record: ~800ms
- Public Endpoints: ~600ms (cached)
- CSV Export: 1-2s (small datasets)
Technical Details
- Database: PostgreSQL 16 with connection pooling
- Platform: Railway (auto-scaling)
- Concurrent Requests: 5-15 recommended for best performance
- Data Updates: Daily at 6 AM UTC (99.9% uptime)
Best Practices for High-Volume Usage
Expect geographic variation in response times:
curl -w "Total: %{time_total}s (Network: ~%{time_connect}s + Server: ~%{time_starttransfer}s)\n" \
-H "Authorization: Bearer YOUR_API_KEY" \
"https://govconapi.com/api/v1/opportunities/search?limit=100"
Use pagination for large datasets:
curl -H "Authorization: Bearer YOUR_API_KEY" \
"https://govconapi.com/api/v1/opportunities/search?limit=100&offset=0"
Monitor your usage to stay within rate limits:
- Free plan: 25 requests/day
- Developer: 1,000 requests/hour
Use specific filters to reduce server processing time:
curl -H "Authorization: Bearer YOUR_API_KEY" \
"https://govconapi.com/api/v1/opportunities/search?naics=541330&posted_after=2025-11-01"
Note: NAICS filtering is well-optimized and faster than generic searches
Error Handling Best Practices
import requests
import time
def make_api_request(url, headers):
response = requests.get(url, headers=headers)
if response.status_code == 429:
# Rate limit exceeded
retry_after = int(response.headers.get('Retry-After', 3600))
print(f"Rate limited. Waiting {retry_after} seconds...")
time.sleep(retry_after)
return make_api_request(url, headers) # Retry
elif response.status_code == 403:
# Plan upgrade needed
error = response.json()
print(f"Upgrade required: {error['detail']}")
return None
return response.json()
Technical Architecture (Click to expand)
Rate Limiting Implementation
- Current Status: Rate limiting is not actively enforced but documented limits should be respected
- Plan Restrictions: Advanced filters and CSV export restrictions are properly enforced according to plan levels
- Future Implementation: Rate limiting with proper HTTP headers planned for future release
Database Performance
- Database: PostgreSQL 16 on Railway (managed)
- Connection Pool: 2-20 connections via psycopg2 ThreadedConnectionPool
- Query Optimization: Indexed on common search fields (agency, naics, dates)
- Performance Notes: NAICS filtering is well-optimized and 2x faster than generic searches
- Data Size: 112,000+ opportunities updated daily
Infrastructure
- Platform: Railway (Docker-based deployment)
- Server: FastAPI with uvicorn ASGI server
- Monitoring: Built-in health checks and error logging
- SSL/TLS: Automatic HTTPS with Railway managed certificates
Data Pipeline
- Source: SAM.gov API (daily collection at 6 AM UTC)
- Processing: Dual storage (raw JSON + normalized fields)
- Deduplication: SHA256 hash comparison to prevent duplicates
- Historical Tracking: Full version history for opportunities
Production Usage Guide
For High-Volume Applications
# Production-ready integration pattern
import requests
import time
from datetime import datetime, timedelta
class GovConAPIClient:
def __init__(self, api_key, plan='developer'):
self.api_key = api_key
self.base_url = 'https://govconapi.com/api/v1'
self.headers = {'Authorization': f'Bearer {api_key}'}
# Configure based on your plan
if plan == 'developer':
self.rate_limit = 1000 # hourly
self.burst_limit = 60 # per minute
else:
self.rate_limit = 25 # daily (free)
self.burst_limit = 10 # per minute
def search_opportunities(self, filters=None, limit=100):
"""Search with automatic pagination and rate limiting"""
all_results = []
offset = 0
while True:
params = {'limit': limit, 'offset': offset}
if filters:
params.update(filters)
response = self._make_request('/opportunities/search', params)
if not response or 'data' not in response:
break
opportunities = response['data']
all_results.extend(opportunities)
# Check if we got fewer results than requested (end of data)
if len(opportunities) < limit:
break
offset += limit
# Optional: respect rate limits proactively
time.sleep(0.1) # Small delay between requests
return all_results
def _make_request(self, endpoint, params=None):
"""Make request with proper error handling"""
url = f"{self.base_url}{endpoint}"
try:
response = requests.get(url, headers=self.headers, params=params)
# Monitor response for rate limiting
if response.status_code == 429:
print("Rate limited - waiting before retry...")
if response.status_code == 429:
retry_after = int(response.headers.get('Retry-After', 3600))
print(f"Rate limited. Waiting {retry_after} seconds...")
time.sleep(retry_after)
return self._make_request(endpoint, params)
response.raise_for_status()
return response.json()
except requests.exceptions.RequestException as e:
print(f"Request failed: {e}")
return None
# Example usage
client = GovConAPIClient('your_api_key_here', 'developer')
# Search for DoD opportunities in the last 30 days
filters = {
'agency_filter': 'Department of Defense',
'posted_after': (datetime.now() - timedelta(days=30)).strftime('%Y-%m-%d')
}
opportunities = client.search_opportunities(filters)
print(f"Found {len(opportunities)} opportunities")
Load Testing Recommendations
- Burst Testing: Developer plan: up to 60 req/min for short bursts
- Sustained Load: Stay under 80% of your hourly limit for consistent performance
- Concurrent Requests: 5-15 concurrent requests recommended for optimal reliability
- Response Caching: Cache responses for 10-15 minutes to reduce API calls
- Geographic Considerations: Network latency varies 50-400ms by location
- Performance Monitoring: Use curl timing options to distinguish network vs server performance
Enterprise Integration Patterns (Click to expand)
Pattern 1: Daily Sync with Incremental Updates
Sync new opportunities from the last 24 hours to keep your internal database up to date.
def daily_sync():
"""Sync new opportunities from the last 24 hours"""
yesterday = (datetime.now() - timedelta(days=1)).strftime('%Y-%m-%d')
client = GovConAPIClient('your_api_key')
new_ops = client.search_opportunities({
'posted_after': yesterday,
'limit': 1000 # Developer plan allows large pages
})
# Process and store in your system
for op in new_ops:
store_opportunity(op)
Pattern 2: Webhook Alternative (Agency Monitoring)
Poll for opportunities from specific agencies of interest and send notifications.
def monitor_agencies(agencies_of_interest):
"""Poll for opportunities from specific agencies"""
for agency in agencies_of_interest:
opportunities = client.search_opportunities({
'agency_filter': agency,
'posted_after': datetime.now().strftime('%Y-%m-%d'),
'limit': 100
})
for op in opportunities:
if is_new_opportunity(op):
send_notification(op)
time.sleep(1) # Small delay between agency checks
Pattern 3: Bulk Export for Analytics
Export large datasets for business intelligence using CSV for efficiency.
def export_for_analytics(date_range_days=90):
"""Export large datasets for business intelligence"""
start_date = (datetime.now() - timedelta(days=date_range_days)).strftime('%Y-%m-%d')
# Use CSV export for large datasets (more efficient)
csv_data = client._make_request('/opportunities/export', {
'posted_after': start_date,
'format': 'csv'
})
# Process CSV data for your analytics platform
return csv_data
Error Codes
| Code |
Description |
Common Causes |
| 400 |
Bad Request |
Invalid query parameters or malformed request |
| 401 |
Unauthorized |
Missing or invalid API key |
| 403 |
Forbidden |
Attempting to use advanced filters on Free plan |
| 404 |
Not Found |
Resource (notice_id) doesn't exist |
| 429 |
Too Many Requests |
Rate limit exceeded for your plan |
| 500 |
Internal Server Error |
Server-side error (contact support) |
Data Freshness & Transparency
Contract notices are collected daily at 6:00 AM UTC from SAM.gov using their official API. The collection includes:
- 48-hour lookback window to ensure completeness
- Never archived - all historical data is retained (730+ days)
- Full descriptions scraped from SAM.gov solicitation pages
- Award data linked when available
Check the /meta endpoint for the exact timestamp of the last collection run.
Important: We Keep ALL Records
We intentionally never archive or delete records. This means:
- Use
response_deadline to determine if opportunities are still open for bidding
- Use
notice_type to understand lifecycle (Solicitation → Award Notice)
- Don't use
active field - it's always "Yes" (meaningless SAM.gov value)
- Past-deadline opportunities remain queryable for competitive intelligence
Current data (Dec 2025): 88,115 total records | 7,625 still open for bidding | 56,665 past deadline | 23,825 awards/pre-solicitations
Tracking amendments: Same solicitation_number appears multiple times (original + amendments). Use notice_id for unique records, or group by solicitation_number to track changes.
Tested Integration Examples
1. Plan Verification Workflow
Step 1: Check your plan level
curl -H "Authorization: Bearer YOUR_API_KEY" \
"https://govconapi.com/api/v1/me"
Response: {"plan":"developer","email":"[email protected]"}
Step 2: Use features appropriate to your plan
Paid plans: All features available
curl -H "Authorization: Bearer YOUR_API_KEY" \
"https://govconapi.com/api/v1/opportunities/search?agency=defense¬ice_type=Award%20Notice&value_min=1000000"
Free plan: Basic filters only
curl -H "Authorization: Bearer YOUR_API_KEY" \
"https://govconapi.com/api/v1/opportunities/search?naics=541330&state=CA"
2. Error-Aware Search Implementation
Try advanced filter and handle graceful fallback:
curl -H "Authorization: Bearer YOUR_API_KEY" \
"https://govconapi.com/api/v1/opportunities/search?agency=defense" 2>/dev/null
If 403 error (Free plan), fallback to basic search:
if [ $? -ne 0 ]; then
echo "Free plan detected, using basic search..."
curl -H "Authorization: Bearer YOUR_API_KEY" \
"https://govconapi.com/api/v1/opportunities/search?keywords=defense"
fi
3. Pagination Best Practices
Efficient pagination for large datasets:
offset=0
limit=100
total_collected=0
while true; do
response=$(curl -s -H "Authorization: Bearer YOUR_API_KEY" \
"https://govconapi.com/api/v1/opportunities/search?limit=$limit&offset=$offset")
has_next=$(echo "$response" | jq -r '.pagination.has_next')
data_length=$(echo "$response" | jq '.data | length')
total_collected=$((total_collected + data_length))
echo "Collected $total_collected records..."
if [ "$has_next" != "true" ] || [ "$data_length" -eq 0 ]; then
break
fi
offset=$((offset + limit))
sleep 0.1
done
Note: Be respectful of the API with small delays between requests
API Validation & Testing
Validated December 2025: Plan restrictions are properly enforced. Advanced filters require a paid plan (Developer). Free plan has access to basic filters only (NAICS, PSC, state, keywords, notice_type, solicitation_number).
All endpoints and features have been comprehensively tested. Below are the validation results:
Verified Working Features
- All Public Endpoints: /health, /meta, /api/stats, /demo/opportunities, /api/agency-crosswalk
- Authentication System: Bearer token validation working correctly
- Plan Restrictions: Properly enforced - advanced filters require paid plan (Developer), CSV export requires paid plans
- Basic Search Filters: naics, psc, state, keywords, notice_type, solicitation_number (all plans)
- Advanced Filters: agency, value_min/max, date filters (Developer plan)
- Pagination: limit/offset working correctly with has_next indicator
- Individual Record Lookup: /api/v1/opportunities/{id} working
- CSV Export: Working for Developer plan
- Error Handling: Proper HTTP codes and messages
- POST Endpoints: API key creation and Stripe checkout working
Current Database Stats (Tested)
- Total Records: 62,550 contract opportunities
- Currently Open: 8,757 active opportunities
- Award Records: 17,001 opportunities with award details
- Full Descriptions: 37,452 opportunities with complete requirement text
- Agency Coverage: 1,563 unique agencies and sub-agencies
- NAICS Coverage: 849 unique industry codes
- Last Updated: 2025-11-22 (daily collection at 6 AM UTC)
Performance Benchmarks (Tested)
- Search Response: ~1.1s average (measured in production)
- Network Latency: ~20ms connection time
- Server Processing: ~980ms average
- CSV Export: 1-2s for small datasets
- Public Endpoints: 400-600ms (cached)
Federal Contracting Officers Directory
Need direct contact with government buyers? Our sister site contacts.govconapi.com provides verified contact information for 15,917+ federal contracting officers with phone numbers, emails, agency details, and recent procurement activity.
Need Help?
Questions or issues? Contact [email protected]
API Status: https://govconapi.com/health
Interactive Docs: https://govconapi.com/docs (auto-generated from OpenAPI schema)