The REST API
Push content from your own code with an API key, and check what happened.
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:writescope. - 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
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.