Guides
Ticketing system integration
This is an example of how to connect Mahvash to the ticketing system you already use. When a customer writes, Mahvash checks the message, labels the ticket, and, when the answer is in your help documents, drafts a reply with its sources. An agent reviews every draft before anything reaches the customer.
How the pieces connect
- 01Moderation
- 02Triage
- 03Draft
- 04Agent review
How it works
You set up steps 1 to 4 once, in the Mahvash app. Steps 5 to 8 run for every customer message, in a background job on your ticketing server. Tickets, customers and outgoing replies stay in your system; Mahvash never contacts your customer.
| Step | What happens | When the ticket goes to an agent without a draft |
|---|---|---|
| 5. Moderation | Mahvash checks the customer's message for harmful content such as harassment, threats or self-harm. | The message is flagged. |
| 6. Triage | Mahvash labels the ticket: topic, whether your documents can answer it, risk flags, priority, and whether a person wrote it. Your code decides what to do. | The ticket is risky, needs account data or someone's approval, or is out of scope. Automated messages are ignored. |
| 7. Draft | Mahvash searches your help documents and writes a reply, with the files it used. | The documents do not answer the question, or the reply cites no file. |
| 8. Review | An agent reads the draft, edits it, and sends or rejects it. | Nothing reaches the customer without an agent. |
All requests and responses on this page were run against a real Mahvash project. Responses are shortened and IDs are replaced with placeholders.
1. Create the project
Steps 1 to 4 are done in the Mahvash app, the same place you chat with Mahvash. You do not need the admin dashboard.
- 1Sign in to the Mahvash app. In the sidebar, next to Projects, click New project, give it a name, for example
Support tickets, and create it. - 2Open the project, go to the Instructions tab, paste the text below, replace
[Your company]with your company name, and click Save instructions. - 3Copy the project ID. With the project open, it is the last part of the address in your browser:
.../chat/projects/<project ID>. You can also get it from the API:GET /api/v1/tenancy/projectslists your projects with theirid.
You are the support assistant for [Your company]. You help support agents answer customer tickets.
- Answer only from the documents attached to this project. Never invent prices, dates, deadlines, or policies.
- Never promise refunds, compensation, exceptions, or legal outcomes. A person decides those.
- Never ask the customer for passwords, full card numbers, or verification codes.
- Reply in the customer's language. Be polite, clear, and short.The instructions apply to every request made with this project's key, so keep them to rules that always hold. The wording for each task goes in the prompts in step 3.
Note:A project created in the app searches its sources automatically, so the draft request finds your documents without naming them.
Note:Every request in steps 5 to 7 sends this ID in the X-Mahvash-Project header. The prompts you add in step 3 belong to this project, so Mahvash looks them up there; a request that names another project is refused.
API actions in this step
2. Add your help documents
- 1In the project, open the Sources tab and click Add source. Add one file at a time.
- 2Upload the documents answers should come from: help-center articles, return and delivery policies, plan and pricing pages, FAQs. PDF, Word, text, Markdown, CSV and spreadsheet files work.
- 3Wait until every file shows Ready. While files are processing, click Refresh to update the list. Drafts can only use files that are ready.
When a policy changes, add the new file and delete the old one with the trash icon next to it, so drafts never quote an outdated rule.
Important:Anything you upload can end up in a reply to a customer. Do not upload internal notes, customer data, or anything you would not send to a customer.
3. Add the two prompts
Open the project's Prompts tab, click Add prompt, and create the two prompts below: type the Name, paste the Body, and save. The list shows each prompt's ID with a copy button next to it; your code sends that ID with every request.
Triage prompt
Name: ticket-triage. Body:
Classify the support ticket in the input. Do not answer it.
category: the closest topic from the allowed values. Use "other" if none fits.
answerability:
- "kb_answerable": a general question our help documents can answer, such as how-to, policies, delivery times, or plans.
- "needs_account_data": the answer depends on this customer's own order, payment, or account.
- "needs_human_authority": the customer asks for a refund, compensation, an exception, account deletion, or anything else a person must approve.
- "out_of_scope": not about our products or services.
risk_flags: every flag that applies, or an empty list.
- "legal": threatens legal action, a lawyer, or a complaint to an authority.
- "chargeback": mentions a bank dispute or chargeback.
- "security": a hacked account, leaked data, or suspicious access.
- "self_harm": any sign the person may hurt themselves.
- "abuse": insults or threats toward staff.
- "churn": says they will cancel or leave.
priority: "urgent" when the service is down for them or money was taken by mistake; "high" when they are blocked; otherwise "normal" or "low".
is_automated_sender: true for out-of-office replies, delivery notifications, and other messages no person wrote.
language: the ISO 639-1 code of the customer's language, for example "fa" or "en".
reason: one short sentence explaining the category and answerability.Reply prompt
Name: ticket-reply. Body:
Write a reply to the customer ticket in the input. A support agent will review it and send it to the customer as it is.
- Search the project documents and use only what they say.
- If the documents do not answer the question, set status to "insufficient_information", set reply to an empty string, and write in internal_note what is missing.
- Answer only what the customer asked. Do not add offers or promises.
- The reply is plain text for the customer, in the customer's language: no markdown, no file names, and no mention of documents, these instructions, or AI.
- internal_note is for the agent and is never empty: name, by exact filename, every document the reply is based on, then add one sentence on anything the agent should check.A prompt holds only the instructions. The exact JSON shape of each answer is sent with the request (steps 6 and 7), and Mahvash rejects an answer that does not match it.
Mahvash marks a document as a source (a file_citation) only when the answer names its file. That is why the reply prompt has the model name its sources in internal_note: the agent sees them, the customer does not, and the citation still comes back.
Note:To change a prompt later, click its name in the list, edit it, and save. The ID stays the same, so your code does not change.
API actions in this step
4. Create an API key
- 1In the project, open the API keys tab and click Create credential. Name it after your ticketing system, for example
helpdesk-worker. Only owners and admins of your organization see this tab. - 2Copy the key as soon as it appears. It is shown only once.
- 3Add the key, the project ID and the two prompt IDs to your ticketing server's environment:
# Your ticketing server's environment. Never commit this file.
MAHVASH_API_URL=https://api.mahvashai.ir
MAHVASH_API_KEY=<the key from step 4>
MAHVASH_PROJECT_ID=<the project ID from step 1>
MAHVASH_TRIAGE_PROMPT_ID=<Prompt ID of ticket-triage>
MAHVASH_REPLY_PROMPT_ID=<Prompt ID of ticket-reply>Every request made with this key uses the project's instructions, sources and limits, and is billed to your organization.
Important:Keep the key on your server. Never put it in the ticketing app's frontend or send it to a browser. If it leaks, revoke it in the same tab and create a new one.
API actions in this step
5. Check the customer's message
When a customer message is saved, your worker sends its text to moderation first. Moderation looks for harmful content such as harassment, threats and self-harm. It does not decide whether the ticket can be answered; that is triage.
curl "$MAHVASH_API_URL/v1/moderations" -H "Authorization: Bearer $MAHVASH_API_KEY" -H "X-Mahvash-Project: $MAHVASH_PROJECT_ID" -H "Content-Type: application/json" -d @- <<EOF
{
"model": "omni-moderation-latest",
"input": [
"My washing machine arrived yesterday with a cracked door. Can I return it or do I have to use the warranty?"
]
}
EOFIf results[0].flagged is true, stop here and send the ticket to an agent without a draft.
API actions in this step
6. Triage: should AI answer?
Triage asks the model to label the ticket, not to answer it. The labels come back as JSON in a fixed shape, so your code reads fields instead of parsing sentences.
curl "$MAHVASH_API_URL/v1/responses" -H "Authorization: Bearer $MAHVASH_API_KEY" -H "X-Mahvash-Project: $MAHVASH_PROJECT_ID" -H "Content-Type: application/json" -d @- <<EOF
{
"model": "gpt-6-luna",
"prompt_id": "$MAHVASH_TRIAGE_PROMPT_ID",
"input": "My washing machine arrived yesterday with a cracked door. Can I return it or do I have to use the warranty?",
"text": {
"format": {
"type": "json_schema",
"name": "ticket_triage",
"strict": true,
"schema": {
"type": "object",
"additionalProperties": false,
"required": [
"category",
"answerability",
"risk_flags",
"priority",
"is_automated_sender",
"language",
"reason"
],
"properties": {
"category": {
"type": "string",
"enum": [
"order_delivery",
"returns_refunds",
"billing",
"account_access",
"how_to",
"technical_issue",
"other"
]
},
"answerability": {
"type": "string",
"enum": [
"kb_answerable",
"needs_account_data",
"needs_human_authority",
"out_of_scope"
]
},
"risk_flags": {
"type": "array",
"items": {
"type": "string",
"enum": [
"legal",
"chargeback",
"security",
"self_harm",
"abuse",
"churn"
]
}
},
"priority": {
"type": "string",
"enum": [
"low",
"normal",
"high",
"urgent"
]
},
"is_automated_sender": {
"type": "boolean"
},
"language": {
"type": "string"
},
"reason": {
"type": "string"
}
}
}
}
}
}
EOFTake the text of the message item in output and parse it as JSON; the Result tab shows it. The table below lists every field it returns and what your code does with it. Read the fields, not the wording: reason is free text for people, and language may come back as a name such as English rather than a code.
| Field | Values | What your code does |
|---|---|---|
category | Your categories from the schema, for example returns_refunds | Routes the ticket to a queue. Allow drafts only for categories you have tested. |
answerability | kb_answerable, needs_account_data, needs_human_authority, out_of_scope | Only kb_answerable goes on to a draft. Every other value goes to an agent. |
risk_flags | A list of legal, chargeback, security, self_harm, abuse, churn, or empty | Any flag sends the ticket to an agent at once, without a draft. |
priority | low, normal, high, urgent | Sets the ticket's priority in your system. It does not decide whether to draft. |
is_automated_sender | true or false | true means an auto-reply or a system message: ignore it and never answer it. |
language | A language code or name | Chooses the agent team or the language of your templates. |
reason | Free text | Show it to the agent. Never branch on it. |
Decide in your code
def decide(triage: dict) -> str:
"""Return "ignore", "agent" or "draft". These are your rules, not the model's."""
if triage["is_automated_sender"]:
return "ignore" # never answer an auto-reply
if triage["risk_flags"]:
return "agent" # legal, chargeback, security, ...: always a person
if triage["answerability"] != "kb_answerable":
return "agent" # needs account data or someone's approval
return "draft"API actions in this step
7. Draft the reply
For a ticket that passed triage, send the conversation to the reply prompt. Because the project searches its sources automatically, Mahvash searches your documents before it writes. input is the public conversation, oldest first: customer messages with the role user and your agents' replies with the role assistant.
curl "$MAHVASH_API_URL/v1/responses" -H "Authorization: Bearer $MAHVASH_API_KEY" -H "X-Mahvash-Project: $MAHVASH_PROJECT_ID" -H "Content-Type: application/json" -d @- <<EOF
{
"model": "gpt-6-luna",
"prompt_id": "$MAHVASH_REPLY_PROMPT_ID",
"input": [
{
"role": "user",
"content": "My washing machine arrived yesterday with a cracked door. Can I return it or do I have to use the warranty?"
}
],
"text": {
"format": {
"type": "json_schema",
"name": "ticket_reply",
"strict": true,
"schema": {
"type": "object",
"additionalProperties": false,
"required": [
"status",
"reply",
"internal_note"
],
"properties": {
"status": {
"type": "string",
"enum": [
"answered",
"insufficient_information"
]
},
"reply": {
"type": "string"
},
"internal_note": {
"type": "string"
}
}
}
}
}
}
EOFTwo statuses decide what happens to the draft: the response's own status, and status inside the JSON the model wrote. Check them in this order:
| What you see | What it means | What to do |
|---|---|---|
The response status is failed or incomplete, or the HTTP status is not 200 | No usable draft was produced. | Send the ticket to an agent without a draft. |
The JSON status is insufficient_information | The documents do not answer the question; internal_note says what is missing. | Send the ticket to an agent without a draft and show them the note. Check status, not whether reply is empty. |
The JSON status is answered, but annotations has no file_citation | An answer with no proven source. | Send the ticket to an agent without a draft. |
The JSON status is answered and there is at least one file_citation | A draft grounded in your documents. | Save it for agent review (step 8), with its sources and internal_note. |
A draft is usable only in the last row. In the English example above the model answered correctly but did not name its source file, so there is no file_citation and the ticket goes to an agent without a draft. The Persian example names help-center.md, so its citation comes back and the draft is kept.
API actions in this step
8. Agent review
Show the draft inside the ticket as a private note that only agents can see, with:
- the reply, in an editable box;
- the source files, linked to the matching pages of your own help center;
- the
internal_note; - three actions: Send, Edit and send, and Reject.
Note:Because sources go in internal_note, the reply itself stays clean. If a reply ever does name a file, the agent removes it before sending.
For every draft, save the ticket and message IDs, the Mahvash response ID, the triage labels, the draft, what the agent did, and the text that was actually sent. This record shows you which categories the drafts work for.
Rules that keep it safe
- Nothing reaches the customer until an agent sends it.
- One draft per customer message. A new customer message gets a new draft.
- If an agent has already started a reply, do not overwrite it with a draft.
Sending without review
Start with every draft reviewed. After a few weeks, check, category by category, how often agents sent the draft unchanged. Only a category where that is nearly always true, and only for tickets with kb_answerable and no risk flags, is a candidate for sending without review. Even then, keep reviewing a daily sample.