Goatlab Tools LogoGoatlab Tools API Docs

Extract citations

Extracts citations from an answer by matching source content against the answer text.

The endpoint uses a three-tier matching strategy with prioritization:

  1. Exact match — The source content is found verbatim in the answer
  2. Case-insensitive match — The source content is found ignoring case differences
  3. Fuzzy match — The source content is matched using semantic similarity via embeddings and cosine similarity

Matching tiers: The two literal tiers can be switched off individually with exact_matching and caseless_matching. Exact matching is on by default and caseless matching is off, since caseless matching finds everything exact matching finds and only adds case variants. Any chunk without a literal match falls through to fuzzy matching, so disabling both tiers forces embeddings for the whole answer.

Overlap protection: If multiple sources match the same region of the answer, only the highest-priority match is kept (exact > case-insensitive > fuzzy).

Fuzzy threshold: The fuzzyThreshold query parameter controls how similar a source must be to match. A value of 0 matches everything, while 1 requires perfect similarity. The default of 0.35 works well for most use cases.

Embedding model: The model query parameter selects the embedding model used for the fuzzy matching tier. It defaults to titan (Amazon Titan Text Embeddings V2). Set it to cohere to use Cohere's embedding model. The exact match and case-insensitive tiers are unaffected by this parameter.

Text splitting: The answer and the sources are chunked independently, each with its own strategy and its own recursive tuning: answer_splitting / answer_recursive_chunk_size / answer_recursive_overlap for the answer, and sources_splitting / sources_recursive_chunk_size / sources_recursive_overlap for the sources. All six default to sentence-based splitting with a 500 character chunk size and a 10% overlap ratio, and the recursive knobs are ignored unless the matching strategy is recursive.

Statement splitting (answer_splitting=statements): A markdown-aware strategy that breaks the answer into the units a citation may point at. It is accepted for the answer only — sources are documents, so sources_splitting does not offer it.

  • Paragraphs become one statement each.
  • Lists are read in context. When the items are fragments (short, or ending in a comma, semicolon, colon or a trailing conjunction), the introducing paragraph, the list and any lower-case continuation paragraph merge into a single statement. When the items are self-contained sentences, each item becomes its own statement and the introducing paragraph is dropped. Nested lists always stay with their parent item.
  • Blockquotes become one statement each.
  • Headings, tables, code blocks and horizontal rules are excluded. A paragraph consisting entirely of one bold span (for example **Notice period**) is treated as a heading and excluded too.

Excluded blocks are not merely deprioritised: no matching tier can return a citation inside them, so a source fragment that only appears in a heading or a table yields no citation at all.

POST
/v1/citations

Authorization

ApiTokenAuth
x-api-key<token>

API Key with role based permission

In: header

Scope:

Query Parameters

fuzzyThreshold?number

Similarity threshold for fuzzy matching (0-1). Higher values require closer semantic matches. Default is 0.35.

Default0.35
Range0 <= value <= 1
model?string

Embedding model used for the fuzzy (semantic) matching tier.

  • titan: Amazon Titan Text Embeddings V2 (default)
  • cohere: Cohere's embedding V4
Default"titan"
Value in"titan" | "cohere"
exact_matching?boolean

Whether the exact matching tier runs. Enabled by default. Disabling it does not widen or narrow what is found when caseless_matching is on — caseless matching finds everything exact matching finds — but citations that were verbatim are then reported as exact_case_insensitive.

Defaulttrue
caseless_matching?boolean

Whether the case-insensitive matching tier runs. Disabled by default, because it matches everything the exact tier matches and only adds case variants, at roughly ten times the cost. Enable it when the answer may differ from the sources only by casing.

With both tiers disabled every chunk falls through to fuzzy matching, which means an embedding call per answer chunk and per source.

Defaultfalse
answer_splitting?string

Text splitting strategy used to chunk the answer.

  • sentences: sentence-based splitting (default)
  • recursive: LangChain recursive character splitting following markdown structure (headings, paragraphs, lists)
  • statements: markdown-aware splitting into citable statements (answer only, see below)
Default"sentences"
Value in"sentences" | "recursive" | "statements"
sources_splitting?string

