---
title: "Contribute claims, sources, or both to a brief's fact ledger"
method: POST
path: "/briefs/{id}/facts"
tags: ["Briefs"]
---

# Contribute claims, sources, or both to a brief's fact ledger

`POST /briefs/{id}/facts`

## Request

- Base URL: `https://next.frase.io/api/v1`
- URL: `https://next.frase.io/api/v1/briefs/{id}/facts`
- Auth: API key in header `X-API-KEY`

Records what only the customer knows. Each entry is either a claim they are asserting (stored as theirs and cited to them) or a URL alone, which is fetched so its claims can be extracted. A claim may also carry `source_url`: the page the caller says backs that claim, attached in the SAME write and recorded as the caller's own decision (`human_verified`), never as machine grounding. A claim and its `source_url` land together or not at all, so no result ever means "the fact saved but its source did not" — such an entry comes back `failed`. Reported per contribution: a body of three where one is rejected is a 200 naming that one with its reason, not a 400 discarding the other two.

## Path parameters

- `id` string, uuid, required

## Request body

- object
  - `contributions` object[], required
    - `kind` 'figure' | 'source' | 'experience', required
    - `text` string, required: The customer's own words. A full http(s) URL on its own is fetched and its claims extracted; anything else is stored as their own claim.
    - `source_url` string, uri: Optional. A page backing this claim, attached to it and cited to the caller. This does NOT fetch the page for claims — put a URL alone in `text` for that. Ignored when `text` is itself a URL.
  - `split_multi_item` boolean: Split one entry's text into several facts when it lists separate claims. One contribution can then produce several results, so do not assume the results array matches the input by index.

## Response `200`

Per-contribution results. `status` is written | already_existed | queued | failed. On a success, `source_url` echoes back only when the source was actually attached, and `source_skipped` says why when a URL was sent and deliberately not attached.

## Other responses

- `400`: Validation failed
- `401`: Authentication failed
- `403`: Forbidden - action not allowed on this resource
- `404`: Resource not found
- `429`: Rate limit exceeded

---

[API](https://skmtc.dev/frase/apis/frase-api.md) · [All operations](https://skmtc.dev/frase/apis/frase-api/llms.txt) · [OpenAPI document](https://skmtc.dev/frase/apis/frase-api/revisions/7fb75d89f7a4?raw)
