Federal contract discovery platform. Searches SAM.gov and USASpending.gov APIs to find opportunities for government contractors.
| Source | Endpoint | Data |
|---|---|---|
| SAM.gov Entity API | api.sam.gov/entity-information/v3/entities |
Company registration, NAICS codes, UEI, business types |
| SAM.gov Opportunities API | api.sam.gov/opportunities/v2/search |
Active solicitations, presolicitations |
| USASpending.gov API | api.usaspending.gov/api/v2/search/spending_by_award |
Historical contract awards by recipient |
Anonymous search flow. Rate limited to 5 searches per IP per day.
| Route | Action | Description |
|---|---|---|
GET / |
index |
Landing page with search form |
POST /try/search_sam |
search_sam |
Search SAM.gov for entity by name |
POST /try/create_from_sam |
create_from_sam |
Create trial company from selected entity |
Dataflow:
User enters company name
→ SamGovService.search_entity(name)
→ Returns array of matching entities with NAICS codes
→ User selects entity
→ Company.create(sam_entity_data: entity, is_trial: true)
→ UsaSpendingService.find_past_contracts(name, cage_code)
→ Company.update(past_contracts: results)
→ Session stores company_id for subsequent requests
Authenticated user company management and search execution.
| Route | Action | Description |
|---|---|---|
POST /companies/search_sam |
search_sam |
Search SAM.gov entity API |
POST /companies/create_from_sam |
create_from_sam |
Create company from SAM entity |
POST /companies/:id/generate_profile |
generate_profile |
Generate AI profile from past contracts |
POST /companies/:id/generate_queries |
generate_queries |
Generate search queries from profile |
POST /companies/:id/execute_searches |
execute_searches |
Run queries against SAM.gov |
POST /companies/:id/score_all |
score_all |
AI score all opportunities YES/MAYBE/NO |
POST /companies/:id/email_opportunities |
email_opportunities |
Send opportunities via email |
POST /companies/:id/save_recurring_search |
save_recurring_search |
Enable weekly email digest |
Full Search Dataflow:
1. Entity Selection
POST /companies/search_sam {query: "Company Name"}
→ SamGovService.search_entity()
→ Returns: [{legalBusinessName, cageCode, ueiSAM, naicsCodes, businessTypes}]
2. Company Creation
POST /companies/create_from_sam {entity: {...}}
→ Company.create(sam_entity_data: entity)
→ LoadPastContractsJob.perform_later(company)
→ UsaSpendingService.find_past_contracts(name, cage_code)
→ Company.update(past_contracts: awards)
3. Profile Generation (optional)
POST /companies/:id/generate_profile
→ CompanyProfileService.generate_or_fetch_profile()
→ Uses Perplexity API to analyze past contracts
→ Company.update(ai_profile: {keywords, capabilities, target_agencies})
4. Query Generation
POST /companies/:id/generate_queries
→ AiQueryGenerator.build_queries()
→ Returns: [{keywords, naics, notice_types, days_back, rationale}]
5. Search Execution
POST /companies/:id/execute_searches {queries: [...]}
→ For each query (with 2s delay for rate limiting):
→ SamGovContractSearchService.search_opportunities_direct()
→ Dedup by solicitation_id
→ Save to contracts table
→ Returns: {opportunities_count, opportunities: [...]}
6. AI Scoring (optional)
POST /companies/:id/score_all
→ OpportunityScoringService.score_all_opportunities()
→ GPT-4o-mini scores each contract against company profile
→ Contract.update(ai_score: YES|MAYBE|NO, ai_reasoning: "...")
Individual contract actions.
| Route | Action | Description |
|---|---|---|
PATCH /contracts/:id/yes |
yes |
Mark contract as user interested |
PATCH /contracts/:id/no |
no |
Mark contract as not interested |
POST /contracts/:id/email |
email |
Email single contract |
DELETE /contracts/:id |
destroy |
Remove contract |
Public shareable company pages. No authentication required.
| Route | Action | Description |
|---|---|---|
GET /c/:token |
show |
Display public company page with YES/MAYBE contracts |
POST /c/:token/send_preview |
send_preview |
Email opportunities to visitor |
Entity registration lookup.
service = SamGovService.new
entities = service.search_entity("Company Name")
# Returns: [{legalBusinessName, cageCode, ueiSAM, naicsCodes, businessTypes, expirationDate}]Opportunities search with caching.
service = SamGovContractSearchService.new
results = service.search_opportunities_direct(
keywords: "software",
naics: "541511",
notice_types: ['p', 's'], # presol, special notice
days_back: 45,
company_name: "Test Co" # for cache key
)
# Returns: [{solicitation_id, title, agency, due_date, set_aside, url, type}]Notice Types:
p= Presolicitations= Special Noticek= Combined Synopsis/Solicitationo= Solicitationr= Sources Sought
Historical contract awards.
service = UsaSpendingService.new
awards = service.find_past_contracts("Company Name", "CAGE123")
# Returns: [{title, agency, award_amount, award_date, naics_code}]| Column | Type | Description |
|---|---|---|
sam_entity_data |
JSON | SAM.gov registration data |
past_contracts |
JSON | USASpending.gov awards |
ai_profile |
JSON | Generated company profile |
saved_queries |
JSON | Recurring search queries |
recurring_email |
string | Email for weekly digest |
is_trial |
boolean | Anonymous trial company |
is_public |
boolean | Publicly shareable |
share_token |
string | Public page URL token |
| Column | Type | Description |
|---|---|---|
solicitation_id |
string | SAM.gov notice ID |
title |
text | Opportunity title |
agency |
string | Federal agency (DOD, DHS, etc) |
due_date |
date | Response deadline |
set_aside |
string | Small business set-aside type |
url |
string | SAM.gov opportunity link |
contract_type |
string | Solicitation, Presol, etc |
ai_score |
string | YES, MAYBE, NO |
ai_reasoning |
text | GPT explanation |
raw_json |
JSON | Full SAM.gov response |
- Rate limit: ~4 requests before 429 error
- Required delay: 2-3 seconds between requests
- OR queries broken: "keyword1 OR keyword2" returns 0 results
- Use single keywords: "software" works, "software development" may not
titleparam filters title only,qparam searches title + description
SAM_GOV_API_KEY=... # Required - SAM.gov API key
OPENAI_API_KEY=... # Optional - AI scoring
PERPLEXITY_API_KEY=... # Optional - Profile generation
STRIPE_SECRET_KEY=... # Optional - Payments| Job | Trigger | Action |
|---|---|---|
LoadPastContractsJob |
Company creation | Fetch USASpending awards |
ContractScanJob |
Manual scan | Search SAM.gov opportunities |
AiContractSearchJob |
AI search | Full search + scoring pipeline |
SendOpportunityNotificationsJob |
New results | Email/Slack notifications |
RecurringSearchJob |
Weekly cron | Re-run saved queries |
# Anonymous
GET / → trial_search#index
POST /try/search_sam → trial_search#search_sam
POST /try/create_from_sam → trial_search#create_from_sam
# Authenticated
GET /companies → companies#index
POST /companies/search_sam → companies#search_sam
POST /companies/create_from_sam → companies#create_from_sam
POST /companies/:id/execute_searches → companies#execute_searches
POST /companies/:id/score_all → companies#score_all
# Public
GET /c/:token → public_companies#show
- Rails 8, SQLite, Solid Queue
- Kamal deployment
- Devise auth, Stripe payments
- Tailwind CSS