Stateless document conversion API
Genflows API
Convert procedure PDFs and images to V1 - VLM JSON or V2 - CaptureGraph DAG. Provider keys are request-only and are not stored.
Interactive Request
Choose a version and provider. Upload a PDF or image, or use the bundled sample.
Request Examples
Response
No request sent yet.
API Reference
Shared request fields apply to both conversion versions.
JSON Request Shape
{
"provider": "openai",
"api_key": "$OPENAI_API_KEY",
"filename": "document.pdf",
"mime_type": "application/pdf",
"document_base64": "<base64 file bytes>",
"prompt": "Extract procedural steps."
}
Shared Fields
| provider | openai, gemini, anthropic |
| api_key | request-only provider key |
| file | multipart PDF, PNG, JPG, or JPEG |
| document_base64 | JSON request file bytes |
| prompt | optional extraction instructions |
| model | optional provider model override |
Errors return JSON with error and docs_url. Typical requests take 10 seconds to 4 minutes depending on file size and provider.
Conversion Endpoints
POST /api/convert V1 - VLM JSON
Returns procedures, sections, and steps. Accepts multipart uploads or JSON with base64 file bytes.
curl -X POST https://genflows-api.vercel.app/api/convert \
-F provider=openai \
-F api_key="$OPENAI_API_KEY" \
-F file=@/path/to/document.pdf \
-o result.json
POST /api/v2/convert V2 - CaptureGraph DAG
Returns a CaptureGraph procedure DAG. Same fields as V1.
curl -X POST https://genflows-api.vercel.app/api/v2/convert \
-F provider=openai \
-F api_key="$OPENAI_API_KEY" \
-F file=@/path/to/document.pdf \
-o procedure.json
Node Types
Nodes use kind, return_type, optional label, settings, inputs, and substeps.
| Kind | Returns | Role |
|---|---|---|
| ProcedureSequence | PVoid | Ordered substeps |
| ShowInstructions | PBool | Instruction text |
| RequireProcedureCompleted | PVoid | Required wrapped step |
| UserInputBool | PBool | Yes / No prompt |
| IfThenElse | PVoid | Boolean branch |
| SwitchString | PVoid | One branch per selected option |
| Stopwatch | PNumber | Timer in seconds |
| PaceKeeper | PVoid | Metronome |
| UserInputString | PString | Free-text field |
| UserInputNumber | PNumber | Numeric input |
| UserInputSelectString | PString | Fixed options |
| DoNothing | PVoid | No-op |
| NullProcedure | any | Typed null |
Output Shape
capturegraph.root points to the first node. capturegraph.nodes stores the DAG.
{
"procedure_name": "Adult Cardiac Arrest",
"format": "capturegraph",
"api_version": "2",
"metadata": {
"node_count": 17,
"model_used": "OpenAI gpt-4o",
...
},
"capturegraph": {
"root": "10",
"nodes": {
"00": {"kind": "NullProcedure", "return_type": "PImage"},
"01": {"kind": "ShowInstructions", "return_type": "PBool",
"label": "Start CPR",
"settings": {"text": "Give oxygen. Attach monitor."},
"inputs": {"image": "00"}},
"02": {"kind": "RequireProcedureCompleted", "return_type": "PVoid",
"inputs": {"procedure": "01"}},
"06": {"kind": "UserInputBool", "return_type": "PBool",
"label": "Shockable Rhythm?",
"settings": {"true_text": "Yes", "false_text": "No"}},
"0F": {"kind": "IfThenElse", "return_type": "PVoid",
"inputs": {"condition": "06",
"true_branch": "0B",
"false_branch": "0E"}},
"10": {"kind": "ProcedureSequence", "return_type": "PVoid",
"label": "Adult Cardiac Arrest",
"substeps": ["02", "03", "05", "0F"]}
}
}
}
Traversal Rules
Walk from root:
- ProcedureSequence: run
substepsin order. - IfThenElse: evaluate
inputs.condition, then followinputs.true_branchorinputs.false_branch. - RequireProcedureCompleted: run
inputs.procedure. - ShowInstructions: show
settings.text. - Stopwatch / PaceKeeper / UserInput*: render the matching control and store the result.
- NullProcedure / DoNothing: skip.
Nodes may be reused. The procedure viewer also supports cycles that complete at least one interactive step before re-entering the loop.
Custom Viewer
A viewer only needs capturegraph.root, capturegraph.nodes, and a value map for completed leaf nodes.
- Load
rootandnodesfrom the V2 response. - Find the next active leaf node from
root. - Render that node from its
kind,label, andsettings. - On completion, save
values[activeId]and recompute the next active node. - For Back, remove the last saved value and recompute from
root.
Open Viewer sends the latest V2 response to /viewer.
function resolve(id, nodes) {
const n = nodes[id];
if (!n) return null;
if (n.kind === "RequireProcedureCompleted")
return resolve(n.inputs?.procedure, nodes);
if (n.kind === "NullProcedure" || n.kind === "DoNothing") return null;
return id;
}
function branchId(node, takeTrue) {
return takeTrue ? node.inputs?.true_branch : node.inputs?.false_branch;
}
function switchBranchId(node, selectedValue) {
const index = (node.settings?.options ?? []).indexOf(selectedValue);
return index >= 0 ? node.substeps?.[index] : null;
}
function nextStep(id, nodes, values) {
const rid = resolve(id, nodes);
if (!rid) return null;
const node = nodes[rid];
if (node.kind === "ProcedureSequence") {
for (const child of node.substeps ?? []) {
const active = nextStep(child, nodes, values);
if (active) return active;
}
return null;
} else if (node.kind === "IfThenElse") {
const condition = resolve(node.inputs?.condition, nodes);
if (values[condition] === undefined) return nextStep(condition, nodes, values);
return nextStep(branchId(node, values[condition]), nodes, values);
} else if (node.kind === "SwitchString") {
const selection = resolve(node.inputs?.selection, nodes);
if (values[selection] === undefined) return nextStep(selection, nodes, values);
return nextStep(switchBranchId(node, values[selection]), nodes, values);
}
return values[rid] === undefined ? rid : null;
}
function complete(activeId, value, state) {
state.values[activeId] = value;
state.history.push(activeId);
state.activeId = nextStep(state.root, state.nodes, state.values);
}
function back(state) {
const id = state.history.pop();
if (id) delete state.values[id];
state.activeId = nextStep(state.root, state.nodes, state.values);
}
Node UI
Use label for titles. Use settings and completion values by node kind.
| Kind | UI element |
|---|---|
| ProcedureSequence | Section / step list |
| ShowInstructions | Info card: settings.text; button uses optional settings.button_text (default Next); save true |
| UserInputBool | Yes / No: true_text, false_text; save boolean |
| SwitchString | Match the saved selection to settings.options, then follow the same-index substeps branch |
| UserInputString | Text field; save string |
| UserInputNumber | Number field: min_value, max_value, step; save number |
| UserInputSelectString | Select / radio: options[]; save selected string |
| IfThenElse | Branch from saved condition value |
| Stopwatch | Timer; save elapsed seconds |
| PaceKeeper | Metronome at settings.bpm; save true |
| RequireProcedureCompleted | Resolve and run inputs.procedure |
| NullProcedure / DoNothing | Skip |
Release history
Changelog
Newest first. Expand an update for changes and JSON DAG migration guidance.
Local Python test environment configuration
What changed
- Added workspace Python settings that select
.venv/bin/pythonand enable pytest discovery for thetestsdirectory. - The local virtual environment now installs both production and development requirements so editor analysis can resolve
pytestand application imports.
Existing JSON DAG compatibility
- No JSON DAG changes are required for this development-environment update.
Vercel production routing recovery
What changed
- Removed the catch-all Vercel rewrite that replaced every incoming path with
/api/index.pyand caused the homepage, viewer, and API routes to return JSON 404 responses. - Vercel now uses its native FastAPI routing so the application receives the original requested path.
- Added regression tests for the Vercel entrypoint, homepage, viewer, health endpoint, content types, and deployment configuration.
Existing JSON DAG compatibility
- No JSON DAG changes are required for this deployment-only fix.
- Existing v2 files, including
SwitchStringbranches and optionalbutton_textsettings, remain compatible.
Multiple-choice routing and instruction action labels
What changed
- Multiple-choice steps now support any number of options, with every option routed to its own next step or nested sequence through
SwitchString. - Instruction buttons accept optional
settings.button_text, such asStartorContinue. - The instruction button default changed from
Got ittoNext.
Existing JSON DAG compatibility
- No change is required for existing DAGs that use
IfThenElse, non-branchingUserInputSelectString, orShowInstructions. Missingbutton_textautomatically displaysNext. - To retain a different instruction label, add
"button_text": "Your label"to thatShowInstructions.settingsobject. - To make an existing select branch, add a
SwitchStringnode whoseinputs.selectionpoints to theUserInputSelectString. Copy the ordered choices toSwitchString.settings.options, place the matching branch node IDs inSwitchString.substepsin the same order, and have the parent reference the switch node. - The option and branch arrays must remain the same length and order: option at index
0follows branch at index0, and so on.