Browse documentation
Docs
FeatureFactory API
v1
The FeatureFactory public API. Base URL: https://featurefactory.com/api/v1 All endpoints require a Bearer API key (ff_...). Every key carries a set of ability scopes; endpoints that require a scope return 403 when the key lacks it. The /me endpoint requires no specific ability beyond a valid key. FeatureFactory currently exposes the production API only; there is no sandbox endpoint or separate test-key namespace. Rate limits: POST /signals uses the signals-ingest bucket (600 rpm). Work order runner endpoints use the runner bucket (600 rpm). All other endpoints use the general api bucket (60 rpm). Exceeded limits return 429 with a Retry-After header. Error responses use RFC 7807 Problem+JSON (Content-Type: application/problem+json). All error bodies include type, title, status, and detail fields. Validation errors also include an errors map.
Authentication
Every request requires a FeatureFactory API key sent as an HTTP Bearer token. Keys use the ff_ prefix. The API currently exposes a production environment only; there is no separate sandbox or test-key namespace.
Create and manage keys in Settings › API. Each key carries a set of ability scopes. Endpoints return 403 when the key lacks the required ability.
Ability scopes
| Ability | Grants access to |
|---|---|
measure:read | Measure - ReadRead pull request and commit activity, line changes, velocity metrics, and quality analysis. |
plan:read | Plan - ReadRead access to planning insights, themes, and plans. |
plan:write | Plan - WriteCreate and update planning insights, themes, and plans. |
intel:read | Intel - ReadRead access to intel sources and enriched signal data. |
intel:write | Intel - WriteIngest raw intel events and update intel sources. |
build:read | Build - ReadRead access to work orders and build pipeline state. |
build:write | Build - WriteCreate and update work orders and build pipeline jobs. |
triage:read | Triage - ReadRead access to triage queues and routing rules. |
triage:write | Triage - WriteCreate and update triage items and routing decisions. |
knowledge:read | Knowledge - ReadANN vector search and read access to knowledge pages. |
outcomes:read | Outcomes - ReadRead access to outcome targets, actuals, and trends. |
usage:read | Usage - ReadRead access to API usage, token consumption, and cost data. |
signals:write | Signals - WriteIngest signals from external systems into FeatureFactory. |
signals:read | Signals - ReadCursor-paginated read access to team signals. |
connectors:ingest | Connectors - IngestStart connector runs and submit source records, relationships, and tombstones. |
connectors:commands | Connectors - CommandsClaim and report results for outbound connector commands. |
connectors:health | Connectors - HealthReport remote connector health and permission status. |
work-orders:claim | Local Runner - ExecuteRegister a local runner, claim work, report progress, and submit results. |
work-orders:read | Local Runner - ReadInspect work that is waiting for an eligible runner. |
work-orders:write | Work Orders - WriteCreate custom work orders for connected repositories. |
Verify your key
curl "https://featurefactory.com/api/v1/me" \ -H "Authorization: Bearer ff_<your-key>"
Returns 200 with { organization_id, name, abilities }. A 401 means the key is missing or invalid.
Pagination
All list endpoints use cursor-based pagination. The default page size is 50 items. Cursors are opaque strings - do not parse or construct them.
| Field | Location | Description |
|---|---|---|
cursor | Query parameter | Pass the previous response's next_cursor to fetch the next page. |
meta.next_cursor | Response body | Opaque string for the next page. null means you are on the last page. |
meta.prev_cursor | Response body | Opaque string for the previous page. null on the first page. |
meta.per_page | Response body | Number of items per page (fixed at 50 for most endpoints). |
Response envelope
{
"data": [ ... ],
"meta": {
"next_cursor": "eyJpZCI6NX0",
"prev_cursor": null,
"per_page": 50
}
}Iterating all pages
cursor=""
while true; do
resp=$(curl -s "https://featurefactory.com/api/v1/signals${cursor:+?cursor=$cursor}" \
-H "Authorization: Bearer $FF_KEY")
echo "$resp" | jq '.data[]'
cursor=$(echo "$resp" | jq -r '.meta.next_cursor // empty')
[ -z "$cursor" ] && break
doneRate limits
| Bucket | Limit | Applies to |
|---|---|---|
| api | 60 requests/minute | All endpoints except signals-ingest and runner routes. |
| signals-ingest | 600 requests/minute | POST /signals only. |
| runner | 600 requests/minute | Work order execution loop (claim, heartbeat, release, complete, context, notes). |
When a limit is exceeded the API returns 429 with Retry-After, X-RateLimit-Limit, and X-RateLimit-Remaining headers. Respect the Retry-After value before retrying.
Errors
All API errors use RFC 7807 Problem+JSON (Content-Type: application/problem+json). Every error body includes the following fields:
| Field | Type | Description |
|---|---|---|
type | string (URI) | Problem type URI. Format: https://featurefactory.com/problems/<slug> |
title | string | Short summary of the problem type. |
status | integer | HTTP status code (mirrors the response status). |
detail | string | Human-readable description of this specific occurrence. |
errors | object (422 only) | Field-level validation errors. Keys are field names, values are arrays of error strings. |
Common status codes
401UnauthenticatedMissing or invalid Bearer token.403ForbiddenKey is valid but lacks the required ability scope.404Not FoundResource does not exist or belongs to another team.409ConflictResource is not in the required state (e.g. work order not active).413Payload Too LargeRequest body exceeds the 256 KB limit.422Unprocessable EntityRequest body failed validation. Check the errors field.429Too Many RequestsRate limit exceeded. Retry after the Retry-After header value.500Internal Server ErrorUnexpected server-side failure.Example error response
{
"type": "https://featurefactory.com/problems/forbidden",
"title": "Forbidden",
"status": 403,
"detail": "This API key does not have the [measure:read] ability."
}Example validation error (422)
{
"type": "https://featurefactory.com/problems/validation-failed",
"title": "Validation Failed",
"status": 422,
"detail": "The external_id field is required.",
"errors": {
"external_id": ["The external_id field is required."]
}
}Auth
/me
Inspect current API key
Returns the identity associated with the Bearer token. Useful for verifying a key is valid and checking which abilities it carries. Required ability: none (any valid key).
Responses
200Key identity.401Missing or invalid API key.Example request
curl "https://featurefactory.com/api/v1/me" \ -H "Authorization: Bearer ff_<your-key>"
Signals
/signals
List signals
Requires signals:read
Cursor-paginated list of signals scoped to the authenticated team, ordered newest first (50 per page). Required ability: signals:read
Parameters
cursorqueryOpaque cursor from the previous page's next_cursor field.Responses
200Paginated signal list.401Missing or invalid API key.403The key does not carry the required ability scope.429Rate limit exceeded.Example request
curl "https://featurefactory.com/api/v1/signals" \ -H "Authorization: Bearer ff_<your-key>"
/signals
Ingest a signal
Requires signals:write
Ingest a signal idempotently. Deduplication key: (team_id, source_provider, external_id). - No existing record: creates and returns 201. - Record exists: updates mutable fields and returns 200. Rate limit: 600 rpm (signals-ingest bucket). Payload cap: 256 KB. Required ability: signals:write
Request body
application/json
| Field | Type | Required | Description |
|---|---|---|---|
source_provider | string | no | Origin system. Defaults to "api" when omitted. |
external_id | string | yes | Unique identifier in the source system. Required. |
title | string | null | no | Short human-readable label. |
body | string | null | no | Long-form description or raw event body. |
occurred_at | string | null | no | When the event happened in the source system (ISO 8601). |
payload | object | null | no | Arbitrary JSON object for additional structured data. |
Examples
GitHub pull request event
{
"source_provider": "github",
"external_id": "pr-8801",
"title": "Fix memory leak in cache driver",
"occurred_at": "2026-01-15T10:00:00Z",
"payload": {
"pr_number": 8801,
"merged_by": "alice"
}
}Minimal - only external_id required
{
"external_id": "evt-0042"
}Responses
200Signal updated (idempotent replay).201Signal created.401Missing or invalid API key.403The key does not carry the required ability scope.413Request body exceeds the 256 KB limit.422Validation error.429Rate limit exceeded.Example request
curl -X POST "https://featurefactory.com/api/v1/signals" \
-H "Authorization: Bearer ff_<your-key>" \
-H "Content-Type: application/json" \
-d '{"source_provider":"github","external_id":"pr-8801","title":"Fix memory leak in cache driver","occurred_at":"2026-01-15T10:00:00Z","payload":{"pr_number":8801,"merged_by":"alice"}}'/inbound/{integration}
Inbound webhook receiver
Receives inbound webhook events from external providers via a team integration. Persists the event and dispatches async processing. Two authentication paths are supported: - API key with signals:write ability (HMAC check skipped). - Unauthenticated callers with a valid X-Hub-Signature-256 header computed against the integration's webhook_secret. Returns 202 Accepted on success or idempotent replay.
Parameters
integrationpathrequiredIntegration UUID.X-Hub-Signature-256headerHMAC-SHA256 signature for unauthenticated callers.X-Delivery-IdheaderUnique delivery identifier for idempotency.X-Event-TypeheaderEvent type label from the originating provider.Request body
application/json
Responses
202Event accepted for processing.401Missing or invalid authentication.404Resource not found.429Rate limit exceeded.Example request
curl -X POST "https://featurefactory.com/api/v1/inbound/<id>" \ -H "Authorization: Bearer ff_<your-key>" \ -H "Content-Type: application/json"
Measure
/repositories
List repositories
Requires measure:read
Cursor-paginated list of repositories for the authenticated team, ordered newest first (50 per page). Required ability: measure:read
Parameters
cursorqueryOpaque cursor from the previous page's next_cursor field.Responses
200Paginated repository list.401Missing or invalid API key.403The key does not carry the required ability scope.429Rate limit exceeded.Example request
curl "https://featurefactory.com/api/v1/repositories" \ -H "Authorization: Bearer ff_<your-key>"
/pull-requests
List pull requests
Requires measure:read
Cursor-paginated pull requests for the authenticated team, ordered by most recently merged or updated (50 per page). Required ability: measure:read
Parameters
cursorqueryOpaque cursor from the previous page's next_cursor field.repository_idqueryFilter by repository ID.statusqueryFilter by status: open | merged | closedoriginqueryFilter by origin: human | agent_local | agent_cloudResponses
200Paginated pull request list.401Missing or invalid API key.403The key does not carry the required ability scope.429Rate limit exceeded.Example request
curl "https://featurefactory.com/api/v1/pull-requests" \ -H "Authorization: Bearer ff_<your-key>"
/pull-requests/{id}
Get pull request
Requires measure:read
Full detail for a single pull request including analysis scores and AI attribution evidence. Required ability: measure:read
Parameters
idpathrequiredPull request ID.Responses
200Pull request detail.401Missing or invalid API key.403The key does not carry the required ability scope.404Resource not found.429Rate limit exceeded.Example request
curl "https://featurefactory.com/api/v1/pull-requests/<id>" \ -H "Authorization: Bearer ff_<your-key>"
/metrics/series
Get daily metrics series
Requires measure:read
Daily metric rollup data for charting. Returns one data point per day within the requested period. Required ability: measure:read
Parameters
periodqueryRolling window in days: 7 | 30 | 90. Default: 30.scopequeryAggregation scope: team | repo | person. Default: team.originqueryFilter by origin: human | agent. Omit for combined.Responses
200Metric series data.401Missing or invalid API key.403The key does not carry the required ability scope.429Rate limit exceeded.Example request
curl "https://featurefactory.com/api/v1/metrics/series" \ -H "Authorization: Bearer ff_<your-key>"
/metrics/dora
Get DORA metrics
Requires measure:read
Aggregated DORA metrics over a rolling period. Required ability: measure:read
Parameters
periodqueryRolling window in days: 7 | 30 | 90. Default: 30.Responses
200DORA metrics.401Missing or invalid API key.403The key does not carry the required ability scope.429Rate limit exceeded.Example request
curl "https://featurefactory.com/api/v1/metrics/dora" \ -H "Authorization: Bearer ff_<your-key>"
Plan
/insights
List insights
Requires plan:read
Cursor-paginated list of insights for the authenticated team, ordered newest first (50 per page). Required ability: plan:read
Parameters
cursorqueryOpaque cursor from the previous page's next_cursor field.typequeryFilter by type: bug | request | friction | praise | insightstatusqueryFilter by status: pending_review | approved | dismissed | duplicatetheme_idqueryFilter by theme ID.Responses
200Paginated insight list.401Missing or invalid API key.403The key does not carry the required ability scope.429Rate limit exceeded.Example request
curl "https://featurefactory.com/api/v1/insights" \ -H "Authorization: Bearer ff_<your-key>"
/themes
List themes
Requires plan:read
Cursor-paginated list of themes for the authenticated team, ordered by insight_score descending then id descending (50 per page). Required ability: plan:read
Parameters
cursorqueryOpaque cursor from the previous page's next_cursor field.statusqueryFilter by status: active | in_progress | archivedResponses
200Paginated theme list.401Missing or invalid API key.403The key does not carry the required ability scope.429Rate limit exceeded.Example request
curl "https://featurefactory.com/api/v1/themes" \ -H "Authorization: Bearer ff_<your-key>"
/themes/{id}
Get theme
Requires plan:read
Fetch a single theme with up to 20 most recent evidence insights. Required ability: plan:read
Parameters
idpathrequiredTheme ID.Responses
200Theme detail.401Missing or invalid API key.403The key does not carry the required ability scope.404Resource not found.429Rate limit exceeded.Example request
curl "https://featurefactory.com/api/v1/themes/<id>" \ -H "Authorization: Bearer ff_<your-key>"
/themes/{id}/review
Approve or dismiss a theme
Requires plan:write
Approve (transitions to in_progress) or dismiss (transitions to archived) a theme. Required ability: plan:write
Parameters
idpathrequiredTheme ID.Request body
application/json
| Field | Type | Required | Description |
|---|---|---|---|
action | string | yes | approve | dismiss |
Examples
Approve the theme
{
"action": "approve"
}Dismiss the theme
{
"action": "dismiss"
}Responses
200Updated theme status.401Missing or invalid API key.403The key does not carry the required ability scope.404Resource not found.422Validation error.429Rate limit exceeded.Example request
curl -X POST "https://featurefactory.com/api/v1/themes/<id>/review" \
-H "Authorization: Bearer ff_<your-key>" \
-H "Content-Type: application/json" \
-d '{"action":"approve"}'/prds
List PRDs
Requires plan:read
Cursor-paginated list of root PRDs for the authenticated team, ordered newest first (50 per page). Required ability: plan:read
Parameters
cursorqueryOpaque cursor from the previous page's next_cursor field.lifecyclequeryFilter by lifecycle: draft | review | approved | archivedResponses
200Paginated PRD list.401Missing or invalid API key.403The key does not carry the required ability scope.429Rate limit exceeded.Example request
curl "https://featurefactory.com/api/v1/prds" \ -H "Authorization: Bearer ff_<your-key>"
/prds
Create a PRD
Requires plan:write
Create a new PRD and dispatch AI generation. The PRD is linked to a theme. Generation is asynchronous; poll the list endpoint or check is_generating on the returned object. Required ability: plan:write
Request body
application/json
| Field | Type | Required | Description |
|---|---|---|---|
theme_id | integer | yes | The theme to base the PRD on. |
scope | string | no | Generation depth: full | outline | brief. Default: full. |
title | string | null | no | Override title (defaults to theme name). |
guidance | string | null | no | Additional instructions for the AI generator. |
Examples
Minimal PRD creation
{
"theme_id": 42
}PRD with custom scope and guidance
{
"theme_id": 42,
"scope": "outline",
"title": "Redesigned checkout flow",
"guidance": "Focus on mobile experience and reduce steps."
}Responses
201PRD created and generation dispatched.401Missing or invalid API key.403The key does not carry the required ability scope.404Resource not found.422Validation error.429Rate limit exceeded.Example request
curl -X POST "https://featurefactory.com/api/v1/prds" \
-H "Authorization: Bearer ff_<your-key>" \
-H "Content-Type: application/json" \
-d '{"theme_id":42}'/prds/{id}/export
Export PRD as Markdown
Requires plan:read
Download the completed PRD as a Markdown file attachment. Returns 422 when the PRD content is not yet available (generation still in progress). Required ability: plan:read
Parameters
idpathrequiredPRD ID.Responses
200Markdown file download.401Missing or invalid API key.403The key does not carry the required ability scope.404Resource not found.422PRD content not yet available.429Rate limit exceeded.Example request
curl "https://featurefactory.com/api/v1/prds/<id>/export" \ -H "Authorization: Bearer ff_<your-key>"
/search
Hybrid plan search
Requires plan:read
Hybrid search (vector ANN + keyword) over plan entities: insights, themes, work items, and PRDs. Results are ranked by similarity and scoped to the authenticated team. Required ability: plan:read
Parameters
qqueryrequiredFree-text search query (max 500 characters).types[]querySubset of entity types to search: insights | themes | work_items | prdsResponses
200Search results.401Missing or invalid API key.403The key does not carry the required ability scope.422Validation error.429Rate limit exceeded.Example request
curl "https://featurefactory.com/api/v1/search" \ -H "Authorization: Bearer ff_<your-key>"
Intel
/intel/accounts
List accounts
Requires intel:read
Cursor-paginated list of accounts for the authenticated team. Required ability: intel:read
Parameters
cursorqueryOpaque cursor from the previous page's next_cursor field.searchquerySearch by account name or domain.tier_idqueryFilter by tier ID.Responses
200Paginated account list.401Missing or invalid API key.403The key does not carry the required ability scope.429Rate limit exceeded.Example request
curl "https://featurefactory.com/api/v1/intel/accounts" \ -H "Authorization: Bearer ff_<your-key>"
/intel/accounts/{id}
Get account
Requires intel:read
Full account detail including health trend (last 10 points), recent activities (last 10), and recent conversations (last 10). Required ability: intel:read
Parameters
idpathrequiredAccount ID.Responses
200Account detail.401Missing or invalid API key.403The key does not carry the required ability scope.404Resource not found.429Rate limit exceeded.Example request
curl "https://featurefactory.com/api/v1/intel/accounts/<id>" \ -H "Authorization: Bearer ff_<your-key>"
/intel/accounts/{id}/tier
Update account tier
Requires intel:write
Set or clear the tier for an account. Pass tier_id as null to remove the existing tier assignment. Required ability: intel:write
Parameters
idpathrequiredAccount ID.Request body
application/json
| Field | Type | Required | Description |
|---|---|---|---|
tier_id | integer | null | yes | Tier ID to assign, or null to clear. |
Examples
Assign tier 2
{
"tier_id": 2
}Clear tier assignment
{
"tier_id": null
}Responses
200Account with updated tier.401Missing or invalid API key.403The key does not carry the required ability scope.404Resource not found.422Validation error.429Rate limit exceeded.Example request
curl -X PATCH "https://featurefactory.com/api/v1/intel/accounts/<id>/tier" \
-H "Authorization: Bearer ff_<your-key>" \
-H "Content-Type: application/json" \
-d '{"tier_id":2}'Build
/build-runs
List build runs
Requires build:read
List build runs for the authenticated team. Required ability: build:read
Parameters
statusqueryFilter by status: pending | running | complete | failed | cancelledResponses
200Build run list.401Missing or invalid API key.403The key does not carry the required ability scope.429Rate limit exceeded.Example request
curl "https://featurefactory.com/api/v1/build-runs" \ -H "Authorization: Bearer ff_<your-key>"
/build-runs/{id}
Get build run
Requires build:read
Get a single build run with its steps. Required ability: build:read
Parameters
idpathrequiredBuild run ID.Responses
200Build run detail.401Missing or invalid API key.403The key does not carry the required ability scope.404Resource not found.429Rate limit exceeded.Example request
curl "https://featurefactory.com/api/v1/build-runs/<id>" \ -H "Authorization: Bearer ff_<your-key>"
/build-runs/{id}/steps/{step}/approve
Approve a build step
Requires build:write
Approve a step that is awaiting human review, allowing the build run to advance to the next step. Required ability: build:write
Parameters
idpathrequiredBuild run ID.steppathrequiredStep sequence number.Request body
application/json
| Field | Type | Required | Description |
|---|---|---|---|
user_id | integer | yes | ID of the user approving the step. |
Responses
200Step approved.401Missing or invalid API key.403The key does not carry the required ability scope.404Resource not found.422Validation error.429Rate limit exceeded.Example request
curl -X POST "https://featurefactory.com/api/v1/build-runs/<id>/steps/<id>/approve" \ -H "Authorization: Bearer ff_<your-key>" \ -H "Content-Type: application/json"
/build-runs/{id}/steps/{step}/reject
Reject a build step
Requires build:write
Reject a step that is awaiting human review, stopping the build run at that point. Required ability: build:write
Parameters
idpathrequiredBuild run ID.steppathrequiredStep sequence number.Request body
application/json
| Field | Type | Required | Description |
|---|---|---|---|
user_id | integer | yes | ID of the user rejecting the step. |
note | string | null | no | Optional rejection note. |
Responses
200Step rejected.401Missing or invalid API key.403The key does not carry the required ability scope.404Resource not found.422Validation error.429Rate limit exceeded.Example request
curl -X POST "https://featurefactory.com/api/v1/build-runs/<id>/steps/<id>/reject" \ -H "Authorization: Bearer ff_<your-key>" \ -H "Content-Type: application/json"
/build-runs/{id}/approve
Approve a build run
Requires build:write
Shorthand approval that finds the current awaiting-approval step for the given build run and approves it. Equivalent to calling the per-step approve endpoint when the step number is unknown. Required ability: build:write
Parameters
idpathrequiredBuild run ID.Request body
application/json
| Field | Type | Required | Description |
|---|---|---|---|
user_id | integer | yes | ID of the user approving the step. |
Responses
200Step approved.401Missing or invalid API key.403The key does not carry the required ability scope.404Resource not found.422Validation error.429Rate limit exceeded.Example request
curl -X POST "https://featurefactory.com/api/v1/build-runs/<id>/approve" \ -H "Authorization: Bearer ff_<your-key>" \ -H "Content-Type: application/json"
/runners
List runners
Requires build:read
List customer runners for the authenticated team. Platform runners are excluded. Required ability: build:read
Parameters
statusqueryFilter by status: online | offline | idleResponses
200Runner list.401Missing or invalid API key.403The key does not carry the required ability scope.429Rate limit exceeded.Example request
curl "https://featurefactory.com/api/v1/runners" \ -H "Authorization: Bearer ff_<your-key>"
Triage
/triages
List triages
Requires triage:read
List triage items for the authenticated team. Required ability: triage:read
Parameters
statusqueryFilter by status: pending | approved | dismissed | completed | failedResponses
200Triage list.401Missing or invalid API key.403The key does not carry the required ability scope.429Rate limit exceeded.Example request
curl "https://featurefactory.com/api/v1/triages" \ -H "Authorization: Bearer ff_<your-key>"
/triages/{id}
Get triage
Requires triage:read
Get a single triage item. Required ability: triage:read
Parameters
idpathrequiredTriage ID.Responses
200Triage detail.401Missing or invalid API key.403The key does not carry the required ability scope.404Resource not found.429Rate limit exceeded.Example request
curl "https://featurefactory.com/api/v1/triages/<id>" \ -H "Authorization: Bearer ff_<your-key>"
/triages/{id}/approve
Approve a triage
Requires triage:write
Approve a pending triage item, optionally attributing the approval to a specific user. Required ability: triage:write
Parameters
idpathrequiredTriage ID.Request body
application/json
| Field | Type | Required | Description |
|---|---|---|---|
user_id | integer | null | no | ID of the approving user (optional). |
Responses
200Triage approved.401Missing or invalid API key.403The key does not carry the required ability scope.404Resource not found.429Rate limit exceeded.Example request
curl -X POST "https://featurefactory.com/api/v1/triages/<id>/approve" \ -H "Authorization: Bearer ff_<your-key>" \ -H "Content-Type: application/json"
/triages/{id}/dismiss
Dismiss a triage
Requires triage:write
Dismiss a pending triage item. Required ability: triage:write
Parameters
idpathrequiredTriage ID.Responses
200Triage dismissed.401Missing or invalid API key.403The key does not carry the required ability scope.404Resource not found.429Rate limit exceeded.Example request
curl -X POST "https://featurefactory.com/api/v1/triages/<id>/dismiss" \ -H "Authorization: Bearer ff_<your-key>"
/triage-policies
List triage policies
Requires triage:read
List triage policies for the authenticated team. Required ability: triage:read
Responses
200Triage policy list.401Missing or invalid API key.403The key does not carry the required ability scope.429Rate limit exceeded.Example request
curl "https://featurefactory.com/api/v1/triage-policies" \ -H "Authorization: Bearer ff_<your-key>"
/triage-policies
Create triage policy
Requires triage:write
Create a new triage routing policy. Required ability: triage:write
Request body
application/json
| Field | Type | Required | Description |
|---|---|---|---|
integration_id | integer | null | no | Integration (Jira/Linear/etc.) to listen on. |
enabled | boolean | yes | Whether the policy is active. |
filters | object | null | no | Label/tag filter rules. |
repository_id | integer | null | no | Restrict fixes to a specific repository. |
lookback_days | integer | null | no | How many days of context to include when triaging. |
min_understanding_confidence | number | null | no | Minimum understanding confidence to auto-approve (0-1). |
min_solving_confidence | number | null | no | Minimum solving confidence to auto-approve (0-1). |
mode | string | null | no | auto = trigger immediately, manual = require human approval. |
daily_cap | integer | null | no | Max triages this policy may trigger per calendar day. |
per_triage_budget_usd | number | null | no | Max USD to spend per triage. |
Examples
Enable Jira policy in manual mode
{
"integration_id": 3,
"enabled": true,
"mode": "manual",
"daily_cap": 10,
"per_triage_budget_usd": 2
}Responses
201Triage policy created.401Missing or invalid API key.403The key does not carry the required ability scope.422Validation error.429Rate limit exceeded.Example request
curl -X POST "https://featurefactory.com/api/v1/triage-policies" \
-H "Authorization: Bearer ff_<your-key>" \
-H "Content-Type: application/json" \
-d '{"integration_id":3,"enabled":true,"mode":"manual","daily_cap":10,"per_triage_budget_usd":2}'/triage-policies/{id}
Get triage policy
Requires triage:read
Get a single triage policy. Required ability: triage:read
Parameters
idpathrequiredTriage policy ID.Responses
200Triage policy.401Missing or invalid API key.403The key does not carry the required ability scope.404Resource not found.429Rate limit exceeded.Example request
curl "https://featurefactory.com/api/v1/triage-policies/<id>" \ -H "Authorization: Bearer ff_<your-key>"
/triage-policies/{id}
Update triage policy
Requires triage:write
Replace all fields of a triage policy. Required ability: triage:write
Parameters
idpathrequiredTriage policy ID.Request body
application/json
| Field | Type | Required | Description |
|---|---|---|---|
integration_id | integer | null | no | |
enabled | boolean | yes | |
filters | object | null | no | |
repository_id | integer | null | no | |
lookback_days | integer | null | no | |
min_understanding_confidence | number | null | no | |
min_solving_confidence | number | null | no | |
mode | string | null | no | |
daily_cap | integer | null | no | |
per_triage_budget_usd | number | null | no |
Responses
200Updated triage policy.401Missing or invalid API key.403The key does not carry the required ability scope.404Resource not found.422Validation error.429Rate limit exceeded.Example request
curl -X PUT "https://featurefactory.com/api/v1/triage-policies/<id>" \ -H "Authorization: Bearer ff_<your-key>" \ -H "Content-Type: application/json"
Knowledge
/knowledge/search
Search knowledge base
Requires knowledge:read
ANN vector search over published knowledge pages for the authenticated team. Returns a synthesized context string suitable for LLM prompting. Required ability: knowledge:read
Parameters
queryqueryrequiredSearch query (max 500 characters).limitqueryMaximum number of pages to include in context (1-20, default 5).Responses
200Search result with synthesized context.401Missing or invalid API key.403The key does not carry the required ability scope.422Validation error.429Rate limit exceeded.Example request
curl "https://featurefactory.com/api/v1/knowledge/search" \ -H "Authorization: Bearer ff_<your-key>"
/knowledge/pages
List knowledge pages
Requires knowledge:read
List knowledge base pages for the authenticated team. Required ability: knowledge:read
Parameters
statusqueryFilter by status: draft | publishedResponses
200Knowledge page list.401Missing or invalid API key.403The key does not carry the required ability scope.429Rate limit exceeded.Example request
curl "https://featurefactory.com/api/v1/knowledge/pages" \ -H "Authorization: Bearer ff_<your-key>"
/knowledge/pages/{id}
Get knowledge page
Requires knowledge:read
Get a single knowledge base page. The embedding vector is omitted from the response. Required ability: knowledge:read
Parameters
idpathrequiredKnowledge page ID.Responses
200Knowledge page.401Missing or invalid API key.403The key does not carry the required ability scope.404Resource not found.429Rate limit exceeded.Example request
curl "https://featurefactory.com/api/v1/knowledge/pages/<id>" \ -H "Authorization: Bearer ff_<your-key>"
Outcomes
/outcomes/metrics
Get outcome metrics
Requires outcomes:read
High-level agent outcome metrics over a rolling period. Required ability: outcomes:read
Parameters
periodqueryRolling window in days: 7 | 30 | 90. Default: 30.Responses
200Outcome metrics.401Missing or invalid API key.403The key does not carry the required ability scope.429Rate limit exceeded.Example request
curl "https://featurefactory.com/api/v1/outcomes/metrics" \ -H "Authorization: Bearer ff_<your-key>"
/outcomes
List outcomes
Requires outcomes:read
List outcome records for the authenticated team. Required ability: outcomes:read
Parameters
statusqueryFilter by status: pending | met | partial | missedResponses
200Outcome list.401Missing or invalid API key.403The key does not carry the required ability scope.429Rate limit exceeded.Example request
curl "https://featurefactory.com/api/v1/outcomes" \ -H "Authorization: Bearer ff_<your-key>"
Usage
/usage
Get usage
Requires usage:read
Current month token consumption and cost data for the authenticated team, broken down by feature. Required ability: usage:read
Responses
200Usage summary.401Missing or invalid API key.403The key does not carry the required ability scope.429Rate limit exceeded.Example request
curl "https://featurefactory.com/api/v1/usage" \ -H "Authorization: Bearer ff_<your-key>"
Work Orders
/work-orders/pending
Peek pending work orders
Requires work-orders:read
Read-only peek at the queue. Lists orders this runner would be eligible to claim, without claiming any of them. Team runners see their own team's queued orders. Platform runners see all queued cloud-eligible orders. Required ability: work-orders:read Rate limit: runner bucket (600 rpm).
Parameters
typequeryFilter by work order type.repositoryqueryFilter by repository ID.pagequeryPage number (default 1).per_pagequeryItems per page (default 20, max 200).Responses
200Pending work orders with pagination metadata.401Missing or invalid API key.403The key does not carry the required ability scope.429Rate limit exceeded.Example request
curl "https://featurefactory.com/api/v1/work-orders/pending" \ -H "Authorization: Bearer ff_<your-key>"
/work-orders
Create a work order
Requires work-orders:write
Create a custom work order (type=custom) against one of the team's repositories. Requires the Build pillar (Business tier). Platform keys cannot create work orders. Required ability: work-orders:write Rate limit: runner bucket (600 rpm).
Request body
application/json
| Field | Type | Required | Description |
|---|---|---|---|
type | string | yes | Must be "custom". Other types are internal. |
repository | yes | Repository ID (integer) or full_name string (org/repo). | |
spec_md | string | yes | Markdown specification for the work order. |
executor | string | no | Preferred runner lane. Default: any. |
priority | integer | no | Priority 0-100 (higher = claimed sooner). Default 50. |
Examples
Create a custom work order
{
"type": "custom",
"repository": "acme/api-service",
"spec_md": "Add rate limiting to the /api/v1/users endpoint.",
"executor": "local",
"priority": 70
}Responses
201Work order created and queued.401Missing or invalid API key.403The key does not carry the required ability scope.422Validation error.429Rate limit exceeded.Example request
curl -X POST "https://featurefactory.com/api/v1/work-orders" \
-H "Authorization: Bearer ff_<your-key>" \
-H "Content-Type: application/json" \
-d '{"type":"custom","repository":"acme/api-service","spec_md":"Add rate limiting to the /api/v1/users endpoint.","executor":"local","priority":70}'/work-orders/claim
Claim a work order
Requires work-orders:claim
Claim the next eligible work order for the calling runner. Returns the claimed order payload (204 when nothing is available). Runner is identified via the X-Runner-Key header or runner_key body field. Required ability: work-orders:claim Rate limit: runner bucket (600 rpm).
Request body
application/json
| Field | Type | Required | Description |
|---|---|---|---|
runner_key | string | no | Runner key (alternative to X-Runner-Key header). |
types | array | no | Filter to specific work order types. |
work_order_id | integer | no | Claim this specific order (must be eligible). |
Responses
200Work order claimed.204No eligible work orders available.401Missing or invalid API key.403The key does not carry the required ability scope.429Rate limit exceeded.Example request
curl -X POST "https://featurefactory.com/api/v1/work-orders/claim" \ -H "Authorization: Bearer ff_<your-key>" \ -H "Content-Type: application/json"
/work-orders/{id}/heartbeat
Extend work order lease
Requires work-orders:claim
Extend the lease on an active work order and transition claimed -> running (recording started_at). Call every 30-60 seconds while executing. Required ability: work-orders:claim Rate limit: runner bucket (600 rpm).
Parameters
idpathrequiredWork order ID.Responses
200Lease extended.401Missing or invalid API key.403The key does not carry the required ability scope.404Resource not found.409Resource is not in the required state for this operation.429Rate limit exceeded.Example request
curl -X POST "https://featurefactory.com/api/v1/work-orders/<id>/heartbeat" \ -H "Authorization: Bearer ff_<your-key>"
/work-orders/{id}/release
Release a work order
Requires work-orders:claim
Voluntarily return a claimed/running order to the queue without consuming an attempt. Use when shutting down or running out of budget. Required ability: work-orders:claim Rate limit: runner bucket (600 rpm).
Parameters
idpathrequiredWork order ID.Responses
200Order released back to queue.401Missing or invalid API key.403The key does not carry the required ability scope.404Resource not found.409Resource is not in the required state for this operation.429Rate limit exceeded.Example request
curl -X POST "https://featurefactory.com/api/v1/work-orders/<id>/release" \ -H "Authorization: Bearer ff_<your-key>"
/work-orders/{id}/complete
Complete a work order
Requires work-orders:claim
Submit the terminal result of a work order run. - status=completed: validates the result against the order's output schema (when set). First schema failure returns 422 with the lease intact (one repair retry). Second failure fails the order. - status=failed: fails the order immediately. Re-queues while attempts remain. Required ability: work-orders:claim Rate limit: runner bucket (600 rpm).
Parameters
idpathrequiredWork order ID.Request body
application/json
| Field | Type | Required | Description |
|---|---|---|---|
runner_key | string | no | Runner key (alternative to X-Runner-Key header). |
status | string | yes | |
result | object | null | no | Structured result payload (validated against output_schema). |
error | string | null | no | Error message when status=failed. |
branch | string | null | no | Branch name created during execution. |
duration_ms | integer | null | no | Total execution time in milliseconds. |
input_tokens | integer | null | no | LLM input tokens consumed. |
output_tokens | integer | null | no | LLM output tokens consumed. |
model | string | null | no | Model identifier used for execution. |
Responses
200Completion accepted.401Missing or invalid API key.403The key does not carry the required ability scope.404Resource not found.409Resource is not in the required state for this operation.422Schema validation failure (first strike - repair and resubmit).429Rate limit exceeded.Example request
curl -X POST "https://featurefactory.com/api/v1/work-orders/<id>/complete" \ -H "Authorization: Bearer ff_<your-key>" \ -H "Content-Type: application/json"
/work-orders/{id}/context
Get work order context bundle
Resolve one context bundle for a work order. The runner pulls exactly the context it needs at execution time, one ref at a time. Only the runner currently owning the order may read context. Required ability: any valid key (ownership enforced in handler). Rate limit: runner bucket (600 rpm).
Parameters
idpathrequiredWork order ID.refqueryrequiredContext reference key to resolve.Responses
200Context bundle.401Missing or invalid API key.403The key does not carry the required ability scope.404Resource not found.429Rate limit exceeded.Example request
curl "https://featurefactory.com/api/v1/work-orders/<id>/context" \ -H "Authorization: Bearer ff_<your-key>"
/work-orders/{id}/notes
Add a work order note
Append a progress note to a work order's UI timeline. Only the runner currently owning the order may write notes. Required ability: any valid key (ownership enforced in handler). Rate limit: runner bucket (600 rpm).
Parameters
idpathrequiredWork order ID.Request body
application/json
| Field | Type | Required | Description |
|---|---|---|---|
runner_key | string | no | Runner key (alternative to X-Runner-Key header). |
note | string | yes | Progress note to append to the timeline. |
Responses
201Note created.401Missing or invalid API key.403The key does not carry the required ability scope.404Resource not found.422Validation error.429Rate limit exceeded.Example request
curl -X POST "https://featurefactory.com/api/v1/work-orders/<id>/notes" \ -H "Authorization: Bearer ff_<your-key>" \ -H "Content-Type: application/json"
Runners
/runners/register
Register a runner
Requires work-orders:claim
Register (or idempotently re-register) a runner for this team. Returns a runner_key that the runner uses to authenticate subsequent requests via the X-Runner-Key header. Team keys bind the runner to the key's team. Platform keys create platform-fleet runners with no team binding. Required ability: work-orders:claim
Request body
application/json
| Field | Type | Required | Description |
|---|---|---|---|
name | string | yes | Human-readable runner name. |
type | string | yes | Runner type. |
Examples
Register a local runner
{
"name": "my-machine",
"type": "local"
}Responses
200Runner registered (or re-registered). Returns runner_key.401Missing or invalid API key.403The key does not carry the required ability scope.422Validation error.429Rate limit exceeded.Example request
curl -X POST "https://featurefactory.com/api/v1/runners/register" \
-H "Authorization: Bearer ff_<your-key>" \
-H "Content-Type: application/json" \
-d '{"name":"my-machine","type":"local"}'/runner/instructions
Get runner instructions
Requires work-orders:claim
Return the polling decision for the runner: stop | wait | claim. Touches last_seen_at. Runner is identified via the X-Runner-Key header or runner_key query parameter. Required ability: work-orders:claim
Parameters
runner_keyqueryRunner key (alternative to X-Runner-Key header).Responses
200Polling instruction.401Missing or invalid API key.403The key does not carry the required ability scope.429Rate limit exceeded.Example request
curl "https://featurefactory.com/api/v1/runner/instructions" \ -H "Authorization: Bearer ff_<your-key>"
Connectors
/connectors/{integration}/runs
Start an idempotent connector sync run
Requires connectors:ingest
Parameters
integrationpathrequiredX-Idempotency-KeyheaderrequiredRequest body
application/json
| Field | Type | Required | Description |
|---|---|---|---|
resource | string | yes | |
entity_type | [ "string", "null" ] | no | |
mode | yes | ||
input | object | no |
Responses
200Existing idempotent run returned201Sync run started401Missing or invalid API key.403The key does not carry the required ability scope.422Validation errorExample request
curl -X POST "https://featurefactory.com/api/v1/connectors/<id>/runs" \ -H "Authorization: Bearer ff_<your-key>" \ -H "Content-Type: application/json"
/connectors/{integration}/runs/{run}
Inspect a connector sync run
Requires connectors:ingest
Parameters
integrationpathrequiredrunpathrequiredResponses
200Current sync run state and counters401Missing or invalid API key.403The key does not carry the required ability scope.404Resource not found.Example request
curl "https://featurefactory.com/api/v1/connectors/<id>/runs/<id>" \ -H "Authorization: Bearer ff_<your-key>"
/connectors/{integration}/runs/{run}/batches
Submit an idempotent source-record batch
Requires connectors:ingest
Limited to 500 records and 10 MB. Results identify accepted, duplicate, stale, tombstoned, and rejected records individually.
Parameters
integrationpathrequiredrunpathrequiredX-Idempotency-KeyheaderrequiredRequest body
application/json
Responses
200Per-record intake results413Batch exceeds 10 MB422Batch validation failedExample request
curl -X POST "https://featurefactory.com/api/v1/connectors/<id>/runs/<id>/batches" \ -H "Authorization: Bearer ff_<your-key>" \ -H "Content-Type: application/json"
/connectors/{integration}/runs/{run}/complete
Complete a run and atomically advance its stream cursor
Requires connectors:ingest
A successful full run also tombstones unseen entities. Failed or incomplete full runs never infer deletions.
Parameters
integrationpathrequiredrunpathrequiredX-Idempotency-KeyheaderrequiredRequest body
application/json
| Field | Type | Required | Description |
|---|---|---|---|
cursor | [ "string", "null" ] | no | |
result | object | no |
Responses
200Run completedExample request
curl -X POST "https://featurefactory.com/api/v1/connectors/<id>/runs/<id>/complete" \ -H "Authorization: Bearer ff_<your-key>" \ -H "Content-Type: application/json"
/connectors/{integration}/commands/claim
Claim pending outbound commands with row-level skip-locking
Requires connectors:commands
Parameters
integrationpathrequiredResponses
200Commands claimed for deliveryExample request
curl -X POST "https://featurefactory.com/api/v1/connectors/<id>/commands/claim" \ -H "Authorization: Bearer ff_<your-key>"
/connectors/{integration}/runs/{run}/fail
Mark a connector sync run failed
Requires connectors:ingest
Parameters
integrationpathrequiredrunpathrequiredX-Idempotency-KeyheaderrequiredRequest body
application/json
| Field | Type | Required | Description |
|---|---|---|---|
message | string | yes |
Responses
200Run failed idempotently409Run is already completedExample request
curl -X POST "https://featurefactory.com/api/v1/connectors/<id>/runs/<id>/fail" \ -H "Authorization: Bearer ff_<your-key>" \ -H "Content-Type: application/json"
/connectors/{integration}/runs/{run}/tombstones
Explicitly tombstone source records
Requires connectors:ingest
Parameters
integrationpathrequiredrunpathrequiredX-Idempotency-KeyheaderrequiredResponses
200Per-record tombstone results409Run is no longer accepting recordsExample request
curl -X POST "https://featurefactory.com/api/v1/connectors/<id>/runs/<id>/tombstones" \ -H "Authorization: Bearer ff_<your-key>"
/connectors/{integration}/commands/{command}/complete
Complete a claimed outbound command
Requires connectors:commands
Parameters
integrationpathrequiredcommandpathrequiredResponses
200Command completedExample request
curl -X POST "https://featurefactory.com/api/v1/connectors/<id>/commands/<id>/complete" \ -H "Authorization: Bearer ff_<your-key>"
/connectors/{integration}/commands/{command}/fail
Fail or mark a claimed outbound command ambiguous
Requires connectors:commands
Parameters
integrationpathrequiredcommandpathrequiredRequest body
application/json
| Field | Type | Required | Description |
|---|---|---|---|
message | string | yes | |
ambiguous | boolean | no |
Responses
200Command state recordedExample request
curl -X POST "https://featurefactory.com/api/v1/connectors/<id>/commands/<id>/fail" \ -H "Authorization: Bearer ff_<your-key>" \ -H "Content-Type: application/json"
/connectors/{integration}/health
Report remote connector health and permissions
Requires connectors:health
Parameters
integrationpathrequiredRequest body
application/json
| Field | Type | Required | Description |
|---|---|---|---|
status | yes | ||
message | [ "string", "null" ] | no | |
permissions | array | no |
Responses
200Health report acceptedExample request
curl -X POST "https://featurefactory.com/api/v1/connectors/<id>/health" \ -H "Authorization: Bearer ff_<your-key>" \ -H "Content-Type: application/json"
Was this article helpful?
Your response enters the same customer insight queue used by product feedback.