Aliases: api, endpoints, docs api, prompts api, get-brain api, http contract

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/docs or /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/prompts or POST /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 as fraya or reference

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 Found if group does 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_path is the repository path and should be treated as the canonical file reference.
  • metadata is normalized from the doc header blockquotes.
  • Common legacy aliases are accepted and normalized to canonical groups, for example standards -> reference and course_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 Found if the document does not exist:
{
  "code": "DOC_NOT_FOUND",
  "message": "Document \"fraya/missing\" not found"
}

Notes:

  • content removes metadata blockquotes that appear before the first ## heading.
  • raw returns markdown with internal HTML comments stripped.
  • If a doc declares a related injection, the endpoint exposes injection_path and injection_name for 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 Request if the request body is not a JSON object:
{
  "code": "INVALID_JSON_BODY",
  "message": "Request body must be a valid JSON object."
}
  • 400 Bad Request if query is missing or empty:
{
  "code": "INVALID_QUERY",
  "message": "Field \"query\" must be a non-empty string."
}
  • 400 Bad Request if scope is 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.
  • score is a relative ranking score for sorting, not a calibrated confidence value.
  • scope accepts 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 -> reference and course_structure -> structure.
  • Use this endpoint for agent tools such as a future get brain reader.

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 as video or course_structure
  • type - limit the response to one prompt type: static, dynamic, or injection
  • include_injections - include files under prompts/injections/; defaults to false

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 Request if include_injections is not a boolean-like flag:
{
  "code": "INVALID_INCLUDE_INJECTIONS",
  "message": "Query param \"include_injections\" must be true or false when provided."
}
  • 404 Not Found if domain or type does not exist in the filtered inventory.

Notes:

  • This endpoint returns prompt metadata only, not rendered prompt text.
  • prompt_path is the canonical repository path for the prompt file.
  • reasoning_excerpt is 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 Request if the request body is not a JSON object or if query is missing.
  • 400 Bad Request if domain, type, include_injections, or max_results are malformed.
  • 404 Not Found if domain is 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, and reasoning.
  • Use this endpoint first when the exact prompt_name is 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 system and user.
  • reasoning and changelog expose 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: "" and user: "<prompt text>".
  • example_request is a generated mock payload shaped from the declared variables. 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 Found if the prompt name does not exist:
{
  "code": "PROMPT_NOT_FOUND",
  "message": "Prompt \"tool_missing\" not found"
}
  • 400 Bad Request if the request body is not a valid JSON object:
{
  "code": "INVALID_JSON_BODY",
  "message": "Request body must be a valid JSON object."
}
  • 422 Unprocessable Entity if 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 POST to fail with 422 if they leave unresolved template expressions, for example {{ section.title }} or {{ template_id }}.
  • missing_variables contains 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_prompt outputs 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:

  1. Fetches the full template catalog from Synthesia using server-side credentials.
  2. Filters it by Fraya's curated allowlist of supported template IDs.
  3. Splits the result into presentations and talking_head.
  4. 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:

  • language
  • format

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:

  1. Loads prompts/section_content/formats.yaml.
  2. Loads prompts/section_content/section_content_types.yaml.
  3. Normalizes them to the runtime shapes already used by Dify:
    • formats[*]enabled, format, purpose, instructions, formatting, example
    • section_types[*]enabled, type, purpose, instructions, place
  4. Filters disabled entries by default.
  5. Optionally resolves one requested format and/or section_type.

Query params:

  • enabled_only default: true
  • format exact format name, for example Text
  • section_type exact section type name, for example Definition

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 format and place so 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_URL and DIFY_API_KEY.
  • It is less repository-centric than /api/prompts/:name.
  • Prefer /api/prompts/:name when the task is about loading prompt logic from the repo.

Environment variables

Current endpoints depend on these server-side variables:

VariableUsed byPurpose
DIFY_API_URL/api/runTarget Dify workflow execution endpoint
DIFY_API_KEY/api/runAuth for the Dify runtime
SYNTHESIA_API_BASE_URL/api/video/templatesBase URL for Synthesia API
SYNTHESIA_API_KEY/api/video/templatesAuth for server-side Synthesia template fetch

  • 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