const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({workflow_id: '<string>', name: '<string>'})
};
fetch('https://api.factify.com/v1/plans', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));import requests
url = "https://api.factify.com/v1/plans"
payload = {
"workflow_id": "<string>",
"name": "<string>"
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.factify.com/v1/plans"
payload := strings.NewReader("{\n \"workflow_id\": \"<string>\",\n \"name\": \"<string>\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.factify.com/v1/plans")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"workflow_id\": \"<string>\",\n \"name\": \"<string>\"\n}")
.asString();curl --request POST \
--url https://api.factify.com/v1/plans \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"workflow_id": "<string>",
"name": "<string>"
}
'{
"id": "<string>",
"name": "<string>",
"workflow_id": "<string>",
"claim_slots": [
{
"rubric_id": "<string>",
"slot_name": "<string>",
"claim_id": "<string>",
"substitution_value": "<string>"
}
],
"submissions": [
{
"id": "<string>",
"plan_id": "<string>",
"filename": "<string>",
"mime_type": "<string>",
"added_at": "2023-11-07T05:31:56Z",
"record_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"params": {}
}
],
"created_at": "2023-11-07T05:31:56Z",
"open_conflicts_count": 1,
"total_conflicts_count": 1,
"locked_at": "2023-11-07T05:31:56Z",
"body_md_resolved": "<string>"
}{
"type": "about:blank",
"title": "Bad Request",
"status": 400,
"detail": "Request body is malformed or missing required fields."
}{
"type": "about:blank",
"title": "Unauthorized",
"status": 401,
"detail": "Missing or invalid authentication credentials."
}{
"type": "about:blank",
"title": "Not Found",
"status": 404,
"detail": "The requested resource does not exist or you do not have permission to view it."
}{
"type": "about:blank",
"title": "Conflict",
"status": 409,
"detail": "The request conflicts with the current state of the resource."
}{
"type": "about:blank",
"title": "Unprocessable Entity",
"status": 422,
"detail": "Request body failed validation.",
"errors": [
{
"location": "body.email",
"message": "expected string to match format 'email'",
"value": "not-an-email"
}
]
}{
"type": "about:blank",
"title": "Too Many Requests",
"status": 429,
"detail": "Rate limit exceeded. Retry after the period indicated by the Retry-After header."
}{
"type": "about:blank",
"title": "Internal Server Error",
"status": 500,
"detail": "An unexpected error occurred. Retry, and contact support if the issue persists."
}Open plan
Instantiate a new plan from a workflow.
const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({workflow_id: '<string>', name: '<string>'})
};
fetch('https://api.factify.com/v1/plans', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));import requests
url = "https://api.factify.com/v1/plans"
payload = {
"workflow_id": "<string>",
"name": "<string>"
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.factify.com/v1/plans"
payload := strings.NewReader("{\n \"workflow_id\": \"<string>\",\n \"name\": \"<string>\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.factify.com/v1/plans")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"workflow_id\": \"<string>\",\n \"name\": \"<string>\"\n}")
.asString();curl --request POST \
--url https://api.factify.com/v1/plans \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"workflow_id": "<string>",
"name": "<string>"
}
'{
"id": "<string>",
"name": "<string>",
"workflow_id": "<string>",
"claim_slots": [
{
"rubric_id": "<string>",
"slot_name": "<string>",
"claim_id": "<string>",
"substitution_value": "<string>"
}
],
"submissions": [
{
"id": "<string>",
"plan_id": "<string>",
"filename": "<string>",
"mime_type": "<string>",
"added_at": "2023-11-07T05:31:56Z",
"record_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"params": {}
}
],
"created_at": "2023-11-07T05:31:56Z",
"open_conflicts_count": 1,
"total_conflicts_count": 1,
"locked_at": "2023-11-07T05:31:56Z",
"body_md_resolved": "<string>"
}{
"type": "about:blank",
"title": "Bad Request",
"status": 400,
"detail": "Request body is malformed or missing required fields."
}{
"type": "about:blank",
"title": "Unauthorized",
"status": 401,
"detail": "Missing or invalid authentication credentials."
}{
"type": "about:blank",
"title": "Not Found",
"status": 404,
"detail": "The requested resource does not exist or you do not have permission to view it."
}{
"type": "about:blank",
"title": "Conflict",
"status": 409,
"detail": "The request conflicts with the current state of the resource."
}{
"type": "about:blank",
"title": "Unprocessable Entity",
"status": 422,
"detail": "Request body failed validation.",
"errors": [
{
"location": "body.email",
"message": "expected string to match format 'email'",
"value": "not-an-email"
}
]
}{
"type": "about:blank",
"title": "Too Many Requests",
"status": 429,
"detail": "Rate limit exceeded. Retry after the period indicated by the Retry-After header."
}{
"type": "about:blank",
"title": "Internal Server Error",
"status": 500,
"detail": "An unexpected error occurred. Retry, and contact support if the issue persists."
}Authorizations
Bearer authentication using a factapi-issued API key
(ffy_<env>_<base32_uuid><base62_random>). Cookie-based
sessions are accepted automatically by user-facing endpoints
but are not surfaced as an OpenAPI auth scheme.
Body
Response
Plan opened.
Plan TypeID (pln_…)
Human-readable case name.
Workflow TypeID.
Server-computed activity state, derived from submission states: IDLE — no submission is currently ingesting. PROCESSING — at least one submission is still ingesting.
Plan lock status is conveyed separately by locked_at
(null = unlocked, set = locked).
IDLE, PROCESSING One entry per rubric in the parent workflow, in creation order.
Show child attributes
Show child attributes
All submissions associated with this plan.
Show child attributes
Show child attributes
Number of claim_conflicts in open status whose new claim was
submitted to this plan. Drives the runs-list "N conflicts" badge
and the per-run conflict tab count without forcing the UI to
N+1 on /v1/conflicts per plan.
x >= 0Total number of claim_conflicts (open + accepted + rejected) whose new claim was submitted to this plan. Backs resolved-since-detection telemetry and the "0/3 unresolved" style breakdown.
x >= 0When the plan was locked. Null for unlocked plans.
Workflow body_md with claim values substituted. Null until the plan is locked.