Skip to main content

AI assistants

The copilot is a chat assistant for your team. A user asks for work in plain language, such as drafting a quote or importing a supplier file. The copilot answers, and when it wants to change data it does not write anything. It posts a proposal card that describes the change. A person approves the card, and only then can the change be applied.

That split is the whole model. Approving a card is one step and applying it is another, with its own permission. This guide covers chats (sessions), proposals, approvals and the apply and undo steps. The copilot also keeps saved prompts, a playbook of standing instructions, supplier import rules, scheduled tasks and memory. Those are listed in the API reference and are not covered here.

The examples use <...> for values you supply. For the exact field list of every type, see the API reference.

1. Before you start​

The copilot needs the catalog copilot feature on your plan. Without it, every operation here is refused. Each operation also needs one of these permissions:

PermissionWhat it allows
catalog:copilot:chatOpen and manage your own chats, send messages, edit, approve and reject cards, and read the copilot's settings
catalog:copilot:parseParse an uploaded supplier file, and build an image-sourcing proposal
catalog:copilot:applyApply an approved card, and retry the failed items of a batch
catalog:copilot:rollbackUndo an applied batch
settings:organization:updateChange the organization's autonomy cap, playbook and saved prompts

Every chat belongs to the user who opened it. A colleague's chat, a chat of another organization and an unknown ID all read as not found. Writes are rate limited per user. Approving, rejecting and editing cards allow 30 requests a minute, applying, retrying and undoing allow 5, and starting a parse or image search allows 10.

The copilot never has more power than the person using it. When a card is applied, the write is checked against that person's own permissions as well as the apply permission, so a user who cannot create a quote by hand cannot get the copilot to create one.

2. Chats​

A chat is a CopilotSession. It holds the messages, the proposal cards the copilot made, and whether a reply is being written right now.

OperationWhat it does
createCopilotSessionStarts a new chat.
copilotSessions(first, after)Lists your chats, most recent first.
copilotSessionHistory(first, after, search, status)Lists your chats for a history view: pinned first, then most recent. search matches titles and message text. status is ACTIVE (the default) or ARCHIVED.
copilotSession(id)Loads one chat with its newest messages and all its cards.
copilotSessionMessages(sessionId, before, first)Reads older messages, oldest first, when copilotSession did not return them all.
renameCopilotSession, pinCopilotSessionChange the title (at most 120 characters), or pin a chat to the top.
archiveCopilotSession, unarchiveCopilotSessionMove a chat out of the main list, or back into it.
deleteCopilotSessionRemoves the chat from every list. Its cards and the record of what was applied stay as an audit trail.
rateCopilotMessage(id, messageId, rating, note)Rates an assistant message as up or down, with an optional note.
query Chat($id: ID!) {
copilotSession(id: $id) {
id
title
runActive
activeRunId
messages { id role text cardIds }
proposals { id kind status payload error batchId }
}
}

3. Send a message​

Sending a message is a streaming call, so it is a REST endpoint and not a GraphQL operation. The rest of the copilot is GraphQL.

CallPurpose
POST /catalog-copilot/sessions/<session-id>/messagesSend a message and read the reply as a server-sent event stream.
GET /catalog-copilot/runs/<run-id>/events?after=<seq>Replay the events a client missed, for example after a dropped connection, and read the run's status.
POST /catalog-copilot/runs/<run-id>/cancelStop a reply.
POST /catalog-copilot/sessions/<session-id>/filesUpload a file for the chat. A supplier file needs catalog:copilot:parse. An image only needs catalog:copilot:chat.
GET /catalog-copilot/capabilitiesRead what this organization's copilot accepts.

Authenticate these calls with the same bearer token as GraphQL. The message body takes message, an optional requestId (a UUID you generate), optional attachments (each with the fileKey the upload returned), and optional modelOverride. The stream carries these events: token, card, question, activity, thinking, usage, done and error. Ignore event names you do not know.

Sending a new message on a chat stops the reply that was still running there. While a reply is running, copilotSession reports runActive: true and the run's ID in activeRunId. A client that reloads can use that ID to reattach.

4. Review a proposal​

Each card in proposals has a kind (what it does, for example a quote draft or a stock transfer), a payload (the change itself), and a status:

StatusMeaning
PROPOSEDWaiting for a decision.
REPRICE_REQUIREDPrices changed since the card was made. It behaves like PROPOSED, and approving it accepts the fresh numbers.
APPROVEDA person approved it. It is ready to apply.
APPLYINGThe change is being made.
APPLIEDThe change was made. batchId names the batch.
FAILEDApplying failed. error says why.
REJECTEDA person turned it down.
SUPERSEDEDThe copilot replaced it with a newer card. supersededById names the new one.

To change a card before you decide, call updateCopilotProposal(sessionId, input) with proposalId and a new payload. The card is checked again and goes back to PROPOSED, so an edit always needs a fresh approval. A card can be edited while it is PROPOSED, APPROVED or FAILED (and REPRICE_REQUIRED for a quote draft). Editing a quote draft prices it again on the server, as the caller, and replaces the payload with the result: you send the changed lines, not the totals.

