Skip to content

Knowledge

Knowledge is the AI assistant's source of truth for everything that isn't a product: shipping policy, return windows, sizing guides, brand story, FAQs. Push it once and the assistant grounds its answers on what you've written, instead of guessing.

Four types of knowledge entries are supported:

  • snippet: short plain-text answer for a single question.
  • article: longer-form HTML content (a blog post, a help-center article).
  • webpage: point Humind at a public URL and we fetch it for you.
  • file: upload a PDF, DOCX, Markdown, or text document. Two-step flow: create the metadata, then upload the file to a pre-signed URL.

The endpoints here cover create, read, update, replace, archive, and batch operations on knowledge entries, including their tags, active periods, and embedded translations.

The Knowledge object

FieldTypeRequiredDescription
external_idstringYes (on create)Your unique identifier for this entry. Free-form, must be unique per company. Used as the upsert key.
humind_idstringReturned only24-char hex ObjectId Humind assigns. Stable for the lifetime of the entry.
type'snippet' | 'article' | 'webpage' | 'file'Yes (on create)Discriminator. Determines which of content, content_html, url, or original_filename + mime_type is required. See type-specific sections below.
titlestringYesShort title used for display, search, and the assistant's grounding context.
tagsstring[]NoTag names. The server resolves them to tag ids and creates any tag that doesn't exist yet. See Tags.
is_available_for_ai_agentbooleanNoDefaults to true. Set to false to keep the entry in your library without exposing it to the assistant.
status'draft' | 'published' | 'archived' | 'queued' | 'failed'NoDefaults to published on create. queued/failed are reserved for webpage fetches, see The webpage type.
active_fromISO 8601NoEarliest date the assistant may use this entry. See Active period.
active_untilISO 8601NoLatest date the assistant may use this entry.
default_languagestringNoISO 639-1 code for the language of the top-level fields.
translationsobject[]NoPer-locale overrides. See Translations.
created_atISO 8601Returned onlyCreation timestamp (UTC).
updated_atISO 8601Returned onlyLast modification timestamp (UTC).

Type-specific fields

The discriminator type decides which body fields are required.

TypeRequiredOptionalNotes
snippetcontent-Plain text. Short, focused answers.
articlecontent_htmlexcerpt, slugHTML body. Sanitized server-side, see HTML content. excerpt and slug auto-derive when omitted.
webpageurlcontent_htmlIf content_html is omitted, Humind fetches the URL asynchronously. See The webpage type.
fileoriginal_filename, mime_type-The actual file is uploaded out-of-band via a pre-signed URL. See The file type.
FieldTypeDescription
contentstringPlain-text body for type='snippet'.
content_htmlstringHTML body for type='article', or a snapshot of the page for type='webpage'.
excerptstringOptional for type='article'. Short summary shown in the dashboard's knowledge list. Max 500 chars. When omitted, the API derives a 160-char excerpt from content_html; published articles always have one. Supply explicitly for a curated tagline.
slugstringOptional for type='article'. URL-safe slug, lowercase, hyphen-separated. Max 255 chars. Derived from title when omitted (lowercase ASCII, non-alphanumerics replaced by hyphens). Same auto-derive policy as excerpt.
urlstringHTTPS URL for type='webpage'. Must be publicly reachable. For live keys, the API rejects http://, userinfo@host syntax (https://example.com@attacker.com), localhost and any *.localhost, the 0.0.0.0 host, RFC 1918 / loopback / link-local / CGN IPv4 ranges, IPv6 loopback (::1) and ULA / link-local IPv6, and Kubernetes internal suffixes (*.svc.cluster.local, *.cluster.local, *.internal) — all return 422 invalid_url. Test keys (hmd_test_*) skip these checks so merchants can develop locally with tunnels or http://localhost.
original_filenamestringDisplay name for type='file'. The API strips directory components (/ and \), control characters, and standalone . / .. segments before storage — only the basename is kept. Final length must be 1–255 chars after sanitization.
mime_typestringMIME type for type='file'. Whitelisted, see The file type.
file_sizeintegerReturned only. Bytes. null while the upload is pending.
file_urlstringReturned only. Opaque relative path; do not parse. null while the upload is pending.
uploaded_atISO 8601Returned only. When the file finished uploading. null until then.

