{
  "info": {
    "name": "Sanad Connection — partner API (demo profile)",
    "version": "1.0.0+641634cf6d69",
    "description": "Generated from the Sanad Connection partner API contract (version 1.0.0+641634cf6d69). Set `baseUrl` and `apiKey`, then run the folders in order: a file upload saves `fileId` for the reply, any queued message saves `messageId`. Do not edit by hand: run scripts/connection-contract.py.",
    "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
  },
  "auth": {
    "type": "apikey",
    "apikey": [
      {
        "key": "key",
        "value": "X-Api-Key",
        "type": "string"
      },
      {
        "key": "value",
        "value": "{{apiKey}}",
        "type": "string"
      },
      {
        "key": "in",
        "value": "header",
        "type": "string"
      }
    ]
  },
  "variable": [
    {
      "key": "baseUrl",
      "value": "http://localhost:8090"
    },
    {
      "key": "apiKey",
      "value": "demo-partner-key-not-for-production"
    },
    {
      "key": "fileId",
      "value": ""
    },
    {
      "key": "messageId",
      "value": ""
    }
  ],
  "item": [
    {
      "name": "Customers",
      "item": [
        {
          "name": "Send a customer profile (flow 1)",
          "request": {
            "method": "PUT",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/customers/SME-100245",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "customers",
                "SME-100245"
              ]
            },
            "description": "Send the **full current profile** when a business completes onboarding and whenever its profile\nchanges. A change older than the last one Sanad applied is ignored (`ignored_older`), so messages\nmay arrive out of order safely. KYC status, expiry and risk are set from the profile only when it\ncreates the customer; after that the end-of-day KYC file is the source of truth. A profile whose\nmobile or email matches a customer an agent created by hand is linked to it (`linked`).\n\noperationId: putCustomer",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"legal_name_en\": \"Al-Waha Trading Est.\",\n  \"legal_name_ar\": \"مؤسسة الواحة التجارية\",\n  \"cr_number\": \"1010000419\",\n  \"mobile\": \"+966 11 000 0419\",\n  \"email\": \"finance@alwaha.example.com\",\n  \"city_code\": \"RUH\",\n  \"language\": \"AR\",\n  \"segment\": \"SMALL\",\n  \"products\": [\n    \"WC\",\n    \"INV\"\n  ],\n  \"account_status\": \"A\",\n  \"cr_expiry\": \"2027-03-14\",\n  \"kyc_status\": \"V\",\n  \"kyc_expiry\": \"2027-10-27\",\n  \"risk_rating\": \"L\",\n  \"updated_at\": \"2026-10-27T09:30:00Z\",\n  \"authorised_persons\": [\n    {\n      \"full_name\": \"Khalid Al-Otaibi\",\n      \"capacity\": \"OWNER\",\n      \"mobile\": \"+966 50 000 1245\",\n      \"id_type\": \"NATIONAL_ID\",\n      \"id_number\": \"1000001245\",\n      \"may_raise_cases\": true\n    }\n  ]\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('answer is described in the contract', function () { pm.expect(pm.response.code).to.be.oneOf([200, 202, 401, 403, 422]); });",
                  "var body = {}; try { body = pm.response.json(); } catch (e) {}",
                  "if (body.messageId) { pm.collectionVariables.set('messageId', body.messageId); }",
                  "if (body.file && body.file.fileId) { pm.collectionVariables.set('fileId', body.file.fileId); }"
                ]
              }
            }
          ]
        },
        {
          "name": "Read a customer's full current details (flow 6)",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/customers/SME-100245",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "customers",
                "SME-100245"
              ]
            },
            "description": "Sanad's current record in your field names. **Off by default:** it needs `allowRead` in your connector\nprofile and the workspace setting \"Let the partner's systems read full customer details\" in Sanad.\nEvery read is recorded in Sanad's audit trail; nothing is stored in Sanad Connection.\n\noperationId: getCustomer"
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('answer is described in the contract', function () { pm.expect(pm.response.code).to.be.oneOf([200, 401, 403, 404, 422, 503]); });",
                  "var body = {}; try { body = pm.response.json(); } catch (e) {}",
                  "if (body.messageId) { pm.collectionVariables.set('messageId', body.messageId); }",
                  "if (body.file && body.file.fileId) { pm.collectionVariables.set('fileId', body.file.fileId); }"
                ]
              }
            }
          ]
        },
        {
          "name": "One-time import of existing customers",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "text/csv"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/customers/import",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "customers",
                "import"
              ]
            },
            "description": "A CSV whose header line uses your profile field names (at most 50,000 customers). Rows are sent one by\none. If Sanad becomes unreachable the import stops and says how far it got (`503`); running it again is\nsafe, because existing customers are updated, not duplicated.\n\noperationId: importCustomers",
            "body": {
              "mode": "raw",
              "raw": "customer_no,legal_name_en,cr_number,city_code,language,segment,account_status\nSME-100245,Al-Waha Trading Est.,1010000419,RUH,AR,SMALL,A\nSME-100311,Najm Logistics,4030000311,JED,EN,MEDIUM,A\n",
              "options": {
                "raw": {
                  "language": "text"
                }
              }
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('answer is described in the contract', function () { pm.expect(pm.response.code).to.be.oneOf([200, 401, 403, 413, 422, 503]); });",
                  "var body = {}; try { body = pm.response.json(); } catch (e) {}",
                  "if (body.messageId) { pm.collectionVariables.set('messageId', body.messageId); }",
                  "if (body.file && body.file.fileId) { pm.collectionVariables.set('fileId', body.file.fileId); }"
                ]
              }
            }
          ]
        }
      ]
    },
    {
      "name": "Cases",
      "item": [
        {
          "name": "Send a case raised in the app (flow 2)",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/cases",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "cases"
              ]
            },
            "description": "The answer carries the case reference to show the customer at once. The same `submission_id` again\nreturns the first case. A case for a customer whose profile has not arrived yet is accepted and moves\nto the customer when the profile arrives (`customerLinked: false`). The priority follows the topic;\npartners cannot set it.\n\noperationId: postCase",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"submission_id\": \"app-7731\",\n  \"customer_no\": \"SME-100245\",\n  \"request_type\": \"COMPLAINT\",\n  \"topic\": \"COLLECTION_CALLS\",\n  \"title\": \"Collection calls after I paid\",\n  \"details\": \"I paid the October instalment on the 3rd but I am still being called every day.\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('answer is described in the contract', function () { pm.expect(pm.response.code).to.be.oneOf([201, 202, 401, 403, 422]); });",
                  "var body = {}; try { body = pm.response.json(); } catch (e) {}",
                  "if (body.messageId) { pm.collectionVariables.set('messageId', body.messageId); }",
                  "if (body.file && body.file.fileId) { pm.collectionVariables.set('fileId', body.file.fileId); }"
                ]
              }
            }
          ]
        },
        {
          "name": "Upload the customer's evidence (flow 8)",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/pdf"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/cases/app-7731/files?fileName=statement-october.pdf&uploadId=up-55120",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "cases",
                "app-7731",
                "files"
              ],
              "query": [
                {
                  "key": "fileName",
                  "value": "statement-october.pdf",
                  "description": "The name to show (at most 200 characters)."
                },
                {
                  "key": "uploadId",
                  "value": "up-55120",
                  "description": "Your own id for this upload, so a retry is not stored twice."
                }
              ]
            },
            "description": "The file itself is the body: PDF, JPG or PNG, up to 10 MB, at most 5 per reply. The type is checked from\nthe content, not the name. Upload each file first, then send the reply with the `fileId`s. Sanad checks\nevery file for viruses before anyone can open it; a file that fails is blocked and you are told\n(`file_blocked` case update). If Sanad has not received the case yet, the file is kept and retried\n(`202`).\n\noperationId: postCaseFile\n\nChoose a PDF, JPG or PNG file for the body.",
            "body": {
              "mode": "file",
              "file": {
                "src": ""
              }
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('answer is described in the contract', function () { pm.expect(pm.response.code).to.be.oneOf([201, 202, 401, 403, 413, 415, 422]); });",
                  "var body = {}; try { body = pm.response.json(); } catch (e) {}",
                  "if (body.messageId) { pm.collectionVariables.set('messageId', body.messageId); }",
                  "if (body.file && body.file.fileId) { pm.collectionVariables.set('fileId', body.file.fileId); }"
                ]
              }
            }
          ]
        },
        {
          "name": "Send the customer's reply (flow 9)",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/cases/app-7731/replies",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "cases",
                "app-7731",
                "replies"
              ]
            },
            "description": "A case waiting on the customer goes back to the team. A reply to a case that is already resolved or\nclosed is added for the team to see; it does not reopen the case by itself.\n\noperationId: postCaseReply",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"reply_id\": \"rep-9921\",\n  \"text\": \"Here is the bank statement showing both payments.\",\n  \"file_ids\": [\n    \"{{fileId}}\"\n  ]\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('answer is described in the contract', function () { pm.expect(pm.response.code).to.be.oneOf([201, 202, 401, 403, 422]); });",
                  "var body = {}; try { body = pm.response.json(); } catch (e) {}",
                  "if (body.messageId) { pm.collectionVariables.set('messageId', body.messageId); }",
                  "if (body.file && body.file.fileId) { pm.collectionVariables.set('fileId', body.file.fileId); }"
                ]
              }
            }
          ]
        }
      ]
    },
    {
      "name": "Daily files",
      "item": [
        {
          "name": "Send the end-of-day KYC file (flow 3)",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "text/csv"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/kyc-files?businessDate=2026-10-27",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "kyc-files"
              ],
              "query": [
                {
                  "key": "businessDate",
                  "value": "2026-10-27",
                  "description": ""
                }
              ]
            },
            "description": "One CSV per business date (UTF-8, header line, at most 50,000 lines) listing only businesses whose KYC\nchanged that day, by the deadline set in Sanad (default 23:00 Riyadh time). The answer is a line-by-line\nreceipt. Sending the same file again changes nothing; a corrected file for the same day replaces the\nearlier one. An expired or rejected KYC flags the profile and creates a follow-up task in Sanad.\n\noperationId: postKycFile",
            "body": {
              "mode": "raw",
              "raw": "partner_customer_no,kyc_status,kyc_review_date,kyc_expiry_date,cr_expiry_date,risk_rating\nSME-100245,V,2026-10-27,2027-10-27,2027-03-14,L\nSME-100311,E,2026-10-27,2026-10-27,2026-12-01,M\n",
              "options": {
                "raw": {
                  "language": "text"
                }
              }
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('answer is described in the contract', function () { pm.expect(pm.response.code).to.be.oneOf([200, 202, 401, 403, 413, 422]); });",
                  "var body = {}; try { body = pm.response.json(); } catch (e) {}",
                  "if (body.messageId) { pm.collectionVariables.set('messageId', body.messageId); }",
                  "if (body.file && body.file.fileId) { pm.collectionVariables.set('fileId', body.file.fileId); }"
                ]
              }
            }
          ]
        },
        {
          "name": "Send the nightly customer list (flow 5)",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "text/csv"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/reconciliations?businessDate=2026-10-27",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "reconciliations"
              ],
              "query": [
                {
                  "key": "businessDate",
                  "value": "2026-10-27",
                  "description": ""
                }
              ]
            },
            "description": "One CSV per night with every customer you have (at most 100,000 lines). Sanad compares it with its own\nrecords and answers with the differences: account or KYC status that differs, customers Sanad does not\nhave, and your customers in Sanad that are not on the list. Sending the same date again replaces the\nresult.\n\noperationId: postReconciliation",
            "body": {
              "mode": "raw",
              "raw": "partner_customer_no,account_status,kyc_status\nSME-100245,A,V\nSME-100311,F,E\n",
              "options": {
                "raw": {
                  "language": "text"
                }
              }
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('answer is described in the contract', function () { pm.expect(pm.response.code).to.be.oneOf([200, 202, 401, 403, 413, 422]); });",
                  "var body = {}; try { body = pm.response.json(); } catch (e) {}",
                  "if (body.messageId) { pm.collectionVariables.set('messageId', body.messageId); }",
                  "if (body.file && body.file.fileId) { pm.collectionVariables.set('fileId', body.file.fileId); }"
                ]
              }
            }
          ]
        }
      ]
    },
    {
      "name": "Files",
      "item": [
        {
          "name": "Download a file named in a case update",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/files/{{fileId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "files",
                "{{fileId}}"
              ]
            },
            "description": "An agent's document, or the customer's own evidence, once it passed the virus check.\n\noperationId: getFile"
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('answer is described in the contract', function () { pm.expect(pm.response.code).to.be.oneOf([200, 401, 403, 404, 409, 503]); });",
                  "var body = {}; try { body = pm.response.json(); } catch (e) {}",
                  "if (body.messageId) { pm.collectionVariables.set('messageId', body.messageId); }",
                  "if (body.file && body.file.fileId) { pm.collectionVariables.set('fileId', body.file.fileId); }"
                ]
              }
            }
          ]
        }
      ]
    },
    {
      "name": "Messages",
      "item": [
        {
          "name": "Ask about a message that was queued",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/messages/{{messageId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "messages",
                "{{messageId}}"
              ]
            },
            "description": "Messages are kept for 30 days after they finish. Only your own messages are visible.\n\noperationId: getMessage"
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('answer is described in the contract', function () { pm.expect(pm.response.code).to.be.oneOf([200, 401, 403, 404]); });",
                  "var body = {}; try { body = pm.response.json(); } catch (e) {}",
                  "if (body.messageId) { pm.collectionVariables.set('messageId', body.messageId); }",
                  "if (body.file && body.file.fileId) { pm.collectionVariables.set('fileId', body.file.fileId); }"
                ]
              }
            }
          ]
        }
      ]
    }
  ]
}
