Launch position
The current `v1` routes cover read-only access for core entities and use the same pagination and sorting envelope across list endpoints. The main remaining boundary is documentation depth and compatibility rigor, not bearer-auth availability.
{
"version": "v1",
"workspaceId": "ws_123",
"meta": {
"page": 1,
"pageSize": 25
},
"data": []
}Public `v1` routes accept either normal Formula Keeper workspace authentication or a scoped bearer API key issued from workspace settings. Internal-only routes continue to use workspace auth or internal runner tokens as appropriate.
Current rate limits are `120` requests per minute for API keys and `180` requests per minute for workspace-session consumers.
API keys now support scope selection, revoke, and rotation. Each current `v1` resource family maps to one read-only scope so external consumers can stay narrow.
Use your normal Formula Keeper session cookie when calling the API from an authenticated workspace context.
fetch("/api/v1/raw-materials?page=1&pageSize=25", {
credentials: "include"
})Issue a scoped key from Settings -> API Keys, then send it as a bearer token.
curl -X GET "$FORMULA_KEEPER_URL/api/v1/formulas?page=1&pageSize=25" \ -H "Authorization: Bearer $FORMULA_KEEPER_API_KEY"
Current routes
These are real routes in the product today. The versioned `v1` surface is the public contract baseline; the internal routes remain app-supporting operational endpoints.
| Method | Path | Auth | Purpose |
|---|---|---|---|
| GET | /api/v1/raw-materials | Session or API key | Versioned raw-material list endpoint with shared pagination and sorting |
| GET | /api/v1/formulas | Session or API key | Versioned formula list endpoint with shared pagination and sorting |
| GET | /api/v1/batches | Session or API key | Versioned batch list endpoint with shared pagination and sorting |
| GET | /api/v1/vendors | Session or API key | Versioned vendor list endpoint with shared pagination and sorting |
| Method | Path | Auth | Purpose |
|---|---|---|---|
| GET | /api/health | None | Internal liveness and database connectivity check |
| POST | /api/internal/background-jobs/run-due | Bearer token | Internal background-job runner entry point |
| GET | /api/global-search | Workspace auth | Authenticated cross-record search inside the app shell |
| GET | /api/raw-materials/export | Workspace auth | CSV export for the raw-material workbench |
| GET | /api/settings/audit-log | Workspace auth | Filtered audit feed with entity and actor metadata |
Endpoint reference
The current API is intentionally read-only. These examples use realistic workspace payload shapes so teams can integrate without reverse-engineering the app UI.
Required scope: RAW_MATERIALS_READ
List request
/api/v1/raw-materials?q=bergamot&page=1&pageSize=25&sort=updatedAt&direction=desc
Detail request
/api/v1/raw-materials/rm_123
Example response
{
"requestId": "req_123",
"version": "v1",
"workspaceId": "ws_123",
"data": [
{
"id": "rm_123",
"name": "Bergamot FCF",
"code": "RM-104",
"supplier": "Aromatic Supply Co.",
"regulatoryStatus": "REVIEWED",
"unitLabel": "g",
"updatedAt": "2026-04-02T15:22:00.000Z"
}
],
"meta": {
"page": 1,
"pageSize": 25,
"totalCount": 1,
"totalPages": 1,
"hasNextPage": false,
"hasPreviousPage": false,
"query": "bergamot",
"sort": "updatedAt",
"direction": "desc"
}
}Required scope: FORMULAS_READ
List request
/api/v1/formulas?q=cologne&sort=version&direction=desc
Detail request
/api/v1/formulas/fo_123
Example response
{
"requestId": "req_123",
"version": "v1",
"workspaceId": "ws_123",
"data": {
"formula": {
"id": "fo_123",
"name": "Citrus Cologne",
"version": 3,
"revisionMinor": 1,
"status": "APPROVED"
},
"batchUsage": [],
"versionFamily": [],
"categoryColor": "#64748B"
}
}Required scope: BATCHES_READ
List request
/api/v1/batches?q=pilot&sort=updatedAt&direction=desc
Detail request
/api/v1/batches/ba_123
Example response
{
"requestId": "req_123",
"version": "v1",
"workspaceId": "ws_123",
"data": {
"batch": {
"id": "ba_123",
"name": "Pilot Batch 03",
"status": "QUALITY_CONTROL",
"formulaName": "Citrus Cologne",
"totalOutputGrams": 1200
}
}
}Required scope: VENDORS_READ
List request
/api/v1/vendors?q=aromatic&sort=name&direction=asc
Detail request
/api/v1/vendors/ve_123
Example response
{
"requestId": "req_123",
"version": "v1",
"workspaceId": "ws_123",
"data": {
"vendor": {
"id": "ve_123",
"name": "Aromatic Supply Co.",
"email": "purchasing@example.com",
"isActive": true,
"contacts": [],
"addresses": []
}
}
}Error model
| Code | HTTP | Meaning |
|---|---|---|
| UNAUTHORIZED | 401 | No valid workspace session or bearer API key was supplied. |
| FORBIDDEN | 403 | The current role or API key scope does not have access to the requested route. |
| NOT_FOUND | 404 | The requested record does not exist in the active workspace. |
| INVALID_SORT_FIELD / INVALID_PAGE / INVALID_PAGE_SIZE | 400 | The list query parameters do not match the documented v1 contract. |
| RATE_LIMITED | 429 | The current API key or workspace session exceeded the current per-minute request limit. |
| INTERNAL_ERROR | 500 | The route failed unexpectedly. Use the returned request ID when investigating logs. |
Retry guidance
Roadmap
Related docs