Hidden fields

The dashboard may show fields the public API doesn't return; treat those as internal. You can safely ignore anything you see in the dashboard that isn't documented here.

HTML content

The content_html field, used by the article and webpage types, accepts HTML. Humind sanitizes HTML server-side before storing it, using a fixed allowlist:

  • Tags preserved: p, a, br, hr, em, strong, b, i, u, ul, ol, li, h1h6, blockquote, pre, code, table, thead, tbody, tr, th, td, img, span, div.
  • Tags stripped: script, iframe, style, object, embed, form, input, and any other tag not in the list above.
  • Attributes stripped: event handlers (onclick, onerror, …), style, and any javascript: URL.
  • Attributes preserved: href on <a> (only https, http, mailto, and valid data: URIs), src, alt, width, height on <img>, plus a limited set of class values.

HTML is sanitized on input

Don't rely on the API to round-trip your raw HTML. Tags like <script> or attributes like onclick are stripped silently, your stored markup may be a strict subset of what you sent. Read back the entry after a write if you need to see what was kept. The same rule applies to webpage snapshots Humind fetches on your behalf.


Create or upsert

POST /knowledge creates a new entry, or updates an existing one if the external_id already exists for your company. The required body fields depend on type.

Required scope: knowledge:write

type='snippet'

Request

bash
curl -X POST https://api.thehumind.com/public/v1/knowledge \
  -H "Authorization: Bearer hmd_live_..." \
  -H "Idempotency-Key: 3f1a8d92-7c4b-4e6f-b2a1-d5e9c8f7a3b4" \
  -H "Content-Type: application/json" \
  -d '{
    "external_id": "kb-shipping-faq",
    "type": "snippet",
    "title": "Shipping delays",
    "content": "Standard delivery takes 3-5 business days. Express delivery takes 1-2 business days. Free shipping on orders over 50 EUR.",
    "tags": ["shipping", "faq"],
    "is_available_for_ai_agent": true,
    "status": "published",
    "default_language": "en"
  }'

Response 201 Created or 200 OK

json
{
  "external_id": "kb-shipping-faq",
  "humind_id": "65f1ab9c8e7d4a2b1c3d4e5f",
  "type": "snippet",
  "title": "Shipping delays",
  "content": "Standard delivery takes 3-5 business days. Express delivery takes 1-2 business days. Free shipping on orders over 50 EUR.",
  "tags": ["shipping", "faq"],
  "is_available_for_ai_agent": true,
  "status": "published",
  "default_language": "en",
  "created_at": "2026-04-25T14:30:00Z",
  "updated_at": "2026-04-25T14:30:00Z"
}

type='article'

Request

bash
curl -X POST https://api.thehumind.com/public/v1/knowledge \
  -H "Authorization: Bearer hmd_live_..." \
  -H "Idempotency-Key: 4a5b6c7d-8e9f-4a3b-2c1d-0e9f8a7b6c5d" \
  -H "Content-Type: application/json" \
  -d '{
    "external_id": "kb-sizing-guide-shoes",
    "type": "article",
    "title": "How to pick the right shoe size",
    "content_html": "<p>If you're between two sizes, we recommend going up.</p><p>Our shoes use European sizing. See the chart below to convert from US or UK.</p>",
    "tags": ["sizing", "shoes"],
    "default_language": "en"
  }'

Response 201 Created or 200 OK

