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.

JSON
{
  "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": []
  }
}
FieldWhat it attachesAdditional scope
workspace_idsWhole libraries. The model searches inside when it needs to.libraries:read
file_idsSpecific files, without pulling their whole library.libraries:read
skill_idsReusable instructions that steer the answer. If they cite other resources, those join too, within the credential's scope (see).skills:read
mcp_server_idsConnectors 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.

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
JSON
{
  "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 in data.
  • 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.

JSON
{
  "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.