Knowledge Center
A hosted, public help site fed by your videos. Articles live in sections, get translated into your enabled locales, and answer questions through a retrieval endpoint you can wire into a support bot.
The Knowledge Center is a paid add-on. Without it, every endpoint here returns
403. One knowledge base per workspace, so this is a singular resource -
/v1/knowledge-base, not /v1/knowledge-bases.
The knowledge base
GET /v1/knowledge-base · POST /v1/knowledge-base · PATCH /v1/knowledge-base ·
DELETE /v1/knowledge-base
curl -X POST https://api.vidocu.ai/v1/knowledge-base \
-H "Authorization: Bearer vdo_live_your_key_here" \
-H "Content-Type: application/json" \
-d '{"name": "Acme Help", "slug": "acme", "defaultLocale": "en"}'
{
"id": "0b0f...",
"name": "Acme Help",
"slug": "acme",
"url": "https://acme.vidocu.help",
"defaultLocale": "en",
"enabledLocales": ["en"],
"visibility": "public"
}
The slug is the public address. Changing it changes every URL on the site, so add
redirects for anything already published. 409 if the workspace already has a
knowledge base or the slug is taken.
DELETE is a soft delete: the site stops resolving, the articles are kept.
Sign-in access
visibility is public (anyone can read) or gated (readers sign in first). On a gated knowledge
base, access decides who gets in:
curl -X PATCH https://api.vidocu.ai/v1/knowledge-base \
-H "Authorization: Bearer $VIDOCU_API_KEY" \
-H "Content-Type: application/json" \
-d '{"visibility": "gated", "access": {"openSignup": false, "allowedEmailDomains": ["acme.com"]}}'
| Field | Meaning |
|---|---|
openSignup | true lets any email in. false limits sign-in to the lists below and to shared sections. |
allowedEmailDomains | Domains with full access to every section, e.g. ["acme.com"]. |
explicitAllowedEmails | Individual addresses with full access to every section. |
authMethod | magic-link emails a one-time link; vidocu signs readers in with a Vidocu account; oidc is company sign-in. |
sharedSectionsRequireApproval | See Approval for shared sections. |
access is merged: fields you leave out are kept. Access is checked on every page, not only at
sign-in, so removing a domain locks its readers out on their next click.
Company sign-in
On the Enterprise plan, readers can sign in through your own identity provider over OpenID Connect (Keycloak, Okta, Microsoft Entra ID and others). Anyone who can sign in there can read the knowledge base. They don't need a Vidocu account.
We set up the connection with your IT team. Once it's active, switch to it:
curl -X PATCH https://api.vidocu.ai/v1/knowledge-base \
-H "Authorization: Bearer $VIDOCU_API_KEY" \
-H "Content-Type: application/json" \
-d '{"visibility": "gated", "access": {"authMethod": "oidc"}}'
- Before the connection is active, setting
oidcreturns400. - With
oidcon,openSignupand the email lists are not used, and section sharing has no effect: every signed-in reader sees every section. - Switching to or from
oidcsigns out everyone who signed in the other way. - A reader session lasts 24 hours, so someone removed from your identity provider loses access within a day.
Sections
GET /v1/knowledge-base/sections · POST /v1/knowledge-base/sections ·
PATCH /v1/knowledge-base/sections/:id · DELETE /v1/knowledge-base/sections/:id ·
POST /v1/knowledge-base/sections/reorder
Every article belongs to a section. New sections are appended to the end of their siblings;
reorder takes the full list of section ids in the order you want them displayed.
Sub-sections
Sections nest one level deep. A section carries parentId: null (or absent, on sections created
before this existed) for a top-level section, or the id of the top-level section it sits under.
curl -X POST https://api.vidocu.ai/v1/knowledge-base/sections \
-H "Authorization: Bearer $VIDOCU_API_KEY" \
-H "Content-Type: application/json" \
-d '{"name": "Billing", "slug": "billing", "parentId": "sec_account"}'
The rules, each answered with 400 when broken:
- The parent must exist and belong to the same knowledge base.
- The parent must itself be top-level. A sub-section cannot have sub-sections.
- A section cannot be its own parent.
- A section that already has sub-sections cannot be moved under another section.
- Deleting a section that still has sub-sections is refused. Move them (
PATCHwith a newparentId, ornullto make them top-level) or delete them first.
What does not change: slugs stay unique across every section of the knowledge base, and public
URLs stay flat (/{section-slug}), so nesting a section never breaks a link. Articles can live in
a top-level section or a sub-section. position orders siblings, so reorder numbers top-level
sections and each parent's sub-sections as separate groups. On the public site a sub-section is
never shown to someone who cannot see its parent: the effective visibility is the more restrictive
of the two.
Deleting a section that still holds articles returns 409 - orphaned articles would vanish from
the site with no trace, so move them first.
Thumbnails
A section can carry a thumbnailUrl: a publicly reachable image shown on its card, on the
homepage grid and on the cards for sub-sections.
curl -X PATCH https://api.vidocu.ai/v1/knowledge-base/sections/sec_billing \
-H "Authorization: Bearer $VIDOCU_API_KEY" \
-H "Content-Type: application/json" \
-d '{"thumbnailUrl": "https://cdn.example.com/billing.png"}'
null removes it. Every section answers thumbnailUrl - null when it has none - whatever era
it was created in.
The shape is one setting for the whole knowledge base, not a field on the section, because a grid that mixes both reads as broken rather than as variety. It lives in the dashboard under Settings -> Homepage:
| Mode | What the card looks like | Image to supply |
|---|---|---|
| Squares (default) | A square tile beside the section name | 200x200 or larger, square |
| Cover | A 16:9 banner across the top of the card | 800x450 or larger, 16:9 |
Images are cropped to fill, never letterboxed. A section with no thumbnail is not a gap: it keeps the tinted tile in squares mode and gets a tinted panel carrying its initial in cover mode, so every card in a row stays the same height.
Thumbnails travel into an offline export with the rest of the site's images.
Section names in other languages
A section's name and description are in the knowledge base's defaultLocale. For its other
enabled locales, a section carries translations, keyed by locale code:
curl -X PATCH https://api.vidocu.ai/v1/knowledge-base/sections/sec_start \
-H "Authorization: Bearer $VIDOCU_API_KEY" \
-H "Content-Type: application/json" \
-d '{"translations": {"fr": {"name": "Premiers pas"}, "it": {"name": "Primi passi", "description": "Da dove iniziare"}}}'
PATCHmerges by locale. Locales you leave out are kept;null(or a row with both fields empty) removes one.- A missing field falls back to the main-language text, so a reader on
/fr/sees the French name with the original description until you add one. - Only the knowledge base's enabled, non-default locales are accepted. The default locale is refused
with
400: its text is the section's ownnameanddescription. - Slugs are not translated.
/fr/startand/startare the same section, so a link never breaks when a reader switches language. POSTtakestranslationstoo. Sections without any answer notranslationsfield.
The public site, its search results and the offline export all use the reader's language. In the dashboard the same fields sit in the section's edit row, with a button that drafts them by machine translation for you to review before saving.
Sharing a section with outside readers
A gated knowledge base can open single sections to people outside its access list, such as a
customer's team, without opening the rest. Set sharedWith on a top-level section:
curl -X PATCH https://api.vidocu.ai/v1/knowledge-base/sections/sec_customer_docs \
-H "Authorization: Bearer $VIDOCU_API_KEY" \
-H "Content-Type: application/json" \
- Full access stays full. Anyone on the knowledge base's own access list still sees every section.
- Outside readers see only what is shared with them. Someone matching only a section's
sharedWithcan sign in, and sees that section, its sub-sections and their articles. Every other section is absent for them: not in navigation, search, Ask answers or direct links (404). - Nothing is shared by default. A new section is invisible to outside readers until you add them to it.
- Sub-sections follow their parent and can't carry
sharedWiththemselves. Nesting a shared section, or sharing a sub-section, is a400. nullor empty lists stop sharing. Entries are lowercased and deduplicated; a domain may be written with or without@.- It applies to gated knowledge bases. On a public one every reader already sees every public section.
Every section answers sharedWith: null when it isn't shared.
Approval for shared sections
With access.sharedSectionsRequireApproval set to true, a shared section only ever shows approved
work:
- An article in a shared section (or its sub-sections) is shown, to every reader, only while its package is approved and locked. Until then it stays published but out of sight, including from search, Ask, the sitemap and offline exports. Unlocking a package takes its article out of view again until the next approval.
- An article with no video has no package to approve, so it never shows in a shared section.
To control who can put an article in a shared section at all, use the section's publishers.
Who can publish in a section
publishers on a section restricts who may create an article in it, move an article into
it, or publish an article while it sits there:
curl -X PATCH https://api.vidocu.ai/v1/knowledge-base/sections/sec_customer_docs \
-H "Authorization: Bearer $VIDOCU_API_KEY" \
-H "Content-Type: application/json" \
-d '{"publishers": {"userIds": ["usr_quality_lead", "usr_docs_owner"]}}'
- Workspace owners and admins always may, so
{"userIds": []}means owners and admins only. nullremoves the restriction: anyone who can edit articles may publish there again.- A sub-section without its own list follows its parent's.
- Anyone else gets
403 insufficient_rolenaming the section. Editing an article that already sits in the section is still allowed; unpublishing is too. - Every move is recorded in the article's change history as its own entry naming both sections, and every publish names the section it went live in. This includes changes made through the API.
publishersandsharedWithare only returned to, and only accepted from, owners and admins (an API key acts as the owner). Other callers see sections without them.
Tags
Tags are private labels on articles. They never appear on the public site; they are how you file articles for yourself, and how you choose which articles go into an offline export. The list lives on the knowledge base and articles point at it by id.
PATCH /v1/knowledge-base takes the full tags list: a row with a known id is renamed or
recolored, a row without one is created, and a tag left out of the list is deleted and removed
from every article that carried it. A knowledge base holds up to 50 tags; names are unique
(case-insensitive) and up to 40 characters; color is a hex value.
curl -X PATCH https://api.vidocu.ai/v1/knowledge-base \
-H "Authorization: Bearer $VIDOCU_API_KEY" \
-H "Content-Type: application/json" \
-d '{"tags": [
{"id": "tag_existing", "name": "Customer: Acme", "color": "#007A7A"},
{"name": "Internal", "color": "#7C3AED"}
]}'
Assign tags with tagIds on POST /v1/knowledge-base/articles and
PATCH /v1/knowledge-base/articles/:id. Unknown ids return 400. Filter the list with
GET /v1/knowledge-base/articles?tagId=.... Tags are filing metadata, so they stay editable on a
locked article.
Articles
GET /v1/knowledge-base/articles · POST /v1/knowledge-base/articles ·
GET /v1/knowledge-base/articles/:id · PATCH /v1/knowledge-base/articles/:id ·
DELETE /v1/knowledge-base/articles/:id
Content is Markdown. The list endpoint returns metadata without article bodies - fetch a single
article for its content. It's cursor-paginated and filterable by state,
sectionId, visibility, linkedVideoId, tagId and sourceLocale.
curl -X POST https://api.vidocu.ai/v1/knowledge-base/articles \
-H "Authorization: Bearer vdo_live_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"sectionId": "sec_...",
"title": "Resetting your password",
"slug": "resetting-your-password",
"content": "## Steps\n\n1. Open Settings...",
"state": "published"
}'
Articles in one language
An article is written in the knowledge base's main language unless you say otherwise. Set
sourceLocale on create to write it in another enabled locale. Its title and content are then in
that language, and readers see it only there: /de/billing/rechnungen-exportieren works, and the
main-language URL returns 404. It stays out of the main language's section pages, search and
sitemap too.
curl -X POST https://api.vidocu.ai/v1/knowledge-base/articles \
-H "Authorization: Bearer $VIDOCU_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"sectionId": "sec_...",
"title": "Rechnungen exportieren",
"slug": "rechnungen-exportieren",
"content": "## Schritte\n\n1. Öffnen Sie die Einstellungen...",
"sourceLocale": "de"
}'
To reach more readers, translate it. A translation into the main language makes it readable there like any other article.
sourceLocalemust be enabled on the knowledge base, or you get400.- Every article returns
sourceLocale, resolved: an article in the main language returns the main language's code. - The language is fixed once the article exists. Sending a different
sourceLocaleonPATCHreturns400. To move an article to another language, create a new one. POST /v1/knowledge-base/articles/from-videotakessourceLocaletoo. Set it to the video's language when that isn't the main one.
Attaching a video
An article can carry the video it documents. Set linkedVideoId on create or update; send null on
update to detach it. Detaching removes the link only - the video itself stays in the workspace.
# Attach
curl -X PATCH https://api.vidocu.ai/v1/knowledge-base/articles/art_... \
-H "Authorization: Bearer $VIDOCU_API_KEY" \
-H "Content-Type: application/json" \
-d '{"linkedVideoId": "vid_..."}'
# Detach
curl -X PATCH https://api.vidocu.ai/v1/knowledge-base/articles/art_... \
-H "Authorization: Bearer $VIDOCU_API_KEY" \
-H "Content-Type: application/json" \
-d '{"linkedVideoId": null}'
The link is frozen on a locked article, and a request that attaches a video and publishes in the same call is gated on the video it is attaching, not the one it is replacing.
Locked articles
An article that came from a locked video is a permanent record. It stays
organizable - sectionId, state, seo and tagIds can still change, so it can be filed and
surfaced - but everything else is frozen and returns 423.
The slug is frozen too. It is the article's address, and re-pointing an approved path at other
content would leave nothing behind in the approval record. Changing the address means unlocking the
article first, which starts a fresh approval round. Locked articles can't be deleted at all.
Publishing and approvals
Publishing an article whose source video is under review is blocked with 409 until every approver
has approved. An approval also goes stale: if the source video was unlocked and edited after being
approved, publishing is refused until a fresh approval round passes.
Publishing is also refused with 409 while the body still carries an unfinished placeholder - any
[TODO: ...] marker outside an HTML comment. Vidocu's own draft writer leaves one wherever it could
not confirm a detail, and a generated article is not finished until every one is resolved. The
error message lists the first few. Creating an article directly as published is checked the same
way.
Import from a video
POST /v1/knowledge-base/articles/from-video
curl -X POST https://api.vidocu.ai/v1/knowledge-base/articles/from-video \
-H "Authorization: Bearer vdo_live_your_key_here" \
-H "Content-Type: application/json" \
-d '{"videoId": "vid_abc123", "sectionId": "sec_...", "state": "published"}'
The video must already have a help article - generate one with
POST /v1/videos/:id/article first, or this returns 409. The dashboard
generates one on the fly; the API deliberately doesn't, because that's a metered job that shouldn't
fire as a side effect of an import.
The imported article is linked back to its video, and a locked video produces a locked article.
Pass sourceLocale when the video is in a language other than the knowledge base's main one (see
Articles in one language).
Translation
POST /v1/knowledge-base/articles/:id/translate with {"targetLocales": ["es", "de"]}
Targets must be locales enabled on the knowledge base. A knowledge base can enable as many locales
as you need. An article translates from the language it is written in, so an article written in
German can be translated into the main language too. Its own language is skipped as a target. Each target language is charged kb_article_translate credits from the workspace
credit balance (see Billing). The whole request is priced up front: if the balance can't
cover every target, nothing is translated and you get 403 limit_exceeded.
A locked article can't be translated (423): its languages are frozen with it.
Languages of an approved article
An article published from an approved package is a controlled document, and so is every language of it. Its languages don't come from machine translation. Each one comes from its own translated package, which goes through its own approval:
- Translate the video into the language. That creates a translated package in the same language family, with its own article.
- Send that package for approval.
- When the last approver approves, the package's article becomes the article's version in that language, copied word for word. Readers see it from that moment. There is no separate publish step.
Until then, readers don't see that language anywhere: no page, no entry in a section or search, no Ask answer, no sitemap or hreflang entry, and nothing in the offline export. A machine translation made before the article was approved stays in the dashboard, marked as hidden, and is replaced once the package in that language is approved. The language page plays that approved package's video.
The copy is the approved text, so it can't be edited, re-translated or deleted (409). To change
it, translate the video into that language again and send the new package for approval. Once it
is approved, it replaces the old version. The most recently created approved package in a language
is the one readers see. A duplicate of a translated package is not part of the language family,
so approving a duplicate does not change the Knowledge Base.
To take one language down without touching the others, archive that language's package. The language disappears for readers at once, and the dashboard shows it as withdrawn. Unarchive the package to publish it again, or approve a new package in that language to replace it. An older approved version never comes back on its own. Archiving a translated package leaves the training alone, because the master and the other languages share it.
Unpublishing, archiving or deleting the article itself still takes every language with it.
The article page for a language shows that language's own approvers and approval date, not the master's.
Articles that were never published under approval are unaffected: their languages work as before.
Ask - retrieval-grounded answers
POST /v1/knowledge-base/ask
The endpoint to wire a support bot into. It answers from your published articles, with citations, rather than from a model's general knowledge.
curl -X POST https://api.vidocu.ai/v1/knowledge-base/ask \
-H "Authorization: Bearer vdo_live_your_key_here" \
-H "Content-Type: application/json" \
-d '{"question": "How do I reset my password?"}'
{
"answer": "Open Settings, choose Security, then...",
"citations": [{ "articleId": "...", "title": "Resetting your password", "slug": "..." }],
"noMatches": false,
"matchedCount": 3
}
Retrieval is cross-lingual: the question is matched against every locale's content, and the answer
is written in the requested locale (defaulting to the knowledge base's).
The answer is markdown: numbered steps, bold control names, and, when an article's screenshot shows the step being explained, that image's markdown copied from the article. Render it, don't print it.
For a conversation, pass the earlier turns in history (oldest first, up to 20 turns of
{ "role": "user" | "assistant", "content": "..." }, not including the current question). A
follow-up such as "and on mobile?" is then answered in the light of what came before, and retrieval
stays on the same subject. Without history every question stands alone, as before.
When nothing relevant is found, noMatches is true and answer is empty - surface that as "I
don't know" rather than inventing one.
Reindex
POST /v1/knowledge-base/reindex
Rebuilds search embeddings for every published article. Publishing indexes automatically, so this is only for after a bulk import or if search looks stale. It walks the whole knowledge base and can take a while.
Redirects
GET /v1/knowledge-base/redirects · POST /v1/knowledge-base/redirects ·
DELETE /v1/knowledge-base/redirects/:id
Keep old links working after a slug change. Both paths start with /:
curl -X POST https://api.vidocu.ai/v1/knowledge-base/redirects \
-H "Authorization: Bearer vdo_live_your_key_here" \
-H "Content-Type: application/json" \
-d '{"fromPath": "/articles/old-slug", "toPath": "/articles/new-slug"}'
Analytics
GET /v1/knowledge-base/analytics?days=30
Article counts by state, most-viewed articles, top searches, and searches that returned nothing - the last being the most direct signal of what your documentation is missing.
Offline export
POST /v1/knowledge-base/exports · GET /v1/knowledge-base/exports ·
GET /v1/knowledge-base/exports/:id
A static copy of the help center as a ZIP, for customers who run documentation on their own
network. It carries your branding, sections, articles, images, the exported video for each article
and a client-side search box on every page. It opens from index.html with no server and also serves from any
static host. AI Ask, reader feedback and quiz scoring need the live site and are left out.
curl -X POST https://api.vidocu.ai/v1/knowledge-base/exports \
-H "Authorization: Bearer $VIDOCU_API_KEY" \
-H "Content-Type: application/json" \
-d '{"locales": ["en", "de"], "tagIds": ["tag_acme"], "includeVideos": true}'
locales- a subset of the enabled locales; the default locale is always included. An extra language only carries the articles that have a translation.tagIds- restrict to tagged articles. Empty means every published, public article.tagMatchisany(default) orall.notes- optional, up to 500 characters: what changed in this version. Kept on the export and in the bundle'smanifest.json; never shown on a page.includeVideos- defaulttrue. Each article's exported video is copied in. A video with no export, or one that changed since, is exported first through the normal export path and counted like any other export. A video that cannot be rendered in time is skipped and listed underskippedVideoswith the reason; the article gets a "watch it online" link instead.
The build runs in the background: the response is 202 with the export record, and
GET /v1/knowledge-base/exports/:id reports progress until status is completed or failed.
One build runs at a time per knowledge base; a second POST returns 409.
Each export gets a version: builds with the same tag selection (or none) form a series, and every
completed build in it is numbered one higher. The number is in the ZIP filename and in the footer of
every page of the exported site, so a customer can tell you which copy they are holding.
A completed export carries a presigned downloadUrl (valid one hour; call the list or get endpoint
again for a fresh one), sizeBytes, counts and skippedVideos. Every image an article shows is
packaged, including ones hosted outside Vidocu (fetched over HTTPS from public hosts, up to 25 MB
each); the few that cannot be fetched stay as online links and are listed in externalImages. The list marks an export stale: true
when the articles, sections or translations it selected have changed since it was built.
Billing
Article translation is charged to the workspace credit balance shown in
GET /v1/usage: kb_article_translate credits per article per target language, on
success only. GET /v1/usage/credits lists the current price. There is no limit on how
many locales a knowledge base enables.
ask and reindex draw on the add-on's own monthly AI credit pool instead. Article reads,
writes and analytics are free.
Errors
| Status | Code | Cause |
|---|---|---|
| 403 | limit_exceeded | The Knowledge Center add-on isn't active on this workspace, or not enough credits to translate |
| 400 | validation_error | Bad parentId: not top-level, wrong knowledge base, or the section has sub-sections |
| 404 | not_found | No knowledge base yet, or the section/article/redirect doesn't exist |
| 409 | conflict | Slug taken, section not empty, a linked video is awaiting approval, or the body still has a [TODO: ...] placeholder |
| 423 | locked | The article is locked |