Upload ACD passport documents
Add or replace the passport copies of a booking that needs ACD: one file for each traveler, sent as multipart/form-data (not JSON).
/api/v1/flights/upload_acd_documentsThe form. booking_id is the PNR, the supplier order id or the FX-Port UUID of a booking of your agency (any other booking, also one of another agency, answers 404 BOOKING_NOT_FOUND, like an unknown id). traveler_ids and documents are matched by order: the first id goes with the first file. Send traveler_ids once for each file, or as one comma-separated value (1,2), or as a JSON array as text (["1","2"]); the documents field is repeated, one file each. replace=true overwrites a traveler who already has a passport; without it that traveler is refused (DOCUMENT_ALREADY_UPLOADED) and nothing changes. The older names bookingId, orderId, travelerIds, files, file and document are still read.
The files. A PDF or an image (JPEG, PNG, WebP, HEIC/HEIF, AVIF, TIFF, BMP, GIF, JPEG 2000, JPEG XL), recognised by content (the extension and the Content-Type you send are not trusted). At most 10 MiB per file and 25 MiB per request, and no more files than travelers. Several files with problems are all reported in results[]: a request with any bad file stores nothing.
Reading the answer. 200 means every file was saved, 207 means only some were (status: partial_success; read results[]), 4xx means nothing changed. A 5xx after the files were stored cannot be told from the outside: call get_acd_documents before you retry. A retry without replace is safe; with replace=true it replaces the file again.
notification says whether the ACD team was told: in the sandbox it is never sent (skipped: true, reason: sandbox_environment). A hold accepts uploads (reason: hold_booking). The download_url links are public: treat them as sensitive. In the sandbox, a booking of CDG to ALG on amadeus_aqc_dz_1 is an ACD booking: use fake files (a PDF or PNG a few bytes long), never a real document.
curl https://api.fx-port.com/api/v1/flights/upload_acd_documents \
-H "Authorization: Bearer fxp_test_YOUR_KEY" \
-F 'booking_id=Y7HWMD' \
-F 'traveler_ids=1' \
-F '[email protected];type=application/pdf'{
"environment": "sandbox",
"success": true,
"status": "success",
"requestId": "58bfcf4a",
"booking_id": "Y7IA34",
"message": "2 documents saved.",
"summary": {
"received": 2,
"uploaded": 2,
"replaced": 0,
"failed": 0
},
"results": [
{
"index": 0,
"traveler_id": "1",
"traveler_name": "JANE DOE",
"filename": "passport-1.png",
"status": "uploaded",
"content_type": "image/png",
"size_bytes": 69,
"download_url": "https://s3.example.com/fx-port/acd_passports/Y7IA34/1/20261101142620_28e115c8.png"
},
{
"index": 1,
"traveler_id": "3",
"traveler_name": "JAMES DOE",
"filename": "passport-3.pdf",
"status": "uploaded",
"content_type": "application/pdf",
"size_bytes": 191,
"download_url": "https://s3.example.com/fx-port/acd_passports/Y7IA34/3/20261101142620_4194315e.pdf"
}
],
"acd": {
"booking_id": "Y7IA34",
"acd_required": true,
"acd_waived": false,
"acd_submitted": true,
"acd_paid": false,
"complete": true,
"can_upload": true,
"blocked_reason": null,
"required_documents": 3,
"uploaded_documents": 3,
"missing_documents": 0,
"missing_traveler_ids": [],
"travelers": [
{
"traveler_id": "1",
"traveler_name": "JANE DOE",
"traveler_type": "ADULT",
"ticket_number": null,
"status": "missing",
"document": null
},
{
"traveler_id": "2",
"traveler_name": "JOHN DOE",
"traveler_type": "ADULT",
"ticket_number": null,
"status": "uploaded",
"document": {
"filename": "john-passport.pdf",
"content_type": "application/pdf",
"size_bytes": 482113,
"download_url": "https://s3.example.com/fx-port/acd_passports/Y7IA34/2/20261101142618_ec9632ab.pdf",
"uploaded_at": "2026-11-01T14:26:18.120000+00:00",
"replaced_count": 0
}
},
{
"traveler_id": "3",
"traveler_name": "JAMES DOE",
"traveler_type": "ADULT",
"ticket_number": null,
"status": "missing",
"document": null
}
],
"unassigned_documents": [],
"revision": 2,
"limits": {
"accepted_formats": [
"avif",
"bmp",
"gif",
"heif",
"jp2",
"jpeg",
"jxl",
"pdf",
"png",
"tiff",
"webp"
],
"accepted_extensions": [
"avif",
"bmp",
"dib",
"gif",
"heic",
"heif",
"j2k",
"jfif",
"jp2",
"jpeg",
"jpg",
"jpx",
"jxl",
"pdf",
"pjp",
"pjpeg",
"png",
"tif",
"tiff",
"webp"
],
"max_file_size_bytes": 10485760,
"max_request_size_bytes": 26214400,
"max_documents_per_request": 3
}
},
"notification": {
"sent": false,
"type": "submitted",
"skipped": true,
"reason": "sandbox_environment"
},
"warnings": []
}{
"environment": "sandbox",
"success": true,
"status": "partial_success",
"requestId": "3d7a91c0",
"booking_id": "Y7IA34",
"message": "1 of 2 documents saved; 1 failed — retry the failed travelers.",
"summary": {
"received": 2,
"uploaded": 1,
"replaced": 0,
"failed": 1
},
"results": [
{
"index": 0,
"traveler_id": "1",
"traveler_name": "JANE DOE",
"filename": "passport-1.png",
"status": "uploaded",
"content_type": "image/png",
"size_bytes": 69,
"download_url": "https://s3.example.com/fx-port/acd_passports/Y7IA34/1/20261101142620_28e115c8.png"
},
{
"index": 1,
"traveler_id": "3",
"traveler_name": "JAMES DOE",
"filename": "passport-3.pdf",
"status": "failed",
"error": {
"code": "STORAGE_UPLOAD_FAILED",
"message": "The document could not be stored. Retry this traveler.",
"reason": "storage_error"
}
}
],
"acd": {
"booking_id": "Y7IA34",
"acd_required": true,
"acd_waived": false,
"acd_submitted": false,
"acd_paid": false,
"complete": false,
"can_upload": true,
"blocked_reason": null,
"required_documents": 3,
"uploaded_documents": 2,
"missing_documents": 1,
"missing_traveler_ids": [
"3"
],
"travelers": [
{
"traveler_id": "1",
"traveler_name": "JANE DOE",
"traveler_type": "ADULT",
"ticket_number": null,
"status": "missing",
"document": null
},
{
"traveler_id": "2",
"traveler_name": "JOHN DOE",
"traveler_type": "ADULT",
"ticket_number": null,
"status": "uploaded",
"document": {
"filename": "john-passport.pdf",
"content_type": "application/pdf",
"size_bytes": 482113,
"download_url": "https://s3.example.com/fx-port/acd_passports/Y7IA34/2/20261101142618_ec9632ab.pdf",
"uploaded_at": "2026-11-01T14:26:18.120000+00:00",
"replaced_count": 0
}
},
{
"traveler_id": "3",
"traveler_name": "JAMES DOE",
"traveler_type": "ADULT",
"ticket_number": null,
"status": "missing",
"document": null
}
],
"unassigned_documents": [],
"revision": 2,
"limits": {
"accepted_formats": [
"avif",
"bmp",
"gif",
"heif",
"jp2",
"jpeg",
"jxl",
"pdf",
"png",
"tiff",
"webp"
],
"accepted_extensions": [
"avif",
"bmp",
"dib",
"gif",
"heic",
"heif",
"j2k",
"jfif",
"jp2",
"jpeg",
"jpg",
"jpx",
"jxl",
"pdf",
"pjp",
"pjpeg",
"png",
"tif",
"tiff",
"webp"
],
"max_file_size_bytes": 10485760,
"max_request_size_bytes": 26214400,
"max_documents_per_request": 3
}
},
"notification": {
"sent": false,
"skipped": true,
"reason": "awaiting_remaining_documents"
},
"warnings": []
}Request
Body
Required, multipart/form-data.
booking_idstringrequiredThe PNR, the supplier order id or the FX-Port booking UUID. order_id and pnr are accepted as other names. When the form holds several spellings with different values, the first of booking_id, bookingId, order_id, orderId, pnr wins and an ignored_field warning names each dropped one.
traveler_idsarray of stringrequiredThe travelers the files belong to, in the same order as documents. Send the field once for each file, or one comma-separated value (1,2), or a JSON array as text (["1","2"]). A traveler may appear once per request.
documentsarray of stringrequiredOne passport file for each traveler: PDF or an image, 10 MiB at most each, 25 MiB for all together. The field name must be documents.
replacestringtrue to overwrite travelers that already have a passport (1, yes, on and 0, no, off are read too). Default false.
One of true, false.
agency_idstringNot needed: the agency of a call is always the one of your API key. The field is accepted so that older clients keep working, and it is ignored.
bookingIdstringdeprecatedLegacy name, still accepted. Use booking_id instead.
The older spelling of booking_id.
orderIdstringdeprecatedLegacy name, still accepted. Use booking_id instead.
The older spelling of booking_id.
agencyIdstringdeprecatedLegacy name, still accepted. Use agency_id instead.
The older spelling of agency_id.
travelerIdsstringdeprecatedLegacy name, still accepted. Use traveler_ids instead.
The older spelling of traveler_ids.
travelerIdstringdeprecatedLegacy name, still accepted. Use traveler_ids instead.
The older spelling of traveler_ids.
traveler_idstringdeprecatedLegacy name, still accepted. Use traveler_ids instead.
The older spelling of traveler_ids.
filesstringdeprecatedLegacy name, still accepted. Use documents instead.
The older spelling of documents.
filestringdeprecatedLegacy name, still accepted. Use documents instead.
The older spelling of documents.
documentstringdeprecatedLegacy name, still accepted. Use documents instead.
The older spelling of documents.
Response
200Every file was saved.
environmentstringThe environment of the key that made the call: sandbox for fxp_test_ keys, live for fxp_live_ keys. Present in every JSON body.
One of sandbox, live.
request_idstringThe id of the request, also in the X-Request-Id header.
warningsarray of objectProblems that did not stop the upload.
successbooleantrue for 200 and for 207.
statusstringsuccess: every file stored. partial_success: some stored, some failed (HTTP 207).
One of success, partial_success.
requestIdstringdeprecatedLegacy name, still accepted. Use request_id instead.
The id of this request, 8 characters, for support.
booking_idstringdeprecatedLegacy name, still accepted. Use pnr instead.
The PNR.
pnrstringThe PNR. The snake_case twin of booking_id, with the same value: use this name.
messagestringA sentence: 2 documents saved.
summaryobjectThe counts of this request.
ShowHide 4 attributes
receivedintegerFiles in the request.
uploadedintegerNew passports.
replacedintegerReplaced passports.
failedintegerFiles that failed.
resultsarray of objectOne entry for each file, in the order you sent them.
ShowHide 9 attributes
indexintegerThe position of the file in your request, from 0.
traveler_idstringThe traveler of this file.
traveler_namestringnullableThe name on the booking, null when the id is not on the booking.
filenamestringThe file name you sent (document-<n> when the part had none).
statusstringuploaded: a new passport. replaced: a passport that existed was replaced. failed: this file has a problem (see error). skipped: this file is valid, but another file of the request failed, so it was not processed.
One of uploaded, replaced, failed, skipped.
content_typestringOnly for uploaded and replaced: the detected type.
size_bytesintegerOnly for uploaded and replaced: the size of the stored file.
download_urlstringOnly for uploaded and replaced: the public address of the stored file.
errorobjectOnly for failed: what is wrong with this file.
ShowHide 6 attributes
codestringThe code of the problem.
messagestringA sentence about it.
expected_traveler_idsarray of stringUNKNOWN_TRAVELER_ID: the ids of the booking.
accepted_extensionsarray of stringUNSUPPORTED_FILE_TYPE: the accepted extensions.
max_file_size_bytesintegerFILE_TOO_LARGE: the limit.
reasonstringSTORAGE_UPLOAD_FAILED: always the fixed text storage_error. The message of the storage system is never returned.
acdobjectThe ACD state of a booking: what is needed, what was uploaded, and whether you can upload.
ShowHide 17 attributes
booking_idstringdeprecatedLegacy name, still accepted. Use pnr instead.
The PNR of the booking.
pnrstringThe PNR of the booking. The snake_case twin of booking_id, with the same value: use this name.
acd_requiredbooleantrue when the booking needs ACD passports and FX-Port did not waive them.
acd_waivedbooleantrue when FX-Port marked ACD as not required for this booking. No upload is accepted then. You cannot set it.
acd_submittedbooleantrue once every traveler has a passport. Never null. Also true for a hold that has a complete set.
acd_paidbooleantrue once FX-Port's team has marked the ACD amount of the booking as paid. You cannot set it.
completebooleanEvery required passport is uploaded. Always false when ACD is not required.
can_uploadbooleanWhether upload_acd_documents accepts files for this booking right now.
blocked_reasonstringnullableWhy can_upload is false. null otherwise.
One of ACD_NOT_REQUIRED, ACD_WAIVED, BOOKING_NOT_ELIGIBLE, TRAVELERS_NOT_AVAILABLE.
required_documentsintegerThe number of passports needed: one for each traveler of the booking. 0 when ACD is not required.
uploaded_documentsintegerHow many of the required passports are uploaded.
missing_documentsintegerHow many are still missing.
missing_traveler_idsarray of stringThe travelers who still need a passport. Use these values in traveler_ids.
travelersarray of objectEvery traveler of the booking.
ShowHide 6 attributes
traveler_idstringThe id to send in traveler_ids: the id of the passenger on the booking ("1", "2" for Amadeus Algeria, pas_... for Duffel).
traveler_namestringThe name on the booking.
traveler_typestringThe passenger type, for example ADULT.
ticket_numberstringnullableThe e-ticket number. null until the airline returns one: always for a hold, and for an instant booking whose number has not arrived.
statusstringWhether this traveler has a passport.
One of uploaded, missing.
documentobjectnullableThe current passport file of a traveler.
ShowHide 6 attributes
filenamestringnullableThe file name you sent.
content_typestringThe type FX-Port detected from the content (for example application/, image/), not the one you sent.
size_bytesintegerThe size of the stored file.
download_urlstringA public address of the file: anyone who has it can download the passport. Treat it as sensitive.
uploaded_atstringISO 8601 UTC time of the upload, with +00:00.
replaced_countintegerHow many times the passport was replaced.
unassigned_documentsarray of objectFiles that do not count as a traveler's passport.
ShowHide 1 attribute
download_urlstringA public address of the file.
revisionintegerIncreases by one with each upload that stored a file. 0 for a booking without any.
limitsobjectThe upload limits. Read them instead of hardcoding them.
ShowHide 5 attributes
accepted_formatsarray of stringThe 11 accepted formats.
accepted_extensionsarray of stringThe 20 extensions of those formats.
max_file_size_bytesintegerThe largest accepted file: 10485760 (10 MiB).
max_request_size_bytesintegerThe largest total size of the files of one request: 26214400 (25 MiB).
max_documents_per_requestintegerThe number of travelers of the booking: the most files one request can carry.
notificationobjectWhether the ACD team was notified. The e-mail goes to FX-Port, not to you or the traveler.
ShowHide 5 attributes
sentbooleanWhether FX-Port's ACD team was e-mailed.
typestringsubmitted when the set became complete, updated after a replacement on a complete set. Absent when nothing was due.
One of submitted, updated.
skippedbooleantrue when no e-mail was due or sent: see reason.
reasonstringWhy no e-mail was sent: awaiting_remaining_documents, hold_booking or sandbox_environment; with error it is the fixed text email_error (the message of the mail system is never returned).
errorstringEMAIL_SEND_FAILED when the e-mail failed. The files are saved.
207Some files were saved and others failed. results[] says which, with a code for each; send the failed travelers again.
environmentstringThe environment of the key that made the call: sandbox for fxp_test_ keys, live for fxp_live_ keys. Present in every JSON body.
One of sandbox, live.
request_idstringThe id of the request, also in the X-Request-Id header.
warningsarray of objectProblems that did not stop the upload.
successbooleantrue for 200 and for 207.
statusstringsuccess: every file stored. partial_success: some stored, some failed (HTTP 207).
One of success, partial_success.
requestIdstringdeprecatedLegacy name, still accepted. Use request_id instead.
The id of this request, 8 characters, for support.
booking_idstringdeprecatedLegacy name, still accepted. Use pnr instead.
The PNR.
pnrstringThe PNR. The snake_case twin of booking_id, with the same value: use this name.
messagestringA sentence: 2 documents saved.
summaryobjectThe counts of this request.
ShowHide 4 attributes
receivedintegerFiles in the request.
uploadedintegerNew passports.
replacedintegerReplaced passports.
failedintegerFiles that failed.
resultsarray of objectOne entry for each file, in the order you sent them.
ShowHide 9 attributes
indexintegerThe position of the file in your request, from 0.
traveler_idstringThe traveler of this file.
traveler_namestringnullableThe name on the booking, null when the id is not on the booking.
filenamestringThe file name you sent (document-<n> when the part had none).
statusstringuploaded: a new passport. replaced: a passport that existed was replaced. failed: this file has a problem (see error). skipped: this file is valid, but another file of the request failed, so it was not processed.
One of uploaded, replaced, failed, skipped.
content_typestringOnly for uploaded and replaced: the detected type.
size_bytesintegerOnly for uploaded and replaced: the size of the stored file.
download_urlstringOnly for uploaded and replaced: the public address of the stored file.
errorobjectOnly for failed: what is wrong with this file.
ShowHide 6 attributes
codestringThe code of the problem.
messagestringA sentence about it.
expected_traveler_idsarray of stringUNKNOWN_TRAVELER_ID: the ids of the booking.
accepted_extensionsarray of stringUNSUPPORTED_FILE_TYPE: the accepted extensions.
max_file_size_bytesintegerFILE_TOO_LARGE: the limit.
reasonstringSTORAGE_UPLOAD_FAILED: always the fixed text storage_error. The message of the storage system is never returned.
acdobjectThe ACD state of a booking: what is needed, what was uploaded, and whether you can upload.
ShowHide 17 attributes
booking_idstringdeprecatedLegacy name, still accepted. Use pnr instead.
The PNR of the booking.
pnrstringThe PNR of the booking. The snake_case twin of booking_id, with the same value: use this name.
acd_requiredbooleantrue when the booking needs ACD passports and FX-Port did not waive them.
acd_waivedbooleantrue when FX-Port marked ACD as not required for this booking. No upload is accepted then. You cannot set it.
acd_submittedbooleantrue once every traveler has a passport. Never null. Also true for a hold that has a complete set.
acd_paidbooleantrue once FX-Port's team has marked the ACD amount of the booking as paid. You cannot set it.
completebooleanEvery required passport is uploaded. Always false when ACD is not required.
can_uploadbooleanWhether upload_acd_documents accepts files for this booking right now.
blocked_reasonstringnullableWhy can_upload is false. null otherwise.
One of ACD_NOT_REQUIRED, ACD_WAIVED, BOOKING_NOT_ELIGIBLE, TRAVELERS_NOT_AVAILABLE.
required_documentsintegerThe number of passports needed: one for each traveler of the booking. 0 when ACD is not required.
uploaded_documentsintegerHow many of the required passports are uploaded.
missing_documentsintegerHow many are still missing.
missing_traveler_idsarray of stringThe travelers who still need a passport. Use these values in traveler_ids.
travelersarray of objectEvery traveler of the booking.
ShowHide 6 attributes
traveler_idstringThe id to send in traveler_ids: the id of the passenger on the booking ("1", "2" for Amadeus Algeria, pas_... for Duffel).
traveler_namestringThe name on the booking.
traveler_typestringThe passenger type, for example ADULT.
ticket_numberstringnullableThe e-ticket number. null until the airline returns one: always for a hold, and for an instant booking whose number has not arrived.
statusstringWhether this traveler has a passport.
One of uploaded, missing.
documentobjectnullableThe current passport file of a traveler.
ShowHide 6 attributes
filenamestringnullableThe file name you sent.
content_typestringThe type FX-Port detected from the content (for example application/, image/), not the one you sent.
size_bytesintegerThe size of the stored file.
download_urlstringA public address of the file: anyone who has it can download the passport. Treat it as sensitive.
uploaded_atstringISO 8601 UTC time of the upload, with +00:00.
replaced_countintegerHow many times the passport was replaced.
unassigned_documentsarray of objectFiles that do not count as a traveler's passport.
ShowHide 1 attribute
download_urlstringA public address of the file.
revisionintegerIncreases by one with each upload that stored a file. 0 for a booking without any.
limitsobjectThe upload limits. Read them instead of hardcoding them.
ShowHide 5 attributes
accepted_formatsarray of stringThe 11 accepted formats.
accepted_extensionsarray of stringThe 20 extensions of those formats.
max_file_size_bytesintegerThe largest accepted file: 10485760 (10 MiB).
max_request_size_bytesintegerThe largest total size of the files of one request: 26214400 (25 MiB).
max_documents_per_requestintegerThe number of travelers of the booking: the most files one request can carry.
notificationobjectWhether the ACD team was notified. The e-mail goes to FX-Port, not to you or the traveler.
ShowHide 5 attributes
sentbooleanWhether FX-Port's ACD team was e-mailed.
typestringsubmitted when the set became complete, updated after a replacement on a complete set. Absent when nothing was due.
One of submitted, updated.
skippedbooleantrue when no e-mail was due or sent: see reason.
reasonstringWhy no e-mail was sent: awaiting_remaining_documents, hold_booking or sandbox_environment; with error it is the fixed text email_error (the message of the mail system is never returned).
errorstringEMAIL_SEND_FAILED when the e-mail failed. The files are saved.
Errors
| Status | Code | Example message |
|---|---|---|
| 400 | DOCUMENT_COUNT_MISMATCH | traveler_ids and documents must have the same number of entries (they are matched by order). |
| 400 | VALIDATION_FAILED | 2 of 2 documents were rejected. Nothing was uploaded. |
| 400 | MISSING_BOOKING_ID | Missing required form field: booking_id |
| 403 | read_write_permission_required | This endpoint requires an API key with read_write permission. |
| 404 | BOOKING_NOT_FOUND | No flight booking found for 'ZZZZZZ'. |
| 409 | DOCUMENT_ALREADY_UPLOADED | A passport is already uploaded for traveler '2'. Send replace=true to replace it. |
| 409 | ACD_NOT_REQUIRED | This booking does not require ACD documents. |
| 413 | FILE_TOO_LARGE | Uploaded document 'big.pdf' exceeds the 10 MB limit. |
| 415 | UNSUPPORTED_FILE_TYPE | 'bad.pdf' is not a supported passport document. Upload a PDF or a JPEG, PNG, WebP, HEIC/HEIF, AVIF, TIFF, BMP, GIF, JPEG 2000 or JPEG XL image. |
| 415 | UNSUPPORTED_MEDIA_TYPE | Send multipart/form-data with the fields booking_id, traveler_ids and documents. |
| 502 | STORAGE_UPLOAD_FAILED | None of the documents could be stored. Nothing was changed. Please retry. |
| 503 | STORAGE_UNAVAILABLE | Document storage is temporarily unavailable. Please retry. |
The shape of the error body depends on the endpoint. See Errors for every shape and code.