json
{
  "external_id": "kb-sizing-guide-shoes",
  "humind_id": "65f1cd9e8e7d4a2b1c3d4e60",
  "type": "article",
  "title": "How to pick the right shoe size",
  "content_html": "<p>If you're between two sizes, we recommend going up.</p><p>Our shoes use European sizing. See the chart below to convert from US or UK.</p>",
  "tags": ["sizing", "shoes"],
  "is_available_for_ai_agent": true,
  "status": "published",
  "default_language": "en",
  "created_at": "2026-04-25T14:30:00Z",
  "updated_at": "2026-04-25T14:30:00Z"
}

type='webpage'

You can either provide both url and content_html (instant publish), or just url and let Humind fetch the page for you (async).

Request, URL only, async fetch

bash
curl -X POST https://api.thehumind.com/public/v1/knowledge \
  -H "Authorization: Bearer hmd_live_..." \
  -H "Idempotency-Key: 7a8b9c0d-1e2f-4a3b-4c5d-6e7f8a9b0c1d" \
  -H "Content-Type: application/json" \
  -d '{
    "external_id": "kb-shipping-policy-page",
    "type": "webpage",
    "title": "Shipping policy",
    "url": "https://merchant.com/shipping-policy",
    "tags": ["shipping", "policy"],
    "default_language": "en"
  }'

Response 202 Accepted (fetch queued)

json
{
  "external_id": "kb-shipping-policy-page",
  "humind_id": "65f1ef0e8e7d4a2b1c3d4e61",
  "type": "webpage",
  "title": "Shipping policy",
  "url": "https://merchant.com/shipping-policy",
  "tags": ["shipping", "policy"],
  "is_available_for_ai_agent": true,
  "status": "queued",
  "default_language": "en",
  "created_at": "2026-04-25T14:30:00Z",
  "updated_at": "2026-04-25T14:30:00Z"
}

The entry exists right away with status: "queued". Humind fetches the URL in the background and flips status to published (or failed) once the fetch completes. Until then, the entry is not used by the assistant. See The webpage type for polling.

Request, URL with content_html (instant publish)

bash
curl -X POST https://api.thehumind.com/public/v1/knowledge \
  -H "Authorization: Bearer hmd_live_..." \
  -H "Idempotency-Key: 8b9c0d1e-2f3a-4b4c-5d6e-7f8a9b0c1d2e" \
  -H "Content-Type: application/json" \
  -d '{
    "external_id": "kb-returns-policy-page",
    "type": "webpage",
    "title": "Returns policy",
    "url": "https://merchant.com/returns",
    "content_html": "<p>You can return any unworn item within 30 days of delivery.</p>",
    "tags": ["returns", "policy"],
    "default_language": "en"
  }'

Response 201 Created

Returned with status: "published" immediately, no fetch is performed because you supplied the snapshot.

200 vs 201 vs 202

  • 201 Created: new entry, ready to use.
  • 200 OK: existing entry updated in place.
  • 202 Accepted: webpage entry created, fetch is queued. Poll the entry to watch status transition.

The webpage type

The webpage type is the path of least resistance for content you already publish on your site. You give Humind a URL; we fetch the page, strip the navigation chrome, and snapshot the body.

Lifecycle

statusMeaning
queuedHumind hasn't fetched the URL yet. The entry exists but isn't used by the assistant.
publishedFetch succeeded. content_html is populated and the assistant grounds answers on it.
failedFetch failed (404, blocked by robots.txt, server error, timeout, etc.). The entry is kept so you can see the failure; the assistant does not use it.

To re-trigger a fetch after a failed status, fix the issue on your side, then PATCH the entry with the same url (or a new one). The status returns to queued.

Polling

There's no webhook for fetch completion. Poll the entry by external_id or humind_id:

bash
curl https://api.thehumind.com/public/v1/knowledge/api:kb-shipping-policy-page \
  -H "Authorization: Bearer hmd_live_..."

Most pages publish within a minute. If status is still queued after a few minutes, check your URL is publicly reachable.

Static snapshot vs live fetch