Text splitting strategy used to chunk the source contents.

  • sentences: sentence-based splitting (default)
  • recursive: LangChain recursive character splitting following markdown structure (headings, paragraphs, lists)

statements is not accepted here — sources are documents and are never split into statements.

Default"sentences"
Value in"sentences" | "recursive"
answer_recursive_chunk_size?integer

Maximum chunk size in characters when answer_splitting is recursive. Ignored for the other strategies. Default is 500.

Default500
Range1 <= value <= 5000
sources_recursive_chunk_size?integer

Maximum chunk size in characters when sources_splitting is recursive. Ignored for the other strategies. Default is 500.

Default500
Range1 <= value <= 5000
answer_recursive_overlap?number

Overlap between consecutive answer chunks when answer_splitting is recursive, as a ratio of the chunk size (0 inclusive to 1 exclusive). Ignored for the other strategies. Default is 0.1 (10% of the chunk size).

Default0.1
Range0 <= value < 1
sources_recursive_overlap?number

Overlap between consecutive source chunks when sources_splitting is recursive, as a ratio of the chunk size (0 inclusive to 1 exclusive). Ignored for the other strategies. Default is 0.1 (10% of the chunk size).

Default0.1
Range0 <= value < 1

Request Body

application/json

The answer text and list of sources to match against it

TypeScript Definitions

Use the request body type in TypeScript.

Response Body

application/json

application/json

application/json

application/json

curl -X POST "https://tools.dev.files.haufe.io/v1/citations" \  -H "Content-Type: application/json" \  -d '{    "answer": "Paris is the capital of France. The Eiffel Tower is one of the most visited monuments in the world. It attracts millions of visitors every year.",    "sources": [      {        "source_id": "src-1",        "content": "Paris is the capital of France.",        "metadata": {          "type": "NUVIO_PERSONAL",          "spaceId": "space-1",          "fileId": "file-1",          "chunkId": "chunk-1"        }      },      {        "source_id": "src-2",        "content": "the eiffel tower is one of the most visited monuments in the world.",        "metadata": {          "type": "NUVIO_SPACE",          "spaceId": "space-2",          "fileId": "file-2",          "chunkId": "chunk-2"        }      },      {        "source_id": "src-3",        "content": "Millions of tourists visit the tower annually",        "metadata": {          "type": "IDESK",          "spaceId": "space-3",          "fileId": "file-3",          "chunkId": "chunk-3"        }      }    ]  }'
{
  "citations": [
    {
      "start": 0,
      "end": 31,
      "source_start": 0,
      "source_end": 31,
      "text": "Paris is the capital of France.",
      "source_text": "Paris is the capital of France.",
      "source_id": "src-1",
      "match_type": "exact",
      "fuzzy_score": null
    },
    {
      "start": 32,
      "end": 99,
      "source_start": 0,
      "source_end": 67,
      "text": "The Eiffel Tower is one of the most visited monuments in the world.",
      "source_text": "the eiffel tower is one of the most visited monuments in the world.",
      "source_id": "src-2",
      "match_type": "exact_case_insensitive",
      "fuzzy_score": null
    },
    {
      "start": 100,
      "end": 149,
      "source_start": 0,
      "source_end": 44,
      "text": "It attracts millions of visitors every year.",
      "source_text": "Millions of tourists visit the tower annually",
      "source_id": "src-3",
      "match_type": "fuzzy",
      "fuzzy_score": 0.82
    },
    {
      "start": 326,
      "end": 556,
      "source_start": 439,
      "source_end": 577,
      "text": "Abzugrenzen ist dies von der beschränkten Steuerpflicht, die greift, wenn weder Wohnsitz noch gewöhnlicher Aufenthalt im Inland liegen und nur inländische Einkünfte (z.B. Arbeitslohn aus Tätigkeit in Deutschland) besteuert werden.",
      "source_text": "[5]  Bei der beschränkten Steuerpflicht unterliegen grundsätzlich nur die inländischen Einkünfte der Person der deutschen Einkommensteuer.",
      "source_id": "HI7693496_HI1153594_0_0",
      "match_type": "fuzzy",
      "fuzzy_score": 0.5382263211063552
    }
  ]
}

{
  "message": "Validation error: answer is required"
}

{
  "message": "Invalid API Key"
}
{
  "message": "Internal Server Error"
}