GET/api/v1/me
The team and key name. Use it to test a connection.
Read and change tickets from your own code, Zapier, Make or n8n. Every plan includes the API. There are no extra charges for using it.
An admin makes a key in Flatdesk under Integrations. Send it in the Authorization header. Requests and responses are JSON. A key can make up to 1,000 requests an hour.
curl https://flatdeskfinal.vercel.app/api/v1/tickets?status=open \ -H "Authorization: Bearer fd_your_key"
Errors come back as { "error": "..." } with a status code: 400 for a bad field, 401 for a missing or revoked key, 402 when the trial ended without a card (reading still works), 404, and 429 when you go too fast.
The team and key name. Use it to test a connection.
Tickets, most recently changed first.
A new ticket from a customer, as if they had emailed. Your replies go to them by email. The AI answers it like any new email unless ai_answer is false.
{
"customer_email": "sam@example.com",
"customer_name": "Sam Lee",
"subject": "Order #1042 arrived damaged",
"body": "The mug was broken when it arrived.",
"tags": ["damaged"]
}One ticket with its whole conversation, internal notes included.
Change the status, priority, assignee or tags.
{ "status": "closed", "add_tags": ["refunded"] }Reply to the customer (emailed to them), or add an internal note. A reply sets the ticket to pending unless you send a status.
{ "body": "Sorry about that, Sam. A replacement is on its way.", "status": "closed" }The team's shared macros: name, reply text, tags and status they set. Personal macros stay private.
Every help center article (without the text), with whether it is published and its public link.
One article with its text.
A new help center article. It stays a draft unless published is true.
{ "title": "Change your plan", "body": "Open Settings, then Billing.", "section": "Billing", "published": true }Change an article's title, text, section, published or internal flag.
{ "published": false }A customer, their fields, and their tickets.
Tickets come back with number, subject, status, channel (email, chat, sms or whatsapp), tags, customer, assignee, answered_by_ai, url and timestamps. A ticket's messages have author_type (customer, agent, ai or system), author_name, body, internal and created_at.
Set a webhook address under Settings, Alerts, and choose which tickets to send: only the ones that need a person, or every new one. Slack, Discord and Google Chat addresses get a message in their own format. Any other address gets JSON like this, with event ticket.created, ticket.handed_back (a customer wrote back to an AI answer) or ticket.overdue. Tick more under Alerts to also get ticket.replied, ticket.customer_replied, ticket.closed and ticket.assigned. A ticket set to “update the customer every…” sends ticket.update_due when the update is late:
{
"event": "ticket.created",
"text": "New ticket for the team: #1042 Order #1042 arrived damaged from Sam Lee (sam@example.com) by email.",
"needsTeam": true,
"reason": null,
"ticket": {
"number": 1042,
"subject": "Order #1042 arrived damaged",
"channel": "email",
"status": "open",
"url": "https://flatdesk.app/app/tickets/1042",
"customer": { "name": "Sam Lee", "email": "sam@example.com" },
"assignee": null
},
"sentAt": "2026-10-06T09:14:00.000Z"
}Check it came from Flatdesk with the X-Flatdesk-Signature header: sha256= followed by the HMAC-SHA256 of the raw body, keyed with the secret shown under Alerts.
Every event uses the JSON above with the event name in event. In Zapier use Webhooks by Zapier, Catch Hook; in Make use Webhooks, Custom webhook. Both read the JSON as is, and a Filter on event picks the ones you want.
| Event | Sent | When |
|---|---|---|
| ticket.created | Always | A new ticket needs a person, or any new ticket if you chose to send every one. |
| ticket.handed_back | Always | A customer wrote back to an answer the AI gave, so a person should look. |
| ticket.overdue | Always | A ticket passed its reply or resolve target. The reason field says which. |
| ticket.replied | Tick under Alerts | Your team sent a public reply to the customer. |
| ticket.customer_replied | Tick under Alerts | A customer wrote back, on any ticket, including ones the AI answered. |
| ticket.closed | Tick under Alerts | A person closed a ticket from its page or with a reply. |
| ticket.assigned | Tick under Alerts | A ticket was assigned to someone, or to nobody. |
| ticket.update_due | With an update timer | A ticket set to "update the customer every..." is late for its next update. |
| test | Test button | You pressed Send test under Settings, Alerts. The ticket field is a sample. |
import { createHmac, timingSafeEqual } from "node:crypto";
function fromFlatdesk(rawBody, signatureHeader, secret) {
const expected = "sha256=" + createHmac("sha256", secret).update(rawBody).digest("hex");
const a = Buffer.from(expected);
const b = Buffer.from(signatureHeader ?? "");
return a.length === b.length && timingSafeEqual(a, b);
}Use the raw request body, not re-serialized JSON. Zapier's Catch Hook doesn't expose headers, so keep the address secret there; a code step or your own server can verify.
Flatdesk is an MCP server, so an AI assistant can search, read and work your tickets: “what are customers saying about the new pricing?”, “tag every ticket about the outage and mark them urgent”, or “draft a note on #1042 with what we know”. Connect it with the same API key:
URL https://flatdeskfinal.vercel.app/api/mcp Header Authorization: Bearer fd_... # Claude Code claude mcp add --transport http flatdesk https://flatdeskfinal.vercel.app/api/mcp \ --header "Authorization: Bearer fd_..."
Tools: search_tickets, list_tickets, get_ticket, get_customer, update_ticket and reply_to_ticket. Replies are emailed to the customer right away, so the assistant is told to check with you before sending one. A team whose trial has ended can read but not change tickets, as with the API.