The content_html Humind stores is a point-in-time snapshot. It does not refresh automatically when your page changes. To pick up edits on your site, PATCH the entry, sending the same url (with no content_html) re-queues a fresh fetch.


List knowledge

GET /knowledge returns the entries that belong to the company the API key is bound to.

Required scope: knowledge:read

Query parameterTypeDescription
cursorstringOpaque cursor returned as next_cursor in the previous page. Omit on the first call.
limitintegerItems per page (1100). Defaults to 50.
type'snippet' | 'article' | 'webpage' | 'file'Filter to a single type.

Request

bash
curl "https://api.thehumind.com/public/v1/knowledge?type=snippet&limit=50" \
  -H "Authorization: Bearer hmd_live_..."

Response 200 OK

json
{
  "data": [
    {
      "external_id": "kb-shipping-faq",
      "humind_id": "65f1ab9c8e7d4a2b1c3d4e5f",
      "type": "snippet",
      "title": "Shipping delays",
      "tags": ["shipping", "faq"],
      "status": "published",
      "is_available_for_ai_agent": true,
      "created_at": "2026-04-25T14:30:00Z",
      "updated_at": "2026-04-25T14:30:00Z"
    }
  ],
  "next_cursor": "eyJpZCI6IjY1ZjFjZDllOGU3ZDRhMmIxYzNkNGU2MCJ9"
}

When next_cursor is null or missing, you've reached the last page.


Retrieve a knowledge entry

GET /knowledge/{id} returns a single entry. The {id} accepts either form documented in Identifiers.

Required scope: knowledge:read

Request

bash
curl https://api.thehumind.com/public/v1/knowledge/api:kb-shipping-faq \
  -H "Authorization: Bearer hmd_live_..."

Response 200 OK

Same shape as the create response.


Replace a knowledge entry

PUT /knowledge/{id} performs a full replace: the request body becomes the entire entry. Fields you don't include are reset to their defaults (or unset where applicable).

Required scope: knowledge:write

Request

bash
curl -X PUT https://api.thehumind.com/public/v1/knowledge/api:kb-shipping-faq \
  -H "Authorization: Bearer hmd_live_..." \
  -H "Idempotency-Key: 7a2b3c4d-5e6f-4a8b-9c0d-1e2f3a4b5c6d" \
  -H "Content-Type: application/json" \
  -d '{
    "external_id": "kb-shipping-faq",
    "type": "snippet",
    "title": "Shipping delays (updated)",
    "content": "Standard delivery now takes 2-4 business days. Express delivery is 1 business day. Free shipping on orders over 50 EUR.",
    "tags": ["shipping", "faq"],
    "default_language": "en"
  }'

Response 200 OK

Returns the full entry, same shape as the create response.

Replace is destructive

PUT removes any field, tag, or translation not present in the body. To bump a single field, use PATCH instead.


Update a knowledge entry

PATCH /knowledge/{id} performs a partial update: only the fields present in the body are modified. Everything else stays as it was.

Required scope: knowledge:write

Request, refresh the content of a snippet

bash
curl -X PATCH https://api.thehumind.com/public/v1/knowledge/api:kb-shipping-faq \
  -H "Authorization: Bearer hmd_live_..." \
  -H "Idempotency-Key: 9b8a7c6d-5e4f-4a3b-2c1d-0e9f8a7b6c5d" \
  -H "Content-Type: application/json" \
  -d '{
    "content": "Standard delivery takes 2-4 business days. Express is 1 business day."
  }'

Request, re-fetch a webpage

bash
curl -X PATCH https://api.thehumind.com/public/v1/knowledge/api:kb-shipping-policy-page \
  -H "Authorization: Bearer hmd_live_..." \
  -H "Idempotency-Key: ab3c4d5e-6f78-4a9b-8c0d-1e2f3a4b5c6d" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://merchant.com/shipping-policy"
  }'

PATCH-ing a webpage entry with a url re-queues a fetch and flips status back to queued.

Response 200 OK

Returns the full updated entry.

Type changes are not allowed

