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
| Field | Type | Required | Description |
|---|---|---|---|
external_id | string | Yes (on create) | Your unique identifier for this entry. Free-form, must be unique per company. Used as the upsert key. |
humind_id | string | Returned only | 24-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. |
title | string | Yes | Short title used for display, search, and the assistant's grounding context. |
tags | string[] | No | Tag names. The server resolves them to tag ids and creates any tag that doesn't exist yet. See Tags. |
is_available_for_ai_agent | boolean | No | Defaults to true. Set to false to keep the entry in your library without exposing it to the assistant. |
status | 'draft' | 'published' | 'archived' | 'queued' | 'failed' | No | Defaults to published on create. queued/failed are reserved for webpage fetches, see The webpage type. |
active_from | ISO 8601 | No | Earliest date the assistant may use this entry. See Active period. |
active_until | ISO 8601 | No | Latest date the assistant may use this entry. |
default_language | string | No | ISO 639-1 code for the language of the top-level fields. |
translations | object[] | No | Per-locale overrides. See Translations. |
created_at | ISO 8601 | Returned only | Creation timestamp (UTC). |
updated_at | ISO 8601 | Returned only | Last modification timestamp (UTC). |
Type-specific fields
The discriminator type decides which body fields are required.
| Type | Required | Optional | Notes |
|---|---|---|---|
snippet | content | - | Plain text. Short, focused answers. |
article | content_html | excerpt, slug | HTML body. Sanitized server-side, see HTML content. excerpt and slug auto-derive when omitted. |
webpage | url | content_html | If content_html is omitted, Humind fetches the URL asynchronously. See The webpage type. |
file | original_filename, mime_type | - | The actual file is uploaded out-of-band via a pre-signed URL. See The file type. |
| Field | Type | Description |
|---|---|---|
content | string | Plain-text body for type='snippet'. |
content_html | string | HTML body for type='article', or a snapshot of the page for type='webpage'. |
excerpt | string | Optional 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. |
slug | string | Optional 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. |
url | string | HTTPS 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_filename | string | Display 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_type | string | MIME type for type='file'. Whitelisted, see The file type. |
file_size | integer | Returned only. Bytes. null while the upload is pending. |
file_url | string | Returned only. Opaque relative path; do not parse. null while the upload is pending. |
uploaded_at | ISO 8601 | Returned 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, h1–h6,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 anyjavascript:URL. - Attributes preserved:
hrefon<a>(onlyhttps,http,mailto, and validdata:URIs),src,alt,width,heighton<img>, plus a limited set ofclassvalues.
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
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
{
"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
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
{
"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
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)
{
"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)
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 watchstatustransition.
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
status | Meaning |
|---|---|
queued | Humind hasn't fetched the URL yet. The entry exists but isn't used by the assistant. |
published | Fetch succeeded. content_html is populated and the assistant grounds answers on it. |
failed | Fetch 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:
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 parameter | Type | Description |
|---|---|---|
cursor | string | Opaque cursor returned as next_cursor in the previous page. Omit on the first call. |
limit | integer | Items per page (1–100). Defaults to 50. |
type | 'snippet' | 'article' | 'webpage' | 'file' | Filter to a single type. |
Request
curl "https://api.thehumind.com/public/v1/knowledge?type=snippet&limit=50" \
-H "Authorization: Bearer hmd_live_..."Response 200 OK
{
"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
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
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
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
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
| Name | Type | Required | Description |
|---|---|---|---|
force | boolean | No | Truthy 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)
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
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:
{
"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 case | Recommended setup |
|---|---|
| Seasonal promotion (Black Friday FAQ) | active_from = "2026-11-25T00:00:00Z", active_until = "2026-12-02T23:59:59Z" |
| Future-dated launch | active_from only, leave active_until unset. |
| Sunsetting policy | active_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:
{
"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:
| Field | Type | Applies to |
|---|---|---|
title | string | All types |
content | string | snippet |
content_html | string | article, 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'
| Field | Type | Description |
|---|---|---|
title | string | Localized display title. |
content | string | Localized plain-text body. |
Body, type='article' or type='webpage'
| Field | Type | Description |
|---|---|---|
title | string | Localized display title. |
content_html | string | Localized 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
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
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
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 populatedThe 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
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
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
{
"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.
curl -X PUT "$UPLOAD_URL" \
-H "x-ms-blob-type: BlockBlob" \
-H "Content-Type: application/pdf" \
--data-binary @sizing-chart.pdfAzure returns 201 Created on success. The SAS URL has only Create + Write permissions, it can't read or list anything else.
Step 4, finalise
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:
{
"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_type | Extension |
|---|---|
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
| Limit | Value |
|---|---|
original_filename length | 255 chars |
| File size | No hard cap. Recommend keeping individual files under 50 MB so the chat agent can fetch them within the request budget. |
| Upload URL TTL | 1 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
| Constraint | Value |
|---|---|
| Max items per batch | 500 |
| Max body size | 5 MB |
| Behaviour | Each item is processed independently; one failure doesn't block the rest. |
Request
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
{
"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" }
]
}
}
}
]
}| Field | Type | Description |
|---|---|---|
results[i].external_id | string | The external_id of the input item, echoed for matching. |
results[i].status | string | One of created, updated, failed. |
results[i].humind_id | string | Present on success — the assigned 24-hex Humind id. |
results[i].queued_for_fetch | boolean | Optional, only on webpage items where the URL fetch was queued. |
results[i].error | object | Present 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:
| Status | Code | When | Fix |
|---|---|---|---|
401 | missing_credentials, invalid_key_format, invalid_key, revoked | Auth header is missing, malformed, or the key is no longer active. | See Authentication. |
403 | insufficient_scope | Key lacks knowledge:read (for GET) or knowledge:write (for POST/PUT/PATCH/DELETE). | Create a new key with the right scope. |
404 | not_found | The {id} doesn't match an entry owned by your company. | Confirm the external_id or humind_id. Cross-tenant lookups also return 404. |
409 | idempotency_conflict | Same Idempotency-Key reused with a different body. | Generate a fresh UUID. |
400 | validation_failed | Body 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. |
422 | wrong_type | Called /file-upload-url or /file-upload-completed on a knowledge entry that isn't type='file'. | Use a file entry, or remove the call. |
422 | file_not_uploaded | Called /file-upload-completed before successfully PUT-ing the bytes to upload_url. | Complete step 3 (the PUT) first, then retry step 4. |
422 | tag_resolution_failed | A tag name in tags couldn't be resolved or created. Rare. | Retry; if it persists, share the request_id with support. |
422 | invalid_lang_format | The {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_failed | Humind 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
- Authentication: generate a
knowledge:writekey. - Conventions: identifiers, idempotency, dates.
- Products: push your catalog so the assistant can recommend.
- Collections: group products for narrower recommendations.