mutation Edit($sessionId: ID!, $input: UpdateCopilotProposalInput!) {
updateCopilotProposal(sessionId: $sessionId, input: $input) {
id
status
payload
}
}

5. Approve or reject​

approveCopilotProposal(sessionId, proposalId) approves a card as the signed-in person. It writes nothing. It only moves the card to APPROVED.

It is refused when:

  • The card was superseded. The message names the newer card.
  • The card carries blockers, which are reasons the change would be refused if it were applied. Edit the card until the blockers clear.
  • The card is in a status that cannot be approved. Only PROPOSED, REPRICE_REQUIRED and FAILED cards can be approved.
  • The card is FAILED but its earlier attempt already created records. Undo that batch first, or applying again would create them twice.
  • A critical operation needs a confirmation phrase typed in the chat, and it was not typed. The error code is TYPED_CONFIRMATION_REQUIRED.
  • The card changes a role. That needs the confirmation argument set to the role's exact name, and the approver must hold every permission the change uses.
  • The card is being applied right now. The error is a conflict that says when to retry.

rejectCopilotProposal(sessionId, proposalId, reason, note) turns a card down. reason is one of: wrong customer, wrong items, wrong quantity or unit, wrong price, wrong details, not needed, or other. An unknown reason is refused. note is optional and is shortened above 280 characters. The reason and note help the copilot learn what to do differently. A rejected card that has no reason can take one later, and the first reason stays.

Standing approval for low-risk cards​

Some cards are low risk. A user can stop being asked about them:

  • setCopilotSessionApprovalRule(sessionId, enabled) approves low-risk cards automatically for one chat.
  • setCopilotAutoApproveLow(enabled) does the same for all of the caller's chats.
  • copilotApprovalRules(sessionId) reads both settings.

These never apply to a card of medium risk or higher. Those always need a person.

6. Apply a proposal​

applyProposal(input) is the only call that writes. It needs catalog:copilot:apply and takes sessionId, proposalId and idempotencyKey.

mutation Apply($input: ApplyProposalInput!) {
applyProposal(input: $input) {
jobId
batchId
replayed
report
}
}
{ "input": { "sessionId": "<session id>", "proposalId": "<proposal id>", "idempotencyKey": "<unique key you generate>" } }

It is refused unless the card is APPROVED, with Only an APPROVED card can be applied. Beyond that it checks that:

  • The organization's autonomy cap allows it (section 8).
  • The caller holds the write permission for what the card does.
  • The document the card refers to has not moved on since the card was made.

Applying one approval writes once. If the same approved card is applied twice, for example from two tabs, the second call does not create a second set of records. A new approval is what allows the next attempt.

Read report for what changed. batchId names the batch of records written. If a batch only partly succeeded, retryFailedProposalItems(sessionId, proposalId, batchId) applies just the failed items again.

A parse or an image search runs in the background. startCopilotParse(input) takes sessionId and the fileKey of an uploaded supplier file and returns a CopilotJob. Poll copilotJob(jobId) until status is COMPLETED or FAILED. startImageSourcing(input) builds a card for variants that lack images.

7. Undo a batch​

rollbackCatalogBatch(batchId) restores the state from before an applied batch. It needs catalog:copilot:rollback, and it also checks the delete permission for what the batch created. The result counts the records restored, deleted, skipped and failed, and details explains each one that needed a note.

catalogBatches(input) lists the batches the copilot applied, newest first, and catalogBatch(id) reads one.

8. Limit what the copilot may do​

An organization can cap the copilot for everyone with setCopilotAutonomyLevel(level). It needs settings:organization:update. copilotAutonomyLevel reads the current cap.

LevelThe copilot may
fullDo anything the user's own permissions allow. This is the default.
no_moneyDraft and do catalog work, but nothing that moves money or stock.
read_onlyAnswer questions only. It proposes nothing.

Tightening the cap does not delete existing cards. They stay in the chat, and applying one that the cap now forbids is refused with a message that names the workspace policy. Pass null to remove the cap.

9. Common errors​

What you seeWhyWhat to do
The feature is not in your planThe catalog copilot is not on the planEnable it on the plan
Missing permission or a permission refusalThe user lacks the permission for the callGrant it, or hide the action
Minori session not foundThe chat is not the caller's, or does not existReload the list of chats
This card was replaced by a newer versionThe card is SUPERSEDEDOpen the card named in the message
Approval is refused with blockersThe change would be refused if appliedEdit the card until the blockers clear
TYPED_CONFIRMATION_REQUIREDA critical change or a role change needs a typed confirmationAsk the user to type it, or pass confirmation for a role change
Only an APPROVED card can be appliedThe card was not approved, or was edited after approvalApprove it again
A conflict that says the card is being appliedAnother apply is still runningWait for the time in the message, then retry
Applying is refused for the workspace policyThe autonomy cap forbids this kind of changeAsk an administrator to change the cap, or do the work by hand
Too many requestsThe per-user limit was reachedWait a minute and retry