Documentation
Attaching resources
Giving the turn libraries, files, skills and connectors — and knowing which ones you can.
A turn with no attachments uses the model's knowledge and the organization's rules. With attachments it also receives your context: documents to search, instructions to follow, tools to use.
{
"content": "Resumí la política de devoluciones y redactá una respuesta al cliente.",
"permission_mode": "auto",
"attachments": {
"workspace_ids": [31],
"file_ids": [],
"skill_ids": [147],
"mcp_server_ids": []
}
}| Field | What it attaches | Additional scope |
|---|---|---|
workspace_ids | Whole libraries. The model searches inside when it needs to. | libraries:read |
file_ids | Specific files, without pulling their whole library. | libraries:read |
skill_ids | Reusable instructions that steer the answer. If they cite other resources, those join too, within the credential's scope (see). | skills:read |
mcp_server_ids | Connectors whose tools the turn will be able to run. | connectors:use |
Heads up
chat:write authorizes conversing, not reading whatever gets attached. Without the companion scope the turn is rejected with 403 api_token_scope_not_allowed, and data.required_scopes names the missing ones. The check runs before the idempotency reservation, so it does not burn your key.
What you can attach
Do not guess the ids. GET /chat/catalog returns exactly what this credential can use, already narrowed by the user's permissions and the credential's scope.
curl -sS 'https://api.niucore.com/api/v1/chat/catalog' \
-H "Authorization: Bearer $NIUCORE_ACCESS_TOKEN" | jq{
"models": [
{ "id": 17, "name": "GPT-4o", "code": "gpt-4o", "type": "COMPLETION" }
],
"permission_modes": ["manual", "auto"],
"libraries": [{ "id": 31, "name": "Procedimientos de soporte" }],
"skills": [{ "id": 147, "name": "Redactar respuesta a cliente" }],
"connectors": [{ "id": 8, "name": "gmail", "display_name": "Gmail" }]
}Note
Blocks are scope-conditional: without libraries:read the libraries key is absent — not empty, absent. An empty list would read as “there are none”, and that would be false. Code with catalog.libraries ?? [].
Choosing the model
model_id travels per turn. Without it the chat's model is used, and without that, the company's. Valid ids come from catalog.models, which only carries COMPLETION models: an embedding model never appears there, because sending one would produce a turn that fails downstream.
Limits
- Up to 20 ids per type and 50 in total. Exceeding it returns
422 api_v1_too_many_attachments, with the offending field indata. - An id outside the credential's scope also rejects the turn, and likewise does not consume idempotency.
- Attaching a lot is not always better: each library adds context the model must wade through, and that is paid in tokens.
Connectors
Attaching an mcp_server_id enables that connector's tools for the turn — but only those the user already connected from the application work. If the model tries an unauthorized one, the turn stops with a message.action_required of kind auth_required, and from the API the only way out is to cancel it.
Tip
With connectors, permission_mode: "manual" is worth it: a Gmail or Calendar tool has effects outside NiuCore, and approving it explicitly is what prevents surprises. See Interactions.
Your own metadata
metadata is a free-form object stored with the turn and echoed verbatim in the response and in the message listing. It never reaches the model: it is for correlating on your side. 2048 serialized bytes max.
{
"content": "…",
"metadata": {
"ticket": "8842",
"channel": "whatsapp",
"customer_id": "c-91021"
}
}It is the recommended way to tie a NiuCore turn to a record of yours: GET /chats/{id}/messages returns it on every message, so you can rebuild traceability without maintaining a mapping table.