FIN API Layer Spec
Date: 2026-05-15
Status: Draft v0.1
Related: docs/architecture/FIN_MCP_SURFACE_ARCHITECTURE_2026-05-15.md,
docs/engineering/FIN_INTRANET_HUMAN_AGENT_PROJECTION_ENGINEERING_SPEC_2026-05-13.md,
docs/engineering/FIN_AGENTIC_HARNESS_SPEC_2026-05-15.md
0. Purpose
MCP serves agents. The API layer serves systems and UIs — customer portal calls, vendor portal calls, webhook ingest from external systems, embedded customer/vendor-facing endpoints, FSMA / GS1 / USDA integration endpoints.
Many endpoints have MCP equivalents. The implementations share code; the contracts and auth differ. This doc specifies the HTTP/webhook surface that is NOT MCP, and the open-standards adopters.
If API contracts diverge across docs, this doc is the source of truth.
1. Scope
- HTTP REST endpoints for UI and system callers
- Webhook receivers (Telnyx, email providers, GitHub, payment processors, ERP integrations)
- WebSocket / SSE for real-time UI streams
- Open-standards integration (GS1 GDSN, FSMA 204, USDA AMS Market News, eBL)
- API versioning, auth, rate limiting, idempotency, pagination, observability
Out of scope:
- MCP tool catalog (see MCP Surface Architecture)
- Internal service-to-service RPC (not externally exposed)
2. URL Layout
All API endpoints under /api/v1/ with prefix routing per audience:
| Path | Audience | Authentication |
|---|---|---|
/api/v1/internal/* | FIN ops, CI | mTLS + service-account JWT |
/api/v1/tenant/<tenant_id>/* | tenant Workspace UI, tenant integrations | tenant OAuth |
/api/v1/portal/customer/* | customer portal UI | customer OAuth |
/api/v1/portal/vendor/* | vendor portal UI | vendor OAuth |
/api/v1/public/* | public consumer site, anonymous fetches | optional API key |
/api/v1/federal/* | federal-mode dashboards | FedRAMP IdP |
/api/v1/webhooks/<provider> | external systems (Telnyx, GitHub, etc.) | signature verification |
URL versioning, not header versioning. Major-version bumps create
parallel routes (/api/v2/...); minor / patch bumps stay within v1.
3. REST Conventions
3.1 Resource Paths
- Collections:
/api/v1/tenant/<tenant_id>/lots/ - Items:
/api/v1/tenant/<tenant_id>/lots/<lot_id> - Sub-collections:
/api/v1/tenant/<tenant_id>/lots/<lot_id>/qc_events/ - Action endpoints (when REST verbs don’t fit):
POST /api/v1/tenant/<tenant_id>/lots/<lot_id>:hold
3.2 Methods
- GET (list / fetch)
- POST (create or trigger action; never PUT for create)
- PATCH (partial update; never PUT for partial)
- PUT (full replacement, rarely used)
- DELETE (only soft-delete, never destructive)
3.3 Query Parameters
Standard parameters across collection endpoints:
limit(default 50, max 200)cursor(opaque pagination cursor; never offset-based)filter[<field>]=<value>(typed filters)sort=<field>orsort=-<field>(negative prefix for descending)fields=<comma_separated>(sparse fieldset)include=<relation>(related resources to expand)
3.4 Response Envelope
{
"data": [...],
"meta": {
"next_cursor": "...",
"total_estimate": 12345
},
"links": {
"self": "...",
"next": "..."
}
}Item responses use the same envelope with data as an object instead of
an array.
3.5 Error Envelope
{
"error": {
"type": "validation_error",
"code": "lot_id_invalid",
"message": "Lot ID format is invalid",
"details": {...},
"request_id": "req_01H...",
"doc_url": "https://docs.fin.app/errors/lot_id_invalid"
}
}Error type taxonomy: validation_error, auth_error, permission_error,
not_found, conflict, rate_limit_exceeded, dependency_failure,
internal_error.
4. Authentication
4.1 Tenant OAuth
Standard OAuth 2.1 with PKCE for tenant users:
- Authorization endpoint:
/oauth/authorize - Token endpoint:
/oauth/token - Refresh: rotation enabled, 30-day expiry
- Access token: 1-hour expiry
- Scopes: per-resource, per-action (e.g.,
lots:read,lots:write,customers:read) - JWT claims:
tenant_id,actor_id,actor_role,tier,scopes[],exp,iat,jti
4.2 Customer / Vendor Portal OAuth
Portal users are external actors with limited scope:
- Login via email magic link OR SSO with tenant-configured providers
- Customer JWT scopes:
customer_self:read,customer_self:orders,customer_self:invoices,harvest_directory:read - Vendor JWT scopes:
vendor_self:read,vendor_self:lots,vendor_self:qc_outcomes,harvest_directory:read - Cross-customer / cross-vendor access strictly refused at route
4.3 Public Anonymous
Public endpoints accept anonymous calls with stricter rate limits. Authenticated public calls (with Tier 0 API key) get higher limits and quota-tracked usage.
4.4 Webhook Auth
Each webhook provider has signature verification:
- Telnyx: HMAC-SHA256 with shared secret
- GitHub: HMAC-SHA256 with webhook secret
- Email providers (Postmark / SendGrid / Mailgun): signature per provider
- Payment processors: provider-specific verification
- Custom partner webhooks: HMAC-SHA256 with per-partner secret
Failed signature verification returns 401, emits
webhook.signature_invalid event, never processes payload.
5. Idempotency
5.1 Idempotency Keys
Mutating endpoints accept Idempotency-Key: <client_supplied_uuid>
header. The server caches the response for 24 hours; same key returns
the same response.
Required on:
POST /api/v1/tenant/<>/lots/POST /api/v1/tenant/<>/orders/POST /api/v1/tenant/<>/payments/- Any endpoint triggering a billable LLM call
- Any endpoint that emits an event with external side effects
5.2 Conflict Detection
If a different request body comes in with the same idempotency key,
return 409 IdempotencyKeyConflict and emit
api.idempotency_conflict_detected.
6. Pagination
6.1 Cursor-Based Only
Every list endpoint uses cursor pagination. Offset pagination is explicitly disallowed (event stream + projection state makes offsets non-stable).
Cursors are opaque, base64-encoded JSON carrying:
{
"anchor": "knowledge_key_or_event_id",
"sort_field": "created_at",
"sort_dir": "desc",
"version": "v1"
}6.2 Stable Sort
All paginated endpoints have stable sort (typically (sort_field DESC, id DESC) as tiebreaker). Without stable sort, cursor pagination skips
or repeats records.
6.3 Default Page Size
50 records, max 200. Larger requests return 400 with the cap.
7. Webhooks
7.1 Inbound Webhook Providers
The harness receives inbound webhooks from:
- Telnyx: SMS receive, voice call events, message status updates
- Email providers: new email at
arrivals@,intel@, others - GitHub: vault commits (per Projection Spec §5.1)
- Stripe (or chosen processor): payment events, dispute events, subscription changes
- Customer / vendor system integrations: when tenants set up outbound webhooks to FIN
- Per-tenant custom: ERP system integrations (Famous, Produce Pro via scraping middleware, Silver Mountain via export drops)
7.2 Outbound Webhook (FIN → Customers)
Tenants and Tier 0 customers can subscribe to webhook deliveries from FIN:
lot.received→ tenant integrationrecall.initiated→ customer / vendor systemscommodity_situation.published→ Tier 0 subscribers
Delivery:
- HMAC-SHA256 signed
- At-least-once delivery with exponential backoff (10 retries over 24h)
- Subscriber acknowledges with 2xx; failures retry
- Subscriber webhook health surfaces in tenant dashboards
- Dead-letter after 24h, emit
webhook.outbound_dead_lettered
7.3 Webhook Schema Versioning
Outbound webhook payloads carry schema version. Subscribers pin a version. Major-version bumps fork the delivery (subscribers explicitly migrate).
8. Real-Time (WebSocket / SSE)
8.1 Live UI Streams
The Workspace UI uses WebSocket or SSE for:
- Lot status updates
- Agent message stream during active session
- Document index refresh notifications
- Channel health alerts
- Inbound message arrival (email, SMS, voice)
8.2 Connection Model
- WebSocket for bidirectional (agent chat)
- SSE for one-way push (status updates)
- Connection authenticated with same OAuth JWT
- Subscription scoped per actor, refreshed on token rotation
8.3 Backpressure
Server can throttle, drop oldest, or close per per-stream policy. Clients reconnect with last event ID to catch up.
9. Open Standards Integration
Adopt established standards where they exist. Don’t reinvent.
9.1 GS1 GDSN (Product Master Data)
- Inbound: tenants subscribed to GDSN feeds get product master data
ingested as
knowledge.domain = product - Outbound: tenant product catalog can publish to GDSN if tenant is a brand owner
- Mappings: GS1 attribute set → entity_profile schema
9.2 FSMA Section 204 Traceability
- Per-lot FSMA Key Data Elements (KDEs) projected from events
- Endpoint:
GET /api/v1/tenant/<tenant_id>/fsma/lots/<lot_id>returns the FSMA-compliant record - Endpoint:
POST /api/v1/tenant/<tenant_id>/fsma/filingsfor agency-initiated requests - Exports in FDA-specified format (CSV, JSON, or XML per the agency’s current spec)
- Real-time generation; no precomputation of records FDA might never request
9.3 USDA AMS Market News
- Inbound: USDA Market News commodity reports ingested as
knowledge.domain = external_market_reportrows for use in aggregate_intelligence projections - Outbound: Wave 5+ — FIN’s anonymized aggregates may feed back to AMS if a partnership develops
9.4 Electronic Bill of Lading (eBL)
- For freight events, eBL records ingested at receiving
- Conversion to
lot.receivedevents with provenance to the eBL - Outbound generation for shipments where tenant is shipper
9.5 GLN / GTIN Resolution
GET /api/v1/tenant/<tenant_id>/resolve/gln/<gln>→vendor_profileorcustomer_profiledocGET /api/v1/tenant/<tenant_id>/resolve/gtin/<gtin>→product_profileorcommodity_profiledoc- Public Tier 0 may resolve GTIN to public commodity profile (anonymized)
9.6 USDA Organic / GAP Certification Verification
- External verification via USDA Organic Integrity Database
- Cached results with
external_source_drift_monitorwatching for changes
10. Customer Portal Endpoints (Tier 2+)
The customer portal is a separate UI experience with its own scoped API:
GET /api/v1/portal/customer/orders/— customer’s ordersGET /api/v1/portal/customer/orders/<order_id>— single order detailGET /api/v1/portal/customer/invoices/— invoicesGET /api/v1/portal/customer/shipments/<shipment_id>/track— trackingPOST /api/v1/portal/customer/orders/— place new order (if tenant enables order entry through portal)POST /api/v1/portal/customer/communications— send message to tenantGET /api/v1/portal/customer/profile— customer’s own profile
All scoped to the authenticated customer. Cross-customer attempts return 404 (not 403, to avoid information leakage).
11. Vendor Portal Endpoints (Tier 2+)
Symmetric to customer portal:
GET /api/v1/portal/vendor/lots/— lots received from this vendorGET /api/v1/portal/vendor/qc_outcomes/— QC resultsGET /api/v1/portal/vendor/payments/— payment statusGET /api/v1/portal/vendor/profile— vendor’s own profilePOST /api/v1/portal/vendor/communications— message to tenant
12. Public Endpoints
GET /api/v1/public/commodities/— public commodity listGET /api/v1/public/commodities/<commodity_id>— commodity profileGET /api/v1/public/regions/<region_id>— region intelligenceGET /api/v1/public/situation/<commodity_id>/<year_week>— weekly situation briefGET /api/v1/public/recalls/— active recalls (subscribable via webhook)GET /api/v1/public/recalls/<recall_id>— recall detail
Tier 0 API key optional but recommended (higher rate limits).
13. Federal Endpoints
When federal mode is enabled:
GET /api/v1/federal/recall_traces/<lot_id>— federal-audience recall traceGET /api/v1/federal/fsma/lots/<lot_id>— FSMA-formatted recordPOST /api/v1/federal/inquiries— agency-initiated inquiry, returns inquiry_id, processed asynchronouslyGET /api/v1/federal/inquiries/<inquiry_id>— inquiry status + result
Federal endpoints on separate FedRAMP-authorized infrastructure. Distinct deployment from public/tenant.
14. Rate Limiting & Quota
14.1 Per-Route Limits
- Public anonymous: 60 req/min/IP
- Public with API key (Tier 0 free): 1000 req/min
- Tenant authenticated: 6000 req/min/tenant + 600 req/min/actor
- Internal: 60000 req/min/service-account
- Federal: per-contract negotiated
14.2 Burst Allowance
2x sustained rate for up to 60 seconds, then sustained applies.
14.3 Cost-Based Limits
Endpoints that trigger expensive backend operations (LLM calls, heavy joins, large exports) have additional per-actor cost budget enforcement.
15. Observability
15.1 Logging
Every request emits structured log with:
request_idrouteactor_id(when authenticated)tenant_id(when applicable)latency_msresponse_statuscost_cents(when applicable)
15.2 Tracing
OpenTelemetry trace propagation. Distributed traces span webhook → ingest → projection → tenant push.
15.3 Metrics
Prometheus-style metrics exposed at /internal/metrics (internal route
only). Standard four golden signals plus:
- per-tenant QPS
- per-route p50/p95/p99 latency
- webhook delivery success rate
- idempotency cache hit rate
16. Open Standards Roadmap
| Standard | Use case | Wave | Status |
|---|---|---|---|
| GS1 GDSN | Product master data sync | 4 | planned |
| FSMA 204 KDE | Federal traceability | 3 (per-tenant), 4 (federal) | planned |
| USDA AMS Market News | Market context | 4 | planned |
| GLN / GTIN | Identifier resolution | 3 | planned |
| eBL | Freight + shipping | 5 | research |
| ASN (Advance Ship Notice) | Receiving prep | 3 | planned |
| EDI 856 / 810 / 820 | Legacy retailer integration | 5 | research |
| OAuth 2.1 + PKCE | Auth | 1 | committed |
| OpenAPI 3.1 | Spec documentation | 2 | planned |
17. Tests
17.1 Unit Tests
- pagination cursor encode/decode
- idempotency-key collision detection
- webhook signature verification per provider
- OAuth token validation
- rate limiter buckets
17.2 Integration Tests
- end-to-end tenant OAuth flow
- customer portal order placement → tenant agent receives → fulfillment
- vendor portal QC outcome retrieval
- public commodity profile fetch by anonymous + by Tier 0 key
- webhook delivery + retry + dead-letter
- FSMA 204 record generation matches FDA-published schema
- cursor pagination stable across new records insertion
- idempotent retry returns cached response
17.3 Open Standards Tests
- GDSN payload roundtrip
- FSMA record schema validation against FDA spec
- USDA AMS feed ingest
18. Proof Gates
Wave 1 API is not complete until:
/api/v1/tenant/*routes with OAuth ship for the Nathel pilot- webhook ingest from Telnyx + GitHub + at least one email provider works end-to-end
- cursor pagination ships and is stable across insertions
- idempotency keys honored on all create endpoints
Wave 2 API is not complete until:
- customer portal + vendor portal endpoints ship for Nathel
- WebSocket / SSE streams active for Workspace UI
- per-tenant rate limiting active
Wave 3 API is not complete until:
- public endpoints deploy at
api.harvestdirectory.org - FSMA 204 record generation passes FDA-spec test
- outbound webhook subscriptions self-service
Wave 4 API is not complete until:
- federal-mode endpoints on FedRAMP infrastructure
- GDSN ingest + outbound
- USDA AMS ingest
- OpenAPI 3.1 spec generated and published
19. Stop Conditions
- OAuth flows can’t be made compliant with tenant-side IdP requirements → return to architecture
- FSMA record generation can’t match the FDA-published schema → defer federal-tenant offerings
- webhook delivery can’t sustain at-least-once guarantees → return to infra design
- public endpoint perf can’t sustain expected QPS without per-tenant isolation impact → return to capacity planning
20. Out Of Scope For v0.1
- GraphQL endpoint
- gRPC public surface
- bulk-import API (use scheduled jobs)
- A2A endpoint (handled by MCP, not API)
- legacy EDI dialect support