You can't switch a snippet to an article (or any other cross-type change) by PATCH-ing type. Delete the entry and create a new one with the right type.


Archive (or delete) a knowledge entry

DELETE /knowledge/{id} performs a soft delete by default: the entry's status is set to archived and the assistant stops using it. The record itself stays so you can restore it later by PATCH-ing the status back to published.

Pass ?force=true to hard delete the entry instead — the document is removed from the database after the archive cascade runs. There is no un-delete; only use this when you really want the entry gone (e.g. test knowledge cleanup, or an entry that was created by mistake).

Required scope: knowledge:write

Query parameters

NameTypeRequiredDescription
forcebooleanNoTruthy values (any case): true, 1, yes, on. Falsy values: false, 0, no, off, or omitted. Anything else returns 400 validation_failed. When truthy, permanently removes the entry. Default is soft delete.

Request — soft delete (default)

bash
curl -X DELETE https://api.thehumind.com/public/v1/knowledge/api:return-policy \
  -H "Authorization: Bearer hmd_live_..." \
  -H "Idempotency-Key: 1a2b3c4d-5e6f-4789-9abc-def012345678"

Request — hard delete

bash
curl -X DELETE 'https://api.thehumind.com/public/v1/knowledge/api:return-policy?force=true' \
  -H "Authorization: Bearer hmd_live_..." \
  -H "Idempotency-Key: 1a2b3c4d-5e6f-4789-9abc-def012345678"

Response 204 No Content

No body.

Soft vs. hard delete

Soft delete is recommended for normal merchant operations: the entry disappears from chat retrieval immediately, you can un-archive in one PATCH, and downstream caches (vector index, dashboards) tear down cleanly via the archive cascade.

Hard delete (?force=true) is for knowledge-base cleanup — typically a test or sandbox entry you want fully gone. Soft delete first triggers the archive routine; ?force=true then permanently removes the resource. Past chat conversations that referenced this entry keep their snapshot, so history stays coherent even after the source is gone.


Tags

tags is an array of free-form names. The server resolves each name to a tag id, creating the tag if it doesn't already exist for your company:

json
{
  "tags": ["shipping", "faq", "europe"]
}

You don't pre-create tags via a separate endpoint, the first time you reference a tag name, it's created. The assistant uses tags as a soft grouping signal when retrieving relevant knowledge for a question.

Keep tag names stable

Renaming a tag means creating a new one and orphaning the old. If you regularly change taxonomy, consider tagging by stable concepts (shipping) rather than transient ones (q1-2026-promo).

If a tag name fails to resolve (rare), the API returns tag_resolution_failed with the offending name in details.tag.


Active period

Set active_from and/or active_until to scope when the assistant may surface an entry. The record stays in your library year-round; the date window only affects whether the assistant grounds on it.

Use caseRecommended setup
Seasonal promotion (Black Friday FAQ)active_from = "2026-11-25T00:00:00Z", active_until = "2026-12-02T23:59:59Z"
Future-dated launchactive_from only, leave active_until unset.
Sunsetting policyactive_until only, assistant stops using it after the cutoff.

Outside the window, the entry is treated as if is_available_for_ai_agent were false. The status (published, archived, …) is not changed.


Translations

translations is a list of per-locale overrides keyed by language:

json
{
  "default_language": "en",
  "title": "Shipping delays",
  "content": "Standard delivery takes 3-5 business days.",
  "translations": [
    {
      "language": "fr",
      "title": "Délais de livraison",
      "content": "La livraison standard prend 3-5 jours ouvrés."
    }
  ]
}

Per-locale fields you can override:

FieldTypeApplies to
titlestringAll types
contentstringsnippet
content_htmlstringarticle, webpage

Locales not listed in translations fall back to the top-level (default_language) value.

You can patch one locale at a time without resending the full entry via the dedicated Translations sub-resource.


Translations sub-resource

