Getting started
MCP Server
Qualy runs a remote Model Context Protocol (MCP) server that lets AI agents work with your Qualy data through a curated set of tools. Connect a client such as Claude Desktop, Cursor, or Windsurf — or build your own agent — and it can look up contacts, create payment requests, search your data, and more, all scoped to your tenant.
Endpoint
The server speaks the Streamable HTTP MCP transport at:
https://api.qualyhq.com/v1/mcp
There are two ways to authenticate:
Sign in with Qualy (OAuth) — for clients with a connectors UI (claude.ai, Claude Desktop, Claude Code, and any client that supports MCP authorization). Add the server URL with no credentials; your browser opens a Qualy sign-in (email + password, plus your usual two-factor code), you review what you're connecting, and you're done. No API key to create or store. The connection acts as you — it can see and do exactly what your Qualy user can, nothing more.
API key — for headless agents, scripts, and clients where you configure headers by hand. Pass your Qualy API key as a bearer token:
| Header | Value |
|---|---|
Authorization | Bearer <your-api-key> |
The tenant is encoded in your login or key, so MCP needs no X-TENANT-ID header. (Authorization: ApiKey <key> is also accepted, and if your client happens to send X-TENANT-ID it's cross-checked against the credential.) Either way, every tool call runs with the permissions of the connected user and is scoped to their tenant.
Connect a client
Claude (sign in — no key)
Add Qualy once as a custom connector on your Claude account, at claude.ai/settings/connectors → Add custom connector → paste https://api.qualyhq.com/v1/mcp, leaving every other field empty. Your browser opens a Qualy sign-in and consent screen; approve it and the tools are available.
Because the connector lives on your account, it follows you into Claude chat, Claude Desktop, Cowork and Claude Code — you set it up once. On Team and Enterprise plans an Owner can add it for the whole organisation under Admin settings → Connectors; everyone else then only clicks Connect and signs in.
See Connect Qualy to Claude for the step-by-step version to hand a non-technical colleague.
To revoke a connection later, remove it in your client — or sign out of all sessions in the Qualy Dashboard, which disconnects every AI client at once.
Claude Code: connector or claude mcp add?
Claude Code can reach Qualy either way, and they don't reach the same places:
claude mcp add --transport http qualy https://api.qualyhq.com/v1/mcp
| Account connector | claude mcp add | |
|---|---|---|
| Claude chat, Desktop, Cowork | yes | no |
| Claude Code | yes | yes |
| Scope | your Claude account | local (this folder, default), project (committed .mcp.json), or user (all your folders) via -s |
| Auth | sign in with Qualy | sign in, or an API key header |
Reach for claude mcp add when you want Qualy pinned to a specific codebase — -s project writes a .mcp.json your teammates get on clone, which is useful for a repo whose whole job is talking to Qualy. For everything else the account connector is the better default: one setup, and it's there in Cowork and on your phone too.
Other clients
Most other MCP clients connect by adding an entry to their MCP configuration. Clients that support MCP authorization will also walk you through the same browser sign-in when you omit the header; otherwise, use an API key.
Clients with native remote MCP support
Cursor, VS Code, Zed and similar clients can point at the remote server directly:
{
"mcpServers": {
"qualy": {
"url": "https://api.qualyhq.com/v1/mcp",
"headers": {
"Authorization": "Bearer pk_prod_your-api-key"
}
}
}
}
Windsurf uses a different key
Windsurf (now Devin Desktop) expects serverUrl, not url. Copying the block above verbatim fails silently — the server is accepted but never connects. Use:
{
"mcpServers": {
"qualy": {
"serverUrl": "https://api.qualyhq.com/v1/mcp"
}
}
}
Stdio-only clients
Clients that only launch local (stdio) servers can bridge to the remote server with mcp-remote (drop the --header arguments to sign in with OAuth instead):
{
"mcpServers": {
"qualy": {
"command": "npx",
"args": [
"-y",
"mcp-remote",
"https://api.qualyhq.com/v1/mcp",
"--header",
"Authorization: Bearer pk_prod_your-api-key"
]
}
}
}
After saving the configuration and restarting your client, the Qualy tools appear in its tool list.
Any MCP-compatible client connects the same way: point it at https://api.qualyhq.com/v1/mcp and either complete the sign-in flow or set Authorization: Bearer <your-api-key>. ChatGPT, Replit, Zed and others each have their own "add MCP server" / connectors screen — follow your client's MCP instructions (for example, Cursor's MCP install links).
For clients and tools that consume the MCP registry format, Qualy publishes a machine-readable entry — name, description, logo, and the remote endpoint — at https://docs.qualyhq.com/server.json.
Keep your key safe
If you configure an API key, your MCP configuration stores it in plain text on your machine. Treat it like any other secret — see API key best practices. Signing in with Qualy avoids this: the client holds short-lived tokens that you can revoke at any time.
How it appears in your client
On connect, the server tells your client who it is — the name Qualy, a website, and the Qualy logo — so clients that support it show proper branding rather than a bare hostname. It also returns a short set of usage instructions that guide the assistant on how to use the tools (find before acting, confirm writes, and so on). You don't configure any of this; it comes from the server. Support varies by client, so exactly how much is shown (logo, description) depends on the app you connect from.
Available tools
All tools are scoped to your tenant. Reads are safe to call freely; writes are rate-limited and de-duplicated.
Contacts
| Tool | What it does |
|---|---|
contact_list | List contacts (customers / leads / students), newest first; filter by email. |
contact_get | Get one contact by id or email. |
contact_create | Create a contact. Returns the existing contact if the email is already on file. |
contact_update | Update a contact's name, phone, or email. |
Payment intents
| Tool | What it does |
|---|---|
payment_intent_list | List payment requests; filter by contact or status. |
payment_intent_get | Get one payment intent, including its shareable payment link. |
payment_intent_create | Create a payment request for a contact. |
payment_intent_cancel | Cancel a payment intent. |
payment_intent_remind | Email the contact a payment reminder — the same reminder the Dashboard sends. Customer-visible; agents confirm before sending. |
Transactions
| Tool | What it does |
|---|---|
transaction_list | List transactions (charges and refunds); filter by contact, payment intent, or status. |
transaction_get | Get one transaction by its id. |
Orders
| Tool | What it does |
|---|---|
order_list | List orders; filter by contact. |
order_get | Get one order by its id. |
Partnerships
| Tool | What it does |
|---|---|
partnership_list | List partnerships (agents / institutions / suppliers). |
partnership_get | Get one partnership by id, code, or name. |
partnership_create | Create a partnership. |
Search
| Tool | What it does |
|---|---|
search | Fuzzy search across contacts or partnerships by name, email, or keyword. |
Analytics
Aggregations computed in the database — ask for totals, trends, and forecasts instead of having the agent page through lists. Amounts in analytics results are integer minor units (cents), grouped by currency.
| Tool | What it does |
|---|---|
payment_intent_stats | Payment analytics in 11 formats: consolidated totals, month-by-month, payer behavior, by-services, calendar-by-day, cancellations, customer payments by month, cash velocity, revenue by service, receivables forecast, and FX corridor margin. |
transaction_stats | Per-currency transaction counts and totals, broken down by status or by type. |
payment_split_stats | Partner-commission analytics: owed / paid / outstanding, monthly cohorts, per-partner and per-team breakdowns, reconciliation. |
Payouts & payment splits
Payouts are read-only; splits can also be recorded.
| Tool | What it does |
|---|---|
payout_list | List partner / supplier payouts; filter by status, partnership, method, or currency. |
payout_get | Get one payout, including its FX details and failure reasons. Bank account numbers are always masked. |
payment_split_list | List payment splits (partner commissions / revenue shares). |
payment_split_get | Get one payment split, including amounts, approval, and payout linkage. |
payment_split_create | Record a fixed-amount commission on a payment intent. Records what is owed — paying it still happens through payouts. |
Customer subscriptions
Your customers' recurring payment schedules — not your own Qualy plan.
| Tool | What it does |
|---|---|
subscription_list | List customer payment schedules; filter by status, mode, or contact. |
subscription_get | Get one payment schedule, including cadence, amounts, and charge stats. |
subscription_create | Create a recurring payment schedule for a contact — Qualy raises a payment request each cycle. |
Refunds & disputes
Disputes are read-only. Refunds can be started — never executed: a created refund is a draft that a human must approve in the Dashboard before any money moves.
| Tool | What it does |
|---|---|
refund_intent_list | List refund requests and their approval / settlement state. |
refund_intent_get | Get one refund request with per-item amounts, fees, and taxes. |
refund_intent_create | Start a refund against a settled charge, as a draft for human approval. |
dispute_list | List disputes (chargebacks); open: true narrows to those still needing attention. |
dispute_get | Get one dispute, including its reason, evidence, and due dates. |
Your Qualy billing
Your own Qualy subscription and invoices — what you pay Qualy, not your customers' payments. Requires an admin user.
| Tool | What it does |
|---|---|
billing_invoice_list | List your Qualy subscription invoices. |
billing_subscription_list | Your Qualy subscription status and plans. |
billing_stats | Account balance, currency, and the upcoming invoice preview. |
What's not exposed
Nothing on the MCP surface executes money movement: no capturing charges, no approving or executing refunds, no creating or sending payouts. Creates raise requests or drafts — a payment request the payer settles through a secure link, a refund draft a human must approve. Executing operations still go through the API or the Dashboard.
Smart references
Wherever a tool takes a contact or partnership, you can pass a natural identifier instead of a Mongo ObjectId — Qualy resolves it to the right record before the tool runs. An agent that only knows a student's email or a partner's name doesn't need to look up an id first. This is the same Smart references resolution the REST API uses, applied to the MCP tools' reference fields.
| Field | Accepts |
|---|---|
contact | a contact id, or the contact's email (case-insensitive) |
partnership | a partnership id, code, exact name, or a unique name prefix |
Examples
Get a contact by email instead of an id — contact_get:
{ "contact": "camila@example.com" }
Raise a payment request straight from the payer's email, with no lookup step — payment_intent_create:
{
"contact": "camila@example.com",
"currency": "AUD",
"items": [{ "name": "Tuition — Semester 1", "amount": 12000 }]
}
Fetch a partner by name (or its code) — partnership_get:
{ "partnership": "Sydney English College" }
The same contact resolution applies to the contact filter on payment_intent_list, transaction_list, and order_list.
When a reference is ambiguous
If a name or prefix matches more than one record, the tool returns an AGENT_TOOL_AMBIGUOUS_REFERENCE error listing the candidate records, so the agent can retry with something more specific (an exact name, the partnership code, or an id). If nothing matches, it returns AGENT_TOOL_REFERENCE_NOT_FOUND.
For automated flows, prefer stable identifiers — email, partnership code, or id — over name prefixes, which can drift into ambiguity as new records are added.
When to use MCP vs the API
The MCP server and the REST API run on the same platform, the same data, and the same API-key auth — they're two front doors to the same back office. Pick by how your integration is driven:
Use the MCP server when an AI assistant is your interface. A person or agent asks in natural language and the model decides which tools to call, in what order, with you approving anything that writes. It exposes a curated, tenant-scoped subset of the platform — ideal for the long tail of ad-hoc questions and tasks ("find this student, draft a plan, chase that invoice") without writing or maintaining integration code.
Use the REST API when you're building a programmatic integration. You get the full endpoint surface, deterministic control over every call, webhooks, idempotency keys, and conveniences like Smart references — sending a contact email, partnership code, or service name where an endpoint expects an ObjectId. (MCP tools accept Smart references too, so this isn't a reason to choose one over the other.)
Many teams use both — an assistant for day-to-day operators, the API for their own product surface. They never conflict: every call, from either door, runs through the same tenant scoping, permissions, and platform controls.
Build your own agent
To drive the server from your own agent, connect with any MCP-compatible client library. With the official TypeScript SDK:
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
const transport = new StreamableHTTPClientTransport(
new URL('https://api.qualyhq.com/v1/mcp'),
{
requestInit: {
headers: {
Authorization: 'Bearer pk_prod_your-api-key',
},
},
},
);
const client = new Client({ name: 'my-agent', version: '1.0.0' });
await client.connect(transport);
// Discover the tools…
const { tools } = await client.listTools();
// …and call one.
const result = await client.callTool({
name: 'contact_list',
arguments: { limit: 5 },
});
console.log(result);
Behavior and limits
- Tenant scoping — every call runs against your tenant only; the tenant is taken from your credentials.
- Permissions — each tool requires the matching view/edit permission on the connected user (for example,
payout_listrequires the splits view permission). A call the user isn't allowed to make returnsAGENT_TOOL_FORBIDDEN. OAuth connections act as the signed-in user; API keys carry their own user's permissions. - Authoritative totals — every
*_listresult includescount(the total matching your filter) andhasMore. Agents should read those instead of counting pages; for sums and trends, the*_statstools aggregate in the database. - Rate limits — calls are rate-limited per tool. Reads have a generous budget; analytics and billing are tighter. Exceeding a limit returns an
AGENT_TOOL_RATE_LIMITEDerror with a retry hint. - Idempotency — write tools (create / update / cancel) are idempotent: repeating the same call with the same input within 24 hours returns the original result instead of creating a duplicate.
- Structured errors — tool errors come back with a
codeandmessageso your agent can react — for example, retry on a rate limit or re-prompt on invalid input. - OAuth session lifetime — sign-in tokens are short-lived and refresh automatically; your client re-prompts to sign in only if the connection is revoked or long unused. Signing out of all Qualy sessions disconnects AI clients too.