Skip to content

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.

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.

Terminal window
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.

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.

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.

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:

Terminal window
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.

Shortly after receiving 202, query the endpoint below at increasing intervals (1s, 2s, 4s for example):

Terminal window
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.

Terminal window
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.

  • perPage is capped at 100. A higher value is not an error; it is silently reduced to 100.
  • The API reference also shows updatedAtGte / updatedAtLte parameters, but this listing binds them to no filter. Sending them does not change the result. Use createdAtGte / createdAtLte to narrow by date.
  • field= accepts only: id, trackingKey, description, priority, ticketStatusId, ticketSubStatusId, requesterFullName, requesterEmailAddress, requesterPhoneNumber, createdAt, completedAt. Even the subject, ticketStatusName and etag fields 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.

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.