For knowledge entries that already exist, you can patch a single locale without resending the full payload. Use the embedded translations field on POST / PUT / PATCH for the initial seed, then drive ongoing updates through these endpoints.

{lang} is a BCP 47 short tag, en, fr, de, pt-BR, zh-CN, etc. The validation regex is ^[a-z]{2}(-[A-Z]{2})?$. An invalid value returns 422 with code: invalid_lang_format.

The body shape depends on the entry's type. The server validates against the matching schema; sending content on an article (or content_html on a snippet) returns 422.

Set or update a translation

PUT /knowledge/{id}/translations/{lang} upserts the translation for a single locale. Fields you omit are left untouched on an update; on first write, they default to unset.

Required scope: knowledge:write

Body, type='snippet'

FieldTypeDescription
titlestringLocalized display title.
contentstringLocalized plain-text body.

Body, type='article' or type='webpage'

FieldTypeDescription
titlestringLocalized display title.
content_htmlstringLocalized HTML body. Sanitized, see HTML content.

All fields are optional individually, but the body must contain at least one. An empty body returns 400 validation_failed.

Request, snippet

bash
curl -X PUT https://api.thehumind.com/public/v1/knowledge/api:kb-shipping-faq/translations/fr \
  -H "Authorization: Bearer hmd_live_..." \
  -H "Idempotency-Key: 5f6a7b8c-9d0e-4f1a-2b3c-4d5e6f7a8b9c" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Délais de livraison",
    "content": "La livraison standard prend 3-5 jours ouvrés."
  }'

Request, article or webpage

bash
curl -X PUT https://api.thehumind.com/public/v1/knowledge/api:kb-sizing-guide-shoes/translations/fr \
  -H "Authorization: Bearer hmd_live_..." \
  -H "Idempotency-Key: 6a7b8c9d-0e1f-4a2b-3c4d-5e6f7a8b9c0d" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Comment choisir la bonne pointure",
    "content_html": "<p>Si vous hésitez entre deux tailles, on recommande de prendre la plus grande.</p>"
  }'

Response 200 OK

Returns the full knowledge entry, the same shape as the create response, so you can see every locale currently stored, not just the one you wrote.

Remove a translation

DELETE /knowledge/{id}/translations/{lang} removes the translation for a single locale. Idempotent: deleting a locale that doesn't exist is a no-op, not an error.

Required scope: knowledge:write

Request

bash
curl -X DELETE https://api.thehumind.com/public/v1/knowledge/api:kb-shipping-faq/translations/fr \
  -H "Authorization: Bearer hmd_live_..." \
  -H "Idempotency-Key: 7b8c9d0e-1f2a-4b3c-4d5e-6f7a8b9c0d1e"

Response 204 No Content

No body.


The file type

type='file' lets you push a PDF, Word document, Markdown, plain text, or HTML file as a knowledge entry. Because the payload doesn't fit a JSON request body, the workflow is split into three steps:

1. POST /knowledge                              ──►  status='draft', file_url=null
   { type: 'file', original_filename, mime_type }

2. POST /knowledge/{id}/file-upload-url         ──►  { upload_url, expires_at }

3. PUT  $UPLOAD_URL  --data-binary @file.pdf    ──►  Azure Blob (no auth, just the SAS)

4. POST /knowledge/{id}/file-upload-completed   ──►  status='published',
                                                     file_size + uploaded_at populated

The first call records metadata only, no file is stored yet. The SAS URL minted in step 2 is write-only and expires after 1 hour. Step 3 is a direct PUT to Azure Blob storage; you don't touch Humind's API. Step 4 verifies the blob landed and flips status to published so the chat agent can use it.

Step 1, create the entry

