{
  "info": {
    "name": "Quatlas Integration API v1",
    "description": "Generated from the Quatlas Integration API OpenAPI document (v1). Set apiKey to your key (a test key qk_test_... to start), or to an OAuth access token. Docs: https://quatlasglobal.com/developers/",
    "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
  },
  "auth": {
    "type": "bearer",
    "bearer": [
      {
        "key": "token",
        "value": "{{apiKey}}",
        "type": "string"
      }
    ]
  },
  "variable": [
    {
      "key": "baseUrl",
      "value": "https://app.quatlasglobal.com"
    },
    {
      "key": "apiKey",
      "value": ""
    },
    {
      "key": "clientId",
      "value": ""
    },
    {
      "key": "clientSecret",
      "value": ""
    }
  ],
  "item": [
    {
      "name": "Shipment requests",
      "item": [
        {
          "name": "Create a shipment request",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "X-Idempotency-Key",
                "value": "{{$guid}}"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/api/integrations/v1/shipment-requests",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "integrations",
                "v1",
                "shipment-requests"
              ],
              "query": [],
              "variable": []
            },
            "description": "Requires the requests:write scope and an X-Idempotency-Key header (the same key with a different body answers 422 Integration.IdempotencyKeyReused). A repeated externalReference returns 409 with the existing request's id. With a test key (qk_test_...) the request goes to your sandbox and the answer says livemode: false.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"cargo\": {\n    \"description\": \"12 pallets of spare parts\",\n    \"packageCount\": 12,\n    \"weightKg\": 8400\n  },\n  \"costCenter\": \"LOJ-210\",\n  \"destination\": {\n    \"countryCode\": \"DE\",\n    \"city\": \"Hamburg\"\n  },\n  \"externalReference\": \"QS-0001\",\n  \"incoterm\": \"FCA\",\n  \"metadata\": {},\n  \"modePolicy\": \"Required\",\n  \"origin\": {\n    \"countryCode\": \"TR\",\n    \"city\": \"Istanbul\"\n  },\n  \"purchaseOrderNumber\": \"PO-88412\",\n  \"transportMode\": \"Road\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Find a shipment request by your externalReference",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/integrations/v1/shipment-requests?externalReference=QS-0001",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "integrations",
                "v1",
                "shipment-requests"
              ],
              "query": [
                {
                  "key": "externalReference",
                  "value": "QS-0001",
                  "disabled": false
                }
              ],
              "variable": []
            },
            "description": "Requires the requests:read scope and the externalReference query parameter. A test key reads your sandbox. Answers with the request's ETag, as the read by id does."
          }
        },
        {
          "name": "Read a shipment request's status",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/integrations/v1/shipment-requests/:id",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "integrations",
                "v1",
                "shipment-requests",
                ":id"
              ],
              "query": [],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "A UUID"
                }
              ]
            },
            "description": "Requires the requests:read scope. Only your company's own requests exist here; a test key reads your sandbox. The ETag header is the request's version: send it back as If-Match on an update or a cancel to refuse the write (412) if the request changed in between."
          }
        },
        {
          "name": "Update a shipment request",
          "request": {
            "method": "PATCH",
            "header": [
              {
                "key": "X-Idempotency-Key",
                "value": "{{$guid}}"
              },
              {
                "key": "If-Match",
                "value": "",
                "disabled": true
              },
              {
                "key": "Content-Type",
                "value": "application/merge-patch+json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/api/integrations/v1/shipment-requests/:id",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "integrations",
                "v1",
                "shipment-requests",
                ":id"
              ],
              "query": [],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "A UUID"
                }
              ]
            },
            "description": "Requires the requests:write scope and an X-Idempotency-Key header; If-Match (the read's ETag) is optional and answers 412 when the request changed since, checked under the request's lock so two writers with one tag cannot both win. Only a request your systems created through the API (409 Integration.RequestNotCreatedViaApi for one opened in the app). A JSON merge patch of at most 64 KB (413 Integration.BodyTooLarge): a field you leave out stays as it is; null clears a field where the app lets an owner clear it, and is refused (Integration.FieldNotClearable) where it does not. The listing's fields change only while the request is Published, as in the app (409 ShipmentRequest.InvalidTransition otherwise); a material change holds every standing offer until its carrier reconfirms, and extends the bidding to at least 12 hours. Your metadata, purchaseOrderNumber and costCenter change in any state. The whole patch is written in one transaction, or nothing is, and raises one request.updated. Answers the request as the read does, with its new ETag.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"notes\": \"Forklift at both ends\",\n  \"metadata\": {\n    \"costCenter\": \"LOJ-210\"\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Read a shipment request by its business number",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/integrations/v1/shipment-requests/by-number/:number",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "integrations",
                "v1",
                "shipment-requests",
                "by-number",
                ":number"
              ],
              "query": [],
              "variable": [
                {
                  "key": "number",
                  "value": ""
                }
              ]
            },
            "description": "Requires the requests:read scope. The request's business number (REQ-048213), in any case, with or without the REQ- prefix (another kind's prefix is 400 Integration.InvalidBusinessNumber). Answers exactly as the read by id, ETag included, under the same rules: only your company's own requests; any other number is 404. A test key reads your sandbox."
          }
        },
        {
          "name": "Cancel a shipment request",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "X-Idempotency-Key",
                "value": "{{$guid}}"
              },
              {
                "key": "If-Match",
                "value": "",
                "disabled": true
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/api/integrations/v1/shipment-requests/:id/cancel",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "integrations",
                "v1",
                "shipment-requests",
                ":id",
                "cancel"
              ],
              "query": [],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "A UUID"
                }
              ]
            },
            "description": "Requires the requests:write scope and an X-Idempotency-Key header; If-Match is optional (412 on a mismatch, checked under the request's lock). Only a request your systems created through the API (409 Integration.RequestNotCreatedViaApi). Cancels where the app lets an owner cancel: Draft, Published or BiddingClosed; the pending offers expire and their carriers are told, as in the app. Cancelling a cancelled request answers it again (200). Once awarded the request is a deal: 409 Integration.RequestAwarded with the dealId; withdrawing a deal or cancelling its operation is done in the app.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"reason\": \"Cancelled in the customer's system\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Read an operation by its business number",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/integrations/v1/operations/by-number/:number",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "integrations",
                "v1",
                "operations",
                "by-number",
                ":number"
              ],
              "query": [],
              "variable": [
                {
                  "key": "number",
                  "value": ""
                }
              ]
            },
            "description": "Requires the tracking:read scope. The operation's business number (SHPT-012618), in any case, with or without the SHPT- prefix (another kind's prefix is 400 Integration.InvalidBusinessNumber). Answers exactly as /deals/{id}/tracking for its deal: an operation of a deal your company is not a party to, or a number that does not exist, is 404."
          }
        }
      ]
    },
    {
      "name": "Sandbox",
      "item": [
        {
          "name": "Move a sandbox request to its next status (test keys only)",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/api/integrations/v1/sandbox/shipment-requests/:id/advance",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "integrations",
                "v1",
                "sandbox",
                "shipment-requests",
                ":id",
                "advance"
              ],
              "query": [],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "A UUID"
                }
              ]
            },
            "description": "Requires a test key (qk_test_...) with the requests:write scope. Moves a sandbox request through the life a real one has: Published, then BidReceived (any number of times), Awarded, Delivered. A live key gets 403 and nothing real ever moves.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"step\": \"Published\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        }
      ]
    },
    {
      "name": "Deals",
      "item": [
        {
          "name": "List the deals made from a request",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/integrations/v1/deals?externalReference=QS-0001",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "integrations",
                "v1",
                "deals"
              ],
              "query": [
                {
                  "key": "shipmentRequestId",
                  "value": "",
                  "disabled": true
                },
                {
                  "key": "externalReference",
                  "value": "QS-0001",
                  "disabled": false
                }
              ],
              "variable": []
            },
            "description": "Requires the deals:read scope and exactly one of shipmentRequestId or externalReference (your own reference, for a request your company owns). Lists only the deals your company is a party to, oldest first; a request re-awarded after a withdrawal has more than one."
          }
        },
        {
          "name": "Read a deal",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/integrations/v1/deals/:id",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "integrations",
                "v1",
                "deals",
                ":id"
              ],
              "query": [],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "A UUID"
                }
              ]
            },
            "description": "Requires the deals:read scope. Your company's side, the agreed price and currency, the Incoterm, the counterparty's name, the stage and its dates, as your company sees them in the app. A deal your company is not a party to answers 404."
          }
        },
        {
          "name": "Read a deal by its business number",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/integrations/v1/deals/by-number/:number",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "integrations",
                "v1",
                "deals",
                "by-number",
                ":number"
              ],
              "query": [],
              "variable": [
                {
                  "key": "number",
                  "value": ""
                }
              ]
            },
            "description": "Requires the deals:read scope. The deal's business number (DEAL-206447), in any case, with or without the DEAL- prefix (another kind's prefix is 400 Integration.InvalidBusinessNumber). Answers exactly as the read by id: a deal your company is not a party to, or a number that does not exist, is 404."
          }
        },
        {
          "name": "Read a deal's operation",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/integrations/v1/deals/:id/tracking",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "integrations",
                "v1",
                "deals",
                ":id",
                "tracking"
              ],
              "query": [],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "A UUID"
                }
              ]
            },
            "description": "Requires the tracking:read scope. The milestones with their times, the planned delivery date (as agreed when the deal went live) and the current estimate, the delivery time and whether a proof of delivery exists. No live position. 404 until the deal goes live."
          }
        },
        {
          "name": "Read a deal's additional costs and statement totals",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/integrations/v1/deals/:id/costs",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "integrations",
                "v1",
                "deals",
                ":id",
                "costs"
              ],
              "query": [],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "A UUID"
                }
              ]
            },
            "description": "Requires the costs:read scope. Every additional cost on the operation's ledger, each Approved (the other side signed it off) or Declared (booked by one side alone), and the operation statement's totals per currency, never converted: the contract amount plus the agreed additional costs, with declared costs not yet agreed in declaredTotal beside them, never in the grand total."
          }
        }
      ]
    },
    {
      "name": "Documents",
      "item": [
        {
          "name": "Upload a document to a deal",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "X-Idempotency-Key",
                "value": "{{$guid}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/api/integrations/v1/deals/:id/documents",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "integrations",
                "v1",
                "deals",
                ":id",
                "documents"
              ],
              "query": [],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "A UUID"
                }
              ]
            },
            "description": "Requires the documents:write scope and an X-Idempotency-Key header. multipart/form-data with the file in 'file' and its category in 'category'. The category must be one the app offers your side at the deal's stage (400 Document.CategoryNotOffered otherwise): in the closing room the shipper uploads PackingList, CommercialInvoice, DangerousGoodsCertificate (hazardous cargo only), Other and LoadingInstruction, the carrier its transport document for the deal's mode, WeighbridgeTicket and Other; once the deal is live either side uploads any current category, the dangerous goods certificate still the shipper's for hazardous cargo and the loading instruction still the shipper's; a withdrawn deal takes none (409 Document.DealNotOpen). CustomsDeclaration, CertificateOfOrigin and InsuranceCertificate are retired and AdditionalCostReceipt belongs to a cost line in the app, so the API takes none of them. The app's own upload rules apply: at most 25 MB; PDF, JPEG, PNG, DOCX or XLSX, checked against the file's contents (a DOCX or XLSX must be a Word or Excel package, recognised through its main part as Office reads it; a Word document sent as XLSX, or a workbook sent as DOCX, is kept as what it is and answered with its real contentType and a fileName with that type's extension; any other archive or an empty file is refused, 415 saying what the file is); a malware scan before anything is stored (an infected file is refused with 422 Document.Infected and never stored); the loading instruction is the shipper's; the carrier's transport document waits for the shipper's approved papers. The same X-Idempotency-Key with a different file or category answers 422 Integration.IdempotencyKeyReused. The other party is told as in the app, and document.uploaded goes to both parties' webhooks. Your company's uploads through the API are capped per UTC day (by default 50 files and 250 MB on Exchange, 200 files and 1 GiB on Enterprise; 429 Integration.DailyUploadLimit). A test key's upload is checked and scanned the same way; the sandbox keeps the record, never the file.",
            "body": {
              "mode": "formdata",
              "formdata": [
                {
                  "key": "file",
                  "type": "file",
                  "src": []
                },
                {
                  "key": "category",
                  "type": "text",
                  "value": "CommercialInvoice"
                }
              ]
            }
          }
        },
        {
          "name": "List a deal's documents (metadata)",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/integrations/v1/deals/:id/documents",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "integrations",
                "v1",
                "deals",
                ":id",
                "documents"
              ],
              "query": [],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "A UUID"
                }
              ]
            },
            "description": "Requires the documents:read scope. The current version of each document in the deal's vault, newest first: category, file name, when and by which side it was uploaded, and its malware scan status. Never the file itself: download a Clean one through /documents/{id}/download."
          }
        },
        {
          "name": "Download a document",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/integrations/v1/documents/:id/download",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "integrations",
                "v1",
                "documents",
                ":id",
                "download"
              ],
              "query": [],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "A UUID"
                }
              ]
            },
            "description": "Requires the documents:read scope. Answers 302 to a download link that works for five minutes. Only the current version of a document of a deal your company is a party to, and only once it passed its malware scan (409 otherwise). Follow the redirect without sending your key to the storage host."
          }
        }
      ]
    },
    {
      "name": "Authentication",
      "item": [
        {
          "name": "Get an access token (OAuth 2.0 client credentials)",
          "request": {
            "method": "POST",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/integrations/v1/oauth/token",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "integrations",
                "v1",
                "oauth",
                "token"
              ],
              "query": [],
              "variable": []
            },
            "description": "Form-encoded, grant_type=client_credentials. Authenticate with HTTP Basic (client_secret_basic) or the client_id and client_secret fields (client_secret_post): the client id is your key's prefix, the secret is the key. Returns a bearer token valid for 15 minutes, for Authorization: Bearer on the other routes. Revoking the key refuses its tokens at once. A key whose \"direct key allowed\" setting is off works here only.",
            "body": {
              "mode": "urlencoded",
              "urlencoded": [
                {
                  "key": "grant_type",
                  "value": "client_credentials"
                },
                {
                  "key": "scope",
                  "value": "",
                  "disabled": true
                }
              ]
            },
            "auth": {
              "type": "basic",
              "basic": [
                {
                  "key": "username",
                  "value": "{{clientId}}",
                  "type": "string"
                },
                {
                  "key": "password",
                  "value": "{{clientSecret}}",
                  "type": "string"
                }
              ]
            }
          }
        }
      ]
    }
  ]
}
