# The REST API > Push content from your own code with an API key, and check what happened. Source: https://welcomeai.dev/docs/rest-ingest Category: Integrations Updated: 2026-09-11 --- ## What it does Sends documents into a knowledge base from your own code. One request per document, or up to twenty-five at a time. It is the right choice when you control what makes the request -- a script, a backend job, a build step. If a no-code tool is making it, use the sync webhook instead: same documents, same endpoint shape, a signature rather than a key so the tool has no credential to store. ## What it can access Exactly what its scopes allow, in the workspace the key belongs to, and nothing outside it. A key pinned to one knowledge base cannot write to another. ## What it needs before you start - A WelcomeAI workspace on **Pro** or above. - An API key with the `ingest:write` scope. - A knowledge base id, from its URL. ## Authentication `Authorization: Bearer wai_live_…`, over HTTPS. The key is shown once when you create it -- we store only a hash, so there is nothing to look up if it is lost. Put it in your password manager or your deployment secrets straight away, and give it the smallest set of scopes that works. ## Setting it up ```bash curl -X POST 'https://app.welcomeai.dev/api/v1/ingest' \ -H "Authorization: Bearer $WELCOMEAI_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "kb_id": "kb_…", "external_id": "faq-refunds", "title": "Refunds", "url": "https://acme.com/help/refunds", "content_md": "# Refunds\n\nWithin 14 days, email billing@acme.com." }' ``` `external_id` is your identifier for the document. Posting the same one again updates it rather than creating a copy, which is what makes retries safe and re-runs cheap. Getting it right is the single most important decision here. Send `{"external_id": "…", "action": "delete"}` to remove one. The full field list, the batch shape, the response, every error code and the retry rules are in the sync webhook guide -- the body is identical and only the authentication differs. ## How syncing works There is no schedule: you push when you have something. Nothing polls, and nothing runs on our side unless you call it. ### Why there is no "REST API" to add You will not find this under **Add source**, and that is not an omission. The source appears in Connected sources on its own, the first time something arrives -- there is nothing to add, because the initiative is entirely on your side. For the same reason it has almost no settings. A crawl holds an address, a schedule and a page budget, and all three are answers to "what should we go and read?" -- which is a question a push source never asks. Those belong to your code. What you can set here is its name, edited in place in the list, and one thing in **Settings**: the site address. ### Site address Optional, and only used when an item arrives with a relative `url` such as `/help/refunds`. That page is then linked under the site address you set, so an answer citing it opens the right page. Without it, a relative `url` is stored as no URL at all: the document is still indexed and still answered from, but the citation carries a title and no link. We do not guess an origin, because a citation that goes to the wrong page is worse than one that is not a link -- the widget runs on **your** visitor's page, so a relative link would open a path on whatever site they happen to be reading. An absolute `url` is always kept exactly as you sent it, whatever the site address says. Sending absolute URLs is still the better habit; this is the setting that rescues the case where you cannot. Sync **runs** stay empty too. A run records that we went and fetched something, and we never do. The Sync **log** is the one to watch: it gets a row per item you push, created, changed or deleted, and that is where a rejected item shows up. Deleting the source asks whether to keep its documents. It reappears the next time you push, so deleting it is a way to detach the documents, not a way to switch the endpoint off -- for that, revoke the API key. Send `content_hash` with each item if you can. An unchanged hash skips re-indexing entirely, so a nightly full re-push of a large corpus costs almost nothing. ## What is imported Whatever you send: `content_md` (preferred) or `content_html`, which is converted. Up to 2 MB per document. `title` becomes the citation's label and `url` its link -- both are optional and both are worth setting, because "Untitled" in a citation makes a correct answer look untrustworthy. ## How updates are handled By `external_id`. Same id, same document. A document you stop sending is **not** removed automatically: we have no way to know whether you meant to delete it or simply did not mention it this time. Send an explicit delete. ## What people use it for Syncing from a CMS we do not have a connector for. Pushing generated content -- release notes, a changelog, a status page -- as part of a build. Keeping a knowledge base in step with an internal system that already knows when its records change. ## How to check it is working The response tells you per item: `created`, `updated`, `unchanged`, `deleted`, `missing` or `failed`. Watch `unchanged`: on a repeated full sync most items should say it. If everything says `updated`, something in your pipeline is rewriting the content each run and you are paying to embed the same text again. Everything also lands in the Sync log with counts per run, and the documents appear in the knowledge base with their titles. ## Common errors **401** -- the key is wrong, revoked, or missing its `Bearer ` prefix. **403** -- the key lacks `ingest:write`, or it is pinned to a different knowledge base than the one in `kb_id`. **400 `invalid_input`** -- a field is missing or the wrong shape; the message names it. The usual cause is neither `content_md` nor `content_html` on an item that is not a delete. **402** -- a plan limit. Documents or storage, and the message says which. **413** -- the body is over 4 MB, or an item over 2 MB. Split the batch. ## Reconnecting Revoke the old key, create a new one, redeploy with it. Nothing already sent is affected -- documents belong to the knowledge base, not to the key that created them. ## Disconnecting Stop calling it, and revoke the key so it cannot be used by anything you have forgotten about. The documents stay and keep answering. To remove them, delete the source in Connected sources.