---
title: "Call knowledge_source.add"
method: POST
path: "/api/v1/tools/knowledge_source.add"
tags: ["knowledge_source"]
---

# Call knowledge_source.add

`POST /api/v1/tools/knowledge_source.add`

## Request

- Base URL: `https://app.goosybear.ai`
- URL: `https://app.goosybear.ai/api/v1/tools/knowledge_source.add`
- Auth: HTTP bearer

Bring a new knowledge source into this workspace's brand knowledge base — 'add our blog as a source', 'ingest this competitor's site', 'keep this decision for everyone', 'remember this for the team', 'put what we agreed in the knowledge base'. Add a knowledge source to this workspace's brand knowledge base — a link, feed, YouTube channel, competitor handle, subreddit, an already-uploaded file, or a note: something the member asks you to save, remember or share with the team from this conversation, which every member's knowledge.search then finds. A member's chats are private to them, so a note is how a decision reaches the team; write it only when the member asks for it, never on your own. Reading a source or a note costs the workspace credits. Calling it raises the approval card showing what would be added, which is the operator's confirmation, so it can still be refused after you call it. Never say a source was added unless the result says so. Report the outcome from the result's status field, never from ingested: status 'ingested' means the source was read into the knowledge base just now; status 'catalogued' means it is saved but NOT read yet, so never claim the knowledge base already answers from it; status 'already_live' means it was ALREADY saved and read, so say it is already working and nothing needed doing — do not describe it as unread. Not for a fact about the brand itself that the member tells you (an offer, a location, a dated plan with no ask to save it for the team), which is `brand_kit.suggest`; not for reading one page right now without saving it — that is `web.read`. Over the API it answers in two calls: without confirmation_id it proposes — says what would happen, returns a confirmation_id and changes, spends and sends nothing; the same arguments again with that confirmation_id, on a member's own API key, do it.

Every result names the workspace it ran in. Say which workspace the answer is about. When the account has more than one workspace and none is selected, this tool refuses with `workspace_ambiguous` and lists the choices — offer them, never pick one.

## Headers

- `x-request-id` string

## Request body

- object
  - `kind` 'file_upload' | 'url' | 'rss_feed' | 'competitor_handle' | 'subreddit' | 'youtube_channel' | 'note', required: What this source is. Use 'note' for something the member asked you to save from this conversation, 'url' for a web page, 'youtube_channel' for a YouTube channel or video, 'rss_feed' for a feed, 'competitor_handle' or 'subreddit' for a monitored handle, 'file_upload' for a file already uploaded on the Knowledge screen.
  - `display_name` string, required: What the operator should see this source called, at least 2 characters. For a note, a short title naming the decision.
  - `reference` string: The link, handle or filename this source points at, at least 2 characters. Never invent one — take it from the operator. Leave it out for a note.
  - `text` string: For a note only: what to save, written from the conversation as plain sentences a colleague can read without it — the decision, its reasons and today's date, in the member's own facts. Never add a fact the conversation does not hold.
  - `ingestion_cadence` 'one_shot' | 'weekly' | 'on_demand': How often to re-read the source. Leave it out unless the member says how often: a web page then re-reads weekly, so its answers stay current.
  - `confirmation_id` string, uuid: Omit to propose: the first call says what would happen, returns a confirmation_id and changes nothing. To go ahead, send the same arguments again with that confirmation_id, on a member's own API key; a service account's key can propose but never confirm.
  - `workspace` string: Which workspace to run in — its slug. Omit to use your default. With more than one reachable workspace and no default, the call is refused and the choices are listed.

## Response `200`

The call was admitted and dispatched. `ok` says whether the tool succeeded — a refusal the tool itself produced is still a 200, exactly as it is a successful JSON-RPC result over MCP.

- union
  - object: The tool ran and answered.
    - `ok` true, required
    - `state` 'confirmation_required'
    - `confirmation_id` string, uuid
    - `expires_in_seconds` integer
    - `request_summary` string: What will happen, in one sentence, for a person to approve before confirming.
    - `source_id` string, uuid
    - `status` 'ingested' | 'catalogued' | 'already_live'
    - `ingested` boolean
    - `chunk_count` integer
    - `workspace` string, required: The slug of the workspace this call ran in.
    - `working_in` object, required: Which workspace this call ran in, and how that was decided. Present on every workspace-scoped result.
      - `label` string, required: The chip words — e.g. "working in: Acme Main".
      - `note` string, required: Why this workspace — e.g. "your default", "this call only".
      - `workspace` string, required: The resolved workspace's slug.
      - `source` 'call-override' | 'key-pin' | 'stored-default' | 'sole-reachable', required: Which rung of the R7 precedence resolved the workspace.
  - ToolRefusal: A call that was admitted and dispatched, and did not succeed.
    - `ok` false, required
    - `code` string, required: The typed reason — e.g. `workspace_ambiguous`, `invalid_arguments`, or the tool's own failure code.
    - `message` string, required: A sentence a person can act on.
    - `workspaces` string[]: On a workspace refusal: the slugs this key may pass as `workspace`.
    - `issues` object[]: On `invalid_arguments`: which fields failed, and why.
      - `path` string, required
      - `message` string, required
    - `path` string: On an authoring refusal: where in your call's input it points, e.g. `steps.<key>.params.<field>`.
    - `fix` string: On an authoring refusal: the change to your call that would pass.
    - `instruction` string: On an authoring refusal: what to do next with it.

## Other responses

- `400`: The request could not be used: the body is present but not a JSON object (`tenant_api.malformed_body`); it is a top-level array carrying more messages than this endpoint accepts (`tenant_mcp.batch_too_large`); or the `Idempotency-Key` header is malformed (`tenant_api.idempotency_key_invalid`), disagrees with the body's `idempotency_key` (`tenant_api.idempotency_key_conflict`), or was sent to an action that cannot replay a retry (`tenant_api.idempotency_unsupported`). Nothing ran.
- `401`: No bearer credential, or one that did not resolve. Deliberately ONE undifferentiated answer for absent / unknown / revoked / expired, so the endpoint is never an oracle over the key space.
- `403`: The credential is good but the surface is not open to it — the platform or account API switch is off, or the holder lacks the `api.access` capability. The `code` says which.
- `404`: No such tool. The body names no tool and is identical for every unrecognised name.
- `429`: Over this key's per-minute cap. Carries `Retry-After` and `error.retry_after_seconds`.

---

[API](https://skmtc.dev/goosybear/apis/goosy-bear-api.md) · [All operations](https://skmtc.dev/goosybear/apis/goosy-bear-api/llms.txt) · [OpenAPI document](https://skmtc.dev/goosybear/apis/goosy-bear-api/revisions/0e033701d3f7?raw)