bash
curl -X POST https://api.thehumind.com/public/v1/knowledge \
  -H "Authorization: Bearer hmd_live_..." \
  -H "Idempotency-Key: 9c8b7a6d-5e4f-4a3b-2c1d-0e9f8a7b6c5d" \
  -H "Content-Type: application/json" \
  -d '{
    "external_id": "kb-sizing-chart-pdf",
    "type": "file",
    "title": "Detailed sizing chart (PDF)",
    "original_filename": "sizing-chart.pdf",
    "mime_type": "application/pdf",
    "tags": ["sizing"],
    "default_language": "en"
  }'

The response comes back with status: "draft", file_url: null, file_size: null, uploaded_at: null. The entry is not used by the assistant until step 4 completes.

Step 2, get an upload URL

bash
curl -X POST https://api.thehumind.com/public/v1/knowledge/api:kb-sizing-chart-pdf/file-upload-url \
  -H "Authorization: Bearer hmd_live_..." \
  -H "Idempotency-Key: 0d9e8f7a-6b5c-4d3e-2f1a-9b8c7d6e5f4a"

Response 200 OK

json
{
  "upload_url": "https://<storage>.blob.core.windows.net/<container>/<opaque-path>/sizing-chart.pdf?sig=...&se=...",
  "expires_at": "2026-04-25T15:30:00Z",
  "blob_path": "<opaque-path>/sizing-chart.pdf"
}

Calling this endpoint on a knowledge entry that isn't type='file' returns 422 wrong_type.

Step 3, PUT the file

Upload the bytes directly to upload_url. No Authorization header, the SAS in the query string is the auth.

bash
curl -X PUT "$UPLOAD_URL" \
  -H "x-ms-blob-type: BlockBlob" \
  -H "Content-Type: application/pdf" \
  --data-binary @sizing-chart.pdf

Azure returns 201 Created on success. The SAS URL has only Create + Write permissions, it can't read or list anything else.

Step 4, finalise

bash
curl -X POST https://api.thehumind.com/public/v1/knowledge/api:kb-sizing-chart-pdf/file-upload-completed \
  -H "Authorization: Bearer hmd_live_..." \
  -H "Idempotency-Key: 1e0f9a8b-7c6d-4e5f-2a1b-0c9d8e7f6a5b"

Response 200 OK

The full updated entry, with status: "published", file_size populated, and a fresh uploaded_at. From this point on, the chat agent can ground answers on the document.

If you call this endpoint before step 3 finished (or if the upload failed silently), you get:

json
{
  "error": {
    "code": "file_not_uploaded",
    "message": "No file has been uploaded to the SAS URL yet. PUT your file before calling this endpoint."
  }
}

Just retry once the PUT completes. The endpoint is idempotent, calling it again after a successful upload re-publishes the entry with a fresh uploaded_at.

Allowed MIME types

mime_typeExtension
application/pdf.pdf
application/vnd.openxmlformats-officedocument.wordprocessingml.document.docx
application/msword.doc (legacy)
text/plain.txt
text/markdown.md
text/html.html
application/rtf.rtf

Anything else returns validation_failed at step 1.

Limits

LimitValue
original_filename length255 chars
File sizeNo hard cap. Recommend keeping individual files under 50 MB so the chat agent can fetch them within the request budget.
Upload URL TTL1 hour. Mint a new one if it expires.

OCR / text extraction

Born-digital PDFs and text-based formats work best. For OCR or scanned PDFs, contact support.


Batch upsert

POST /knowledge/batch accepts up to 500 entries in a single call and returns a 207 Multi-Status response with one entry per item. Use this for the initial sync of your knowledge base or any bulk update.

Required scope: knowledge:write

ConstraintValue
Max items per batch500
Max body size5 MB
BehaviourEach item is processed independently; one failure doesn't block the rest.

Request

