Creating requests
This page covers everything about opening a request in FlowDesk from your own system with
v1/integration/tickets. If you do not have an API token yet, start with
Authentication; none of these endpoints works without one.
Two endpoints, one recommendation
Section titled “Two endpoints, one recommendation”| Endpoint | Response | Use |
|---|---|---|
POST /v1/integration/tickets |
202 + trackingKey |
Recommended. Pool inference, assignment and the welcome e-mail run outside your request, in the background. |
POST /v1/integration/tickets/sync |
201 + the full request body |
Not recommended. The same work runs inside your request: the response time is noticeably higher and a slow dependency (a mail provider, for example) becomes your timeout. The response is marked with Deprecation: true and Link: </v1/integration/tickets>; rel="successor-version". It does not support Idempotency-Key: a repeated call opens a second request. It exists only for integrations that genuinely cannot poll. |
Every example below uses the async (/tickets) endpoint; the field contract is the same for both.
Required and optional fields
Section titled “Required and optional fields”curl -s -X POST https://api.example.com/v1/integration/tickets \ -H "Authorization: Bearer fd_ab12cd34ef...9f" \ -H "Content-Type: application/json" \ -d '{ "subject": "Card transaction is not showing on my account", "description": "My last transaction is not showing on my account, I have been waiting two days.", "priority": "High", "requesterFullName": "Alex Morgan", "requesterEmailAddress": "alex@example.com", "requesterPhoneNumber": "+15550123456", "poolId": 7 }'| Field | Required | Note |
|---|---|---|
subject |
Conditional | At least one of subject and description is required; 422 when both are empty. Maximum 512 characters. |
description |
Conditional | Subject to the condition above. Maximum 8000 characters. |
priority |
No | See below: only two values are valid. Left empty, the deployment’s default (Medium) applies. |
requesterFullName |
No | Maximum 256 characters. |
requesterEmailAddress |
No | Must be a valid e-mail format. When supplied, the welcome e-mail containing the tracking link is sent to this address. |
requesterPhoneNumber |
No | Maximum 256 characters; no format validation. |
poolId |
Conditional | See the “Pool selection” section below. |
priority accepts only two values: Medium and High (case insensitive). FlowDesk’s request
priority model carries no extra levels such as Low/Normal/Critical: any other value
(including "Normal") fails validation and returns 422. Left empty, the deployment’s
default priority (Medium) is used.
The request source (ticketSourceId) is never sent in the request body. It is resolved from
your token, so that each integration appears as its own channel in reporting.
Pool selection: poolId
Section titled “Pool selection: poolId”poolId selects from within the set of pools your token is allowed to use. It can never widen
that set:
| Token’s scope | poolId |
If left empty | If a value outside the scope is given |
|---|---|---|---|
| Single pool | Optional | That single pool is assumed | 403 |
| Several pools | Required | 422: the target is ambiguous |
403 |
All pools (allPools) |
Required | 422: the target is ambiguous |
403 |
There is no customer matching: only requester fields
Section titled “There is no customer matching: only requester fields”FlowDesk keeps no customer or contact record that de-duplicates requesterFullName /
requesterEmailAddress / requesterPhoneNumber by e-mail or phone. Every request is an
independent record carrying its own requester information, and no automatic link is made to other
requests previously opened with the same e-mail. The only functional effect of
requesterEmailAddress is that, when supplied, the welcome e-mail containing the tracking link
goes to that address.
If you need a customer-to-request mapping in your own system (“which order does this request
belong to”), you have to build it on your side by associating the trackingKey with your own
record; see below.
File attachments are not supported
Section titled “File attachments are not supported”v1/integration/tickets (async or sync) does not accept file attachments. There is no such
field in the request body and adding one is not planned in this version. If you need to attach a
file to a request, that has to be done from the dashboard after the request is opened (as an agent
or an administrator); your integration token cannot be used for it.
Safe retries: Idempotency-Key
Section titled “Safe retries: Idempotency-Key”To resolve the “did my request go through or not” uncertainty after something like a network
timeout, send an Idempotency-Key header to the async endpoint:
curl -s -X POST https://api.example.com/v1/integration/tickets \ -H "Authorization: Bearer fd_ab12cd34ef...9f" \ -H "Idempotency-Key: order-42-attempt-1" \ -H "Content-Type: application/json" \ -d '{ "subject": "...", "description": "..." }'| Case | Behaviour |
|---|---|
| Same key + same body | The original 202 response is returned again. No new request is opened. |
| Same key + different body | 409. You reused the key for a different request. There is a bug in your client logic. |
| Two concurrent requests with the same key | The key claim is atomic; exactly one request is opened. |
| Validity window of the key | 24 hours. After that the same key is treated as a new request. |
Keys are scoped per token: two different integrations picking the same key do not collide.
Response body and storing the tracking key
Section titled “Response body and storing the tracking key”The async endpoint returns 202 as soon as the request is queued:
{ "success": true, "data": { "trackingKey": "K7xQm9Zp...eF5A", "status": "queued" } }A trackingKey does not mean the request was created: it means it was queued. The actual
request record is created shortly afterwards, outside your request, in the background. The
trackingKey is a randomly generated, URL-safe, ~64-character opaque string: it carries no FD-
style prefix and no readable structure. Do not try to derive meaning from its content; just store
it and use it to query back.
Store this value in your own system, associated with your own request, order or record id. It is the single point of connection that lets you both query the outcome later (see below) and let the end user follow their own request. No back-reference to your own record is kept on the FlowDesk side.
Reading the request back
Section titled “Reading the request back”Shortly after receiving 202, query the endpoint below at increasing intervals (1s, 2s, 4s for
example):
curl -s https://api.example.com/v1/integration/tickets/by-tracking-key/K7xQm9Zp...eF5A \ -H "Authorization: Bearer fd_ab12cd34ef...9f"If the request has not been created yet, 404: that is not an error, it is the expected first
state; retry at increasing intervals. Once it exists, the same endpoint returns 200 with the
request’s full body. This endpoint is deliberately not cached; otherwise an accepted request
would appear “lost” for the duration of the cache window.
You can also read the request by id (GET /v1/integration/tickets/{id}); that response carries
an ETag, and if you send If-None-Match on a repeat query you get a bodiless 304 when the
request has not changed. Unlike listing, this endpoint is invalidated on every write: it is
never staler than the last change.
The response body is a deliberately narrow slice specific to the integration surface: id,
trackingKey, subject, description, priority, ticketStatusId, ticketStatusName,
ticketSubStatusId, requesterFullName, requesterEmailAddress, requesterPhoneNumber,
createdAt, completedAt, etag. Internal routing information such as the assigned agent, the
department or SLA flags never appears in this response. It is not the integrator’s business.
Listing and filtering
Section titled “Listing and filtering”curl -s "https://api.example.com/v1/integration/tickets?perPage=50&requesterEmail=alex@example.com&createdAtGte=2026-08-01" \ -H "Authorization: Bearer fd_ab12cd34ef...9f"Filters that work: statusIds, requesterEmail, requesterPhone, trackingKey,
createdAtGte, createdAtLte (date only, YYYY-MM-DD). Paging and sorting: page,
perPage, field, order.
perPageis capped at 100. A higher value is not an error; it is silently reduced to 100.- The API reference also shows
updatedAtGte/updatedAtLteparameters, but this listing binds them to no filter. Sending them does not change the result. UsecreatedAtGte/createdAtLteto narrow by date. field=accepts only:id,trackingKey,description,priority,ticketStatusId,ticketSubStatusId,requesterFullName,requesterEmailAddress,requesterPhoneNumber,createdAt,completedAt. Even thesubject,ticketStatusNameandetagfields that appear in the response cannot be sorted by (400). Those fields are stored under a different name in the internal data model, or computed for the response only.
The list response is only time-cached (45 seconds by default) and is not invalidated on
write. A request you just opened, or whose status you just changed, may not appear in the list
during that window, or may appear in its old state. To learn a request’s current state, always
use the single-request read or by-tracking-key; the list endpoint is for bulk scanning and
reporting, not moment-to-moment accuracy.
Error contract
Section titled “Error contract”| Status | Code | Meaning |
|---|---|---|
401 |
FD:0004 |
The credential is missing, invalid, expired or revoked. Do not retry; see the checklist on the authentication page. |
403 |
FD:0001 |
A valid credential, but insufficient scope or an attempt to open a request with a poolId you are not allowed to use: both carry the same code, so read the distinction from the response message. |
404 |
FD:0002 |
The request does not exist, belongs to a pool you are not allowed to use, or the async request has not been created yet. In that last case, retry at increasing intervals. |
409 |
Same Idempotency-Key, different body. A bug in your client logic; do not retry. |
|
422 |
Body validation failed (error.details carries a per-field list), poolId was left ambiguous, or the target pool is not fully configured to receive integration requests. Do not retry without fixing it. |
|
429 |
Rate limit exceeded. Wait for the Retry-After period and retry. |
|
503 |
FD:0011 |
The deployment’s licence state is temporarily refusing the request. Nothing to do with your request; back off and retry. |
Rate limit: every token has its own budget (120 requests per minute by default, partitioned by
token id). A successful response carries no rate-limit header, so you cannot see your remaining
quota in advance; you only learn it from Retry-After when you get a 429.
What’s next
Section titled “What’s next”- Letting the end user follow their request without authenticating, using the
trackingKeyyou created: Tracking requests - How you learn that a request’s status changed (no webhooks, polling instead): Webhooks and automation
- The full endpoint contract:
POST /v1/integration/tickets·GET /v1/integration/tickets·GET /v1/integration/tickets/{id}·GET .../by-tracking-key/{trackingKey}·POST /v1/integration/tickets/sync