Skip to content

API Overview

The RFP Hub API is a RESTful JSON API built with Hono.

Base URL

https://rfp-hub-api.fly.dev/api/v1

Authentication

Most read endpoints are public — no authentication needed.

Write endpoints require an API key passed via the X-API-Key header. There are two auth levels:

Publisher (API Key)

Any publisher can create and update opportunities, bulk import, and submit data. Pass the key via X-API-Key:

curl -H 'X-API-Key: rfphub_...' \
  -X POST 'https://rfp-hub-api.fly.dev/api/v1/opportunities' \
  -H 'Content-Type: application/json' \
  -d '{ ... }'

Admin

Admin-only endpoints (publisher management, submission review, audit log) require an API key belonging to a publisher with admin privileges.

How API Keys Work

  1. An admin creates a publisher via POST /api/v1/publishers with a name and contact email.
  2. The response includes a one-time API key (format: rfphub_...). This is the only time the key is shown.
  3. The key is stored as a SHA-256 hash — it cannot be retrieved later, only rotated.

Community submissions (POST /api/v1/submit) do not require authentication.

Rate Limits

EndpointLimit
General API (/api/*)100 requests/minute
Feed endpoints (/feed/*)100 requests/minute
Community submissions10 requests/minute
Bulk import5 requests/minute
Admin endpoints (/api/v1/admin/*)100 requests/minute

Rate limits are per-IP, enforced with a Redis-backed sliding window. When exceeded, the API returns 429 Too Many Requests.

Pagination

All list endpoints support two pagination modes.

Offset Pagination

Simple page-based pagination — best for browsing and UIs.

ParameterDefaultDescription
page1Page number (1-indexed)
limit20Results per page (max 100)
curl 'https://rfp-hub-api.fly.dev/api/v1/opportunities?page=2&limit=10'

Cursor Pagination

Stable iteration over changing data — best for sync and automation. Use the nextCursor value from the previous response:

curl 'https://rfp-hub-api.fly.dev/api/v1/opportunities?cursor=018e...'

Both modes return a meta object:

{
  "data": [...],
  "meta": {
    "total": 150,
    "page": 1,
    "limit": 20,
    "hasMore": true,
    "nextCursor": "018e..."
  }
}

Conditional GET (ETag)

The search endpoint supports ETag and If-Modified-Since for cache validation:

# First request — note the ETag
curl -i 'https://rfp-hub-api.fly.dev/api/v1/opportunities'
# ETag: "a1b2c3d4e5f67890"
# Last-Modified: Thu, 13 Mar 2026 10:00:00 GMT
 
# Subsequent request — send ETag back
curl -H 'If-None-Match: "a1b2c3d4e5f67890"' \
  'https://rfp-hub-api.fly.dev/api/v1/opportunities'
# Returns 304 Not Modified (no body)

OpenAPI Spec

The auto-generated OpenAPI 3.1 spec is available at:

GET /api/v1/openapi

Endpoints

Public

MethodPathDescription
GET/healthHealth check
GET/api/v1/schemaJSON Schema definitions
GET/api/v1/openapiOpenAPI 3.1 spec
GET/api/v1/opportunitiesSearch & list opportunities
GET/api/v1/opportunities/:idGet opportunity by ID
POST/api/v1/submitCommunity submission
GET/api/v1/sourcesList funding sources
GET/api/v1/sources/:idGet funding source
GET/api/v1/exportBulk export (JSON/CSV)

Authenticated (API Key)

Write endpoints require an X-API-Key header. See the OpenAPI spec for the full authenticated and admin endpoint reference.

MethodPathDescription
POST/api/v1/opportunitiesCreate opportunity
PUT/api/v1/opportunities/:idUpdate opportunity