Fraya API Surface
Agent-oriented map of the HTTP endpoints currently exposed by this application.
Use this page as the fastest way to answer:
- what can this app already do over HTTP;
- which endpoint to call for a given task;
- what data shape to expect back;
- which endpoints are intended as stable integration points vs internal helpers.
Decision guide
If you need to:
- inspect or retrieve repository documentation → use
/api/docsor/api/docs/:group/:name - retrieve only the most relevant documentation excerpts for a question → use
POST /api/docs/query - discover which prompts exist or find the right prompt name → use
/api/promptsorPOST /api/prompts/query - inspect or render a prompt from the repository once the name is known → use
/api/prompts/:name - get the current curated Synthesia template registry for video generation → use
/api/video/templates - get the current section formats and section types registry for outline or production workflows → use
/api/section-content/registry - send an arbitrary system/user prompt bundle to the Dify runtime → use
/api/run
Stable integration endpoints
These endpoints are intended to be used by agents, workflows, or external tooling.
GET /api/docs
Returns the repository documentation index from docs/.
Use when:
- an agent needs to discover which documentation files exist;
- a workflow needs doc metadata before fetching a specific page;
- you want the current docs inventory without scraping the website.
Optional query params:
group- limit the response to a single docs group such asfrayaorreference
Response shape:
{
"groups": ["artifact", "assets", "discovery", "foundations", "fraya", "image", "localization", "localize_ops", "question", "reference", "review_auto", "review_human", "section_content", "structure", "video"],
"count": 2,
"docs": [
{
"slug": ["fraya", "api"],
"group": "fraya",
"title": "Api",
"href": "/docs/fraya/api",
"doc_path": "docs/fraya/api.md",
"metadata": ["Domain: fraya"],
"injection_path": null,
"injection_name": null
}
]
}
Error behavior:
404 Not Foundifgroupdoes not exist:
{
"code": "DOC_GROUP_NOT_FOUND",
"message": "Doc group \"missing\" not found",
"available_groups": ["artifact", "assets", "discovery", "foundations", "fraya", "image", "localization", "localize_ops", "question", "reference", "review_auto", "review_human", "section_content", "structure", "video"]
}
Notes:
- This endpoint returns metadata only, not full markdown content.
doc_pathis the repository path and should be treated as the canonical file reference.metadatais normalized from the doc header blockquotes.- Common legacy aliases are accepted and normalized to canonical groups, for example
standards->referenceandcourse_structure->structure.
GET /api/docs/:group/:name
Returns one documentation file from docs/ in a normalized JSON shape.
Use when:
- an agent needs the full content of a known document;
- a workflow wants to inspect the source-of-truth markdown body;
- you want doc metadata, rendered content, and raw file text in one call.
Response shape:
{
"slug": ["fraya", "api"],
"group": "fraya",
"title": "Api",
"href": "/docs/fraya/api",
"doc_path": "docs/fraya/api.md",
"metadata": ["Domain: fraya"],
"injection_path": null,
"injection_name": null,
"content": "# Fraya API Surface\n\nAgent-oriented map ...",
"raw": "# Fraya API Surface\n\n> Domain: fraya\n\nAgent-oriented map ..."
}
Error behavior:
404 Not Foundif the document does not exist:
{
"code": "DOC_NOT_FOUND",
"message": "Document \"fraya/missing\" not found"
}
Notes:
contentremoves metadata blockquotes that appear before the first##heading.rawreturns markdown with internal HTML comments stripped.- If a doc declares a related injection, the endpoint exposes
injection_pathandinjection_namefor cross-referencing.
POST /api/docs/query
Returns only the most relevant documentation excerpts for a query instead of the full doc corpus.
Use when:
- an agent needs narrow, question-specific context from repository docs;
- you want retrieval behavior similar to a focused lookup tool rather than raw file dumping;
- a tool should expose "diffused" documentation knowledge and let the agent synthesize the final answer.
Request body:
{
"query": "How should Fraya load prompts into Dify workflows?",
"scope": ["fraya", "video"],
"max_results": 4,
"max_chars_per_result": 900
}
Response shape:
{
"query": "How should Fraya load prompts into Dify workflows?",
"scope": ["fraya", "video"],
"max_results": 4,
"max_chars_per_result": 900,
"total_matches": 2,
"matches": [
{
"doc_path": "docs/video/workflow.md",
"slug": ["video", "workflow"],
"group": "video",
"title": "Workflow",
"section": "Prompt loading",
"score": 62,
"excerpt": "Dify fetches prompt metadata from GET /api/prompts/:name ...",
"metadata": ["Domain: video"],
"injection_path": null
}
]
}
Error behavior:
400 Bad Requestif the request body is not a JSON object:
{
"code": "INVALID_JSON_BODY",
"message": "Request body must be a valid JSON object."
}
400 Bad Requestifqueryis missing or empty:
{
"code": "INVALID_QUERY",
"message": "Field \"query\" must be a non-empty string."
}
400 Bad Requestifscopeis not a string or string array, or if it contains unknown groups:
{
"code": "INVALID_SCOPE_GROUPS",
"message": "One or more requested scope groups do not exist.",
"invalid_groups": ["missing"],
"available_groups": ["artifact", "assets", "discovery", "foundations", "fraya", "image", "localization", "localize_ops", "question", "reference", "review_auto", "review_human", "section_content", "structure", "video"]
}
Notes:
- This endpoint performs repository-native retrieval over
docs/; it does not call a model. - Retrieval is section-level, so results are excerpts from the most relevant document sections, not full files.
scoreis a relative ranking score for sorting, not a calibrated confidence value.scopeaccepts a single group, a comma-separated string, or an array of groups.- Common legacy aliases are accepted and normalized to canonical groups, for example
standards->referenceandcourse_structure->structure. - Use this endpoint for agent tools such as a future
get brainreader.
GET /api/prompts
Returns the prompt inventory from prompts/ in a metadata-first JSON shape.
Use when:
- an agent needs to discover which prompt names exist;
- a workflow needs prompt metadata before choosing a specific prompt;
- you want a prompt catalog without scraping the prompts page.
Optional query params:
domain- limit the response to one prompt domain such asvideoorcourse_structuretype- limit the response to one prompt type:static,dynamic, orinjectioninclude_injections- include files underprompts/injections/; defaults tofalse
Response shape:
{
"domains": ["asset", "audience", "course_structure", "depository", "image", "image_translation", "section_content", "video"],
"types": ["dynamic", "static"],
"count": 1,
"prompts": [
{
"name": "tool_synthesia_choose_template",
"domain": "video",
"type": "dynamic",
"version": 5,
"prompt_path": "prompts/video/tool_synthesia_choose_template.yaml",
"components": [],
"variables": [
{ "name": "available_templates", "description": "..." }
],
"output": {
"format": "json",
"schema": "{ ... }"
},
"reasoning_excerpt": "First step of the Synthesia video generation pipeline..."
}
]
}
Error behavior:
400 Bad Requestifinclude_injectionsis not a boolean-like flag:
{
"code": "INVALID_INCLUDE_INJECTIONS",
"message": "Query param \"include_injections\" must be true or false when provided."
}
404 Not Foundifdomainortypedoes not exist in the filtered inventory.
Notes:
- This endpoint returns prompt metadata only, not rendered prompt text.
prompt_pathis the canonical repository path for the prompt file.reasoning_excerptis a shortened summary intended for prompt discovery, not the full reasoning block.- Use this endpoint when the prompt name is unknown and you need inventory-level discovery.
POST /api/prompts/query
Returns the most relevant prompt candidates for a natural-language question.
Use when:
- an agent needs to find the right prompt name before calling
/api/prompts/:name; - a workflow wants narrow prompt discovery instead of the full prompt catalog;
- the user asks about a prompt by function rather than by exact key.
Request body:
{
"query": "Which prompt chooses the Synthesia template?",
"domain": "video",
"max_results": 3
}
Response shape:
{
"query": "Which prompt chooses the Synthesia template?",
"domain": "video",
"type": null,
"include_injections": false,
"max_results": 3,
"total_matches": 1,
"matches": [
{
"name": "tool_synthesia_choose_template",
"domain": "video",
"type": "dynamic",
"version": 5,
"prompt_path": "prompts/video/tool_synthesia_choose_template.yaml",
"score": 68,
"reasoning_excerpt": "First step of the Synthesia video generation pipeline...",
"variables": ["available_templates", "course_title", "video_title", "video_length", "text"],
"components": []
}
]
}
Error behavior:
400 Bad Requestif the request body is not a JSON object or ifqueryis missing.400 Bad Requestifdomain,type,include_injections, ormax_resultsare malformed.404 Not Foundifdomainis provided but does not exist in the filtered prompt inventory.
Notes:
- This endpoint performs repository-native prompt retrieval; it does not call a model.
- Ranking uses prompt metadata such as
name,domain,variables,components, andreasoning. - Use this endpoint first when the exact
prompt_nameis unknown.
GET /api/prompts/:name
Returns prompt metadata and the stored prompt body from prompts/.
Use when:
- you already know the prompt name and need its exact current contract;
- you need prompt metadata before building a request;
- you need to inspect the current stored prompt definition.
Response shape:
{
"name": "tool_synthesia_choose_template",
"type": "dynamic",
"version": 3,
"domain": "video",
"prompt_path": "prompts/video/tool_synthesia_choose_template.yaml",
"reasoning": "First step of the Synthesia video generation pipeline...",
"changelog": ["v1: initial version"],
"variables": [
{ "name": "available_templates", "description": "..." }
],
"example_request": {
"available_templates": "Example content"
},
"output": {
"format": "json",
"schema": "{ ... }"
},
"components": [],
"system": "You are ...",
"user": "- Course title: {{ course_title }}"
}
Notes:
- The external API always returns
systemanduser. reasoningandchangelogexpose the stored prompt rationale and revision notes directly from the YAML file.- If a prompt file is authored as a single-message
prompt, the API normalizes it to:system: ""anduser: "<prompt text>". example_requestis a generated mock payload shaped from the declaredvariables. Use it as a starting point for POST bodies, especially for nested objects and arrays.- This endpoint reads prompt YAML from the repository. It does not execute a model.
POST /api/prompts/:name
Renders a stored prompt with runtime variables substituted.
Use when:
- a workflow needs the current prompt text from the repo;
- you want Dify or another orchestrator to consume prompt instructions over HTTP instead of copying them manually;
- you need injection content already resolved into the rendered result.
Request body:
{
"course": {
"title": "Le 7 abitudini per essere più efficace"
},
"section": {
"title": "La vittoria pubblica: dall'io al noi"
}
}
Response shape:
{
"system": "You are ...",
"user": "- Course title: Le 7 abitudini per essere più efficace\n- Video title: La vittoria pubblica: dall'io al noi"
}
Error behavior:
404 Not Foundif the prompt name does not exist:
{
"code": "PROMPT_NOT_FOUND",
"message": "Prompt \"tool_missing\" not found"
}
400 Bad Requestif the request body is not a valid JSON object:
{
"code": "INVALID_JSON_BODY",
"message": "Request body must be a valid JSON object."
}
422 Unprocessable Entityif required template expressions remain unresolved after rendering:
{
"code": "UNRESOLVED_TEMPLATE_EXPRESSIONS",
"message": "Prompt render failed because required variables are missing or unresolved.",
"unresolved": ["template_id", "template_title"],
"missing_variables": ["template_id", "template_title"],
"rendered_partial": {
"system": "You are ... {{ template_id }} ...",
"user": "Template: {{ template_title }}"
}
}
Notes:
- The renderer supports nested paths (
{{ course.title }}), conditionals, loops, basic comparisons,or/and, and|length. - POST bodies may use nested JSON objects and arrays; flat dotted keys remain accepted for compatibility.
- Missing variables now cause
POSTto fail with422if they leave unresolved template expressions, for example{{ section.title }}or{{ template_id }}. missing_variablescontains unresolved expressions that look like variable paths and are absent from the input body.- This endpoint is both a renderer and a final prompt-readiness validator.
- It is the correct endpoint to use when a workflow wants prompt logic from the repo.
- The rendered response always uses the pair
system+user.
Shared Dify tool wrapper:
- A shared Fraya workflow tool around this endpoint is stored in
fraya-agent/tools/render-prompt/render-prompt.raw.yml. - Use that workflow when Dify needs prompt loading + input validation + normalized
system_prompt/user_promptoutputs as a reusable subflow.
GET /api/video/templates
Builds the current video template registry for the Synthesia workflow.
Use when:
- a workflow needs the curated list of supported Synthesia templates;
- you want live Synthesia metadata without using Google Sheets;
- you need a repo-controlled allowlist combined with current workspace template data.
What it does:
- Fetches the full template catalog from Synthesia using server-side credentials.
- Filters it by Fraya's curated allowlist of supported template IDs.
- Splits the result into
presentationsandtalking_head. - Returns the filtered live metadata.
Response shape:
{
"registry_source": "repo_allowlist_plus_synthesia",
"allowed_template_ids_count": 32,
"workspace_templates_count": 112,
"selected_templates_count": 32,
"presentations": [
{
"template_id": "0c471c30-f3fe-432b-9fae-642b096238c1",
"template_title": "A-F",
"template_description": "Single principle or idea — no enumeration",
"template_variables": [
{ "label": "slide_1_script", "type": "string" }
]
}
],
"talking_head": [
{
"template_id": "9c23e3c3-401b-4373-9ca2-e15b669974a3",
"template_title": "[de] Talking head (A-Q-A-F)",
"template_description": "...",
"template_variables": [
{ "label": "slide_1_script", "type": "string" }
]
}
],
"legacy_language_filtering": false,
"requested_language": "Default (auto detection)",
"requested_format": "VideoTalkingHead",
"note": "language and format are currently informational only; filtering is based on the repo allowlist and runtime consumers can still select the needed subset."
}
Optional query params:
languageformat
Current behavior:
- accepted for compatibility and debugging;
- currently informational only;
- filtering is driven by the repo allowlist, not by language-specific registry columns.
Important:
- This endpoint is the intended replacement for the old Google Sheets-based template allowlist.
- Synthesia remains the source of live metadata (
title,description,variables). - The repository remains the source of truth for which template IDs are allowed.
GET /api/section-content/registry
Builds the current section registry used by outline and section-content workflows.
Use when:
- a workflow needs the enabled list of section formats and section types;
- you want the repository-backed replacement for the old Google Sheets runtime lookup;
- you need either the full registry or one specific format / section type by name.
What it does:
- Loads
prompts/section_content/formats.yaml. - Loads
prompts/section_content/section_content_types.yaml. - Normalizes them to the runtime shapes already used by Dify:
formats[*]→enabled,format,purpose,instructions,formatting,examplesection_types[*]→enabled,type,purpose,instructions,place
- Filters disabled entries by default.
- Optionally resolves one requested
formatand/orsection_type.
Query params:
enabled_onlydefault:trueformatexact format name, for exampleTextsection_typeexact section type name, for exampleDefinition
Response shape:
{
"registry_source": "repo_data_files",
"source_files": {
"formats": "prompts/section_content/formats.yaml",
"section_types": "prompts/section_content/section_content_types.yaml"
},
"enabled_only": true,
"counts": {
"formats": 11,
"section_types": 30
},
"available_formats": ["Text", "VideoPresentation"],
"available_section_types": ["Definition", "CaseStudy"],
"requested_format": {
"enabled": true,
"format": "Text",
"purpose": "...",
"instructions": "...",
"formatting": "...",
"example": "..."
},
"requested_section_type": null,
"formats": [
{
"enabled": true,
"format": "Text",
"purpose": "...",
"instructions": "...",
"formatting": "...",
"example": "..."
}
],
"section_types": [
{
"enabled": true,
"type": "Definition",
"purpose": "...",
"instructions": "...",
"place": ""
}
]
}
Important:
- This endpoint is the intended repository-backed replacement for the old Google Sheets
parsing node that built
{ formats, section_types }inside Dify. - The response keeps the Dify-facing field names
formatandplaceso the workflow can reuse the same downstream shape after one JSON parse step.
Internal helper endpoint
This endpoint exists and works, but should be treated as a thinner runtime helper, not the primary repo-integration surface.
POST /api/run
Proxies a raw { model, system, user } request to the configured Dify workflow runtime.
Use when:
- you want to quickly execute a prompt bundle against the Dify backend from the docs app;
- you need an internal testing surface for prompt execution.
Request body:
{
"model": "gemini-2.5-pro",
"system": "You are ...",
"user": "..."
}
Notes:
- This endpoint depends on
DIFY_API_URLandDIFY_API_KEY. - It is less repository-centric than
/api/prompts/:name. - Prefer
/api/prompts/:namewhen the task is about loading prompt logic from the repo.
Environment variables
Current endpoints depend on these server-side variables:
| Variable | Used by | Purpose |
|---|---|---|
DIFY_API_URL | /api/run | Target Dify workflow execution endpoint |
DIFY_API_KEY | /api/run | Auth for the Dify runtime |
SYNTHESIA_API_BASE_URL | /api/video/templates | Base URL for Synthesia API |
SYNTHESIA_API_KEY | /api/video/templates | Auth for server-side Synthesia template fetch |
Recommended usage patterns
- For repository documentation discovery: call
GET /api/docs - For fetching one known document: call
GET /api/docs/:group/:name - For narrow documentation retrieval: call
POST /api/docs/query - For prompt inventory: call
GET /api/prompts - For prompt-name discovery: call
POST /api/prompts/query - For workflow prompt loading: call
/api/prompts/:name - For video template discovery: call
/api/video/templates - For ad hoc execution from the docs app: call
/api/run
The intended long-term pattern is:
repo docs + repo prompts + repo-controlled template registry + workflow orchestration in Dify