bash
curl -X POST https://api.thehumind.com/public/v1/knowledge/batch \
  -H "Authorization: Bearer hmd_live_..." \
  -H "Idempotency-Key: 4d3c2b1a-9f8e-4d7c-6b5a-4f3e2d1c0b9a" \
  -H "Content-Type: application/json" \
  -d '{
    "items": [
      {
        "external_id": "kb-shipping-faq",
        "type": "snippet",
        "title": "Shipping delays",
        "content": "Standard delivery takes 3-5 business days.",
        "tags": ["shipping", "faq"],
        "default_language": "en"
      },
      {
        "external_id": "kb-returns-page",
        "type": "webpage",
        "title": "Returns policy",
        "url": "https://merchant.com/returns",
        "default_language": "en"
      },
      {
        "external_id": "kb-bad",
        "type": "snippet",
        "title": "Missing content"
      }
    ]
  }'

Response 207 Multi-Status

json
{
  "results": [
    {
      "external_id": "kb-shipping-faq",
      "status": "created",
      "humind_id": "65f1ab9c8e7d4a2b1c3d4e5f"
    },
    {
      "external_id": "kb-returns-page",
      "status": "created",
      "humind_id": "65f1cd9e8e7d4a2b1c3d4e60",
      "queued_for_fetch": true
    },
    {
      "external_id": "kb-bad",
      "status": "failed",
      "error": {
        "code": "validation_failed",
        "message": "Invalid knowledge payload.",
        "details": {
          "issues": [
            { "path": ["content"], "message": "Required", "code": "invalid_type" }
          ]
        }
      }
    }
  ]
}
FieldTypeDescription
results[i].external_idstringThe external_id of the input item, echoed for matching.
results[i].statusstringOne of created, updated, failed.
results[i].humind_idstringPresent on success — the assigned 24-hex Humind id.
results[i].queued_for_fetchbooleanOptional, only on webpage items where the URL fetch was queued.
results[i].errorobjectPresent on failure. Same shape as a top-level error object.

Duplicate external_id within a batch

If the same external_id appears more than once in items, the first occurrence is processed and later duplicates come back with status: "failed" and code duplicate_external_id_in_batch. Deduplicate client-side before sending.

Wrapper or bare array

The body may be a JSON array as shown above, or wrapped: { "items": [ ... ] }. The API normalises both to the same results response.

Idempotency on batches

The Idempotency-Key applies to the whole batch. A retry of the same batch with the same key replays the entire 207 response, no items are processed twice. Generate a new UUID for a different batch.


Common errors

The knowledge endpoints can return any of the standard HTTP statuses, but these are the ones you'll see most often:

StatusCodeWhenFix
401missing_credentials, invalid_key_format, invalid_key, revokedAuth header is missing, malformed, or the key is no longer active.See Authentication.
403insufficient_scopeKey lacks knowledge:read (for GET) or knowledge:write (for POST/PUT/PATCH/DELETE).Create a new key with the right scope.
404not_foundThe {id} doesn't match an entry owned by your company.Confirm the external_id or humind_id. Cross-tenant lookups also return 404.
409idempotency_conflictSame Idempotency-Key reused with a different body.Generate a fresh UUID.
400validation_failedBody fails the Knowledge schema: most often a missing type-specific field (content for snippet, content_html for article, url for webpage, original_filename/mime_type for file).Fix the field and resend.
422wrong_typeCalled /file-upload-url or /file-upload-completed on a knowledge entry that isn't type='file'.Use a file entry, or remove the call.
422file_not_uploadedCalled /file-upload-completed before successfully PUT-ing the bytes to upload_url.Complete step 3 (the PUT) first, then retry step 4.
422tag_resolution_failedA tag name in tags couldn't be resolved or created. Rare.Retry; if it persists, share the request_id with support.
422invalid_lang_formatThe {lang} segment of a translation sub-resource URL doesn't match ^[a-z]{2}(-[A-Z]{2})?$.Use a BCP 47 short tag like en, fr, pt-BR.
200/202 (entry shows status='failed')webpage_fetch_failedHumind tried to fetch a webpage URL and the page returned an error, was blocked by robots.txt, timed out, or didn't return HTML.Verify the URL is reachable, then PATCH the entry with the same (or a fixed) url to re-queue.

Next

Released under the proprietary Humind license.