{
  "info": {
    "name": "Novus Onboarding API v1.0",
    "_postman_id": "8f7b2c9a-1d4e-4a6b-9c3d-novusonboard01",
    "description": "Onboarding πελατών ηλεκτρονικής τιμολόγησης — για software houses που συνεργάζονται με τη **Novus Conceptus**.\n\n## Πριν ξεκινήσετε\n\n1. Στις **Variables** της collection βάλτε το `apiKey` σας (ζητήστε κλειδί dev από τη Novus).\n2. Το `baseUrl` δείχνει εξ ορισμού στο **δοκιμαστικό** `https://provider-dev.timologisi.online`.\n   Για παραγωγή αλλάξτε το σε `https://provider.timologisi.online`.\n3. Και τα δύο περιβάλλοντα έχουν **Swagger UI στο `/index.html`**.\n\n## Σειρά εκτέλεσης\n\nΤρέξτε τα αιτήματα με τη σειρά του φακέλου «1. Ροή onboarding». Το `requestId` και το\n`Idempotency-Key` αποθηκεύονται **αυτόματα** σε μεταβλητές της collection από τα scripts,\nοπότε τα επόμενα αιτήματα δουλεύουν χωρίς copy-paste.\n\n## Προσοχή\n\n- Το ΑΦΜ επικυρώνεται **με check digit** — δοκιμαστικά «123456789» απορρίπτονται.\n- Μία ανοιχτή αίτηση ανά ΑΦΜ ανά software house.\n- Rate limit: **60 κλήσεις/λεπτό** ανά κλειδί.",
    "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
  },
  "auth": {
    "type": "apikey",
    "apikey": [
      {
        "key": "key",
        "value": "API-KEY",
        "type": "string"
      },
      {
        "key": "value",
        "value": "{{apiKey}}",
        "type": "string"
      },
      {
        "key": "in",
        "value": "header",
        "type": "string"
      }
    ]
  },
  "event": [
    {
      "listen": "prerequest",
      "script": {
        "type": "text/javascript",
        "exec": [
          "// Προειδοποίηση αν δεν έχει οριστεί κλειδί",
          "if (!pm.collectionVariables.get('apiKey')) {",
          "    console.warn('Δεν έχει οριστεί apiKey στις Variables της collection.');",
          "}"
        ]
      }
    },
    {
      "listen": "test",
      "script": {
        "type": "text/javascript",
        "exec": [
          "// Κάθε απάντηση έρχεται μέσα σε φάκελο { success, data } ή { success, error }",
          "pm.test('Απάντηση σε JSON φάκελο', function () {",
          "    const j = pm.response.json();",
          "    pm.expect(j).to.have.property('success');",
          "});",
          "",
          "// Δείχνει πόσες κλήσεις απομένουν στο λεπτό (όριο 60/λεπτό ανά κλειδί)",
          "const remaining = pm.response.headers.get('X-RateLimit-Remaining');",
          "if (remaining !== null) { console.log('X-RateLimit-Remaining:', remaining); }"
        ]
      }
    }
  ],
  "variable": [
    {
      "key": "baseUrl",
      "value": "https://provider-dev.timologisi.online",
      "type": "string",
      "description": "Δοκιμαστικό. Παραγωγή: https://provider.timologisi.online"
    },
    {
      "key": "apiKey",
      "value": "",
      "type": "string",
      "description": "Το API key σας — το ίδιο που χρησιμοποιείτε για αποστολή παραστατικών."
    },
    {
      "key": "vatNumber",
      "value": "094019245",
      "type": "string",
      "description": "ΑΦΜ του τελικού πελάτη. Πρέπει να είναι έγκυρο (check digit)."
    },
    {
      "key": "requestId",
      "value": "",
      "type": "string",
      "description": "Συμπληρώνεται αυτόματα από τη «Δημιουργία αίτησης»."
    },
    {
      "key": "idempotencyKey",
      "value": "",
      "type": "string",
      "description": "Συμπληρώνεται αυτόματα. Ένα ανά πελάτη, σταθερό στα retries."
    },
    {
      "key": "endpointId",
      "value": "",
      "type": "string",
      "description": "Συμπληρώνεται αυτόματα από τη «Εγγραφή webhook»."
    },
    {
      "key": "webhookSecret",
      "value": "",
      "type": "string",
      "description": "Επιστρέφεται μόνο μία φορά, στην εγγραφή του webhook."
    }
  ],
  "item": [
    {
      "name": "1. Ροή onboarding",
      "description": "Τα αιτήματα με τη σειρά που τα καλείτε σε έναν πραγματικό πελάτη.",
      "item": [
        {
          "name": "1.1 Δημιουργία αίτησης",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Idempotency-Key",
                "value": "{{idempotencyKey}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/api/v1/requests",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "v1",
                "requests"
              ]
            },
            "description": "Δημιουργεί αίτηση onboarding για έναν τελικό πελάτη.\n\nΟ **τύπος** αποφασίζεται από το σύστημα:\n- `NEW_CONTRACT` — παράγεται σύμβαση PDF, status `PENDING_SIGNATURE`.\n- `LINK_EXISTING` — το ΑΦΜ έχει ήδη σύμβαση· `contract: null`, status `UNDER_REVIEW`.\n\nΤο `Idempotency-Key` παράγεται αυτόματα στο pre-request script και αποθηκεύεται,\nώστε ένα retry να μη δημιουργήσει δεύτερη αίτηση.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"companyDetails\": {\n    \"legalName\": \"Παπαδόπουλος Α.Ε.\",\n    \"tradeName\": \"PapaCorp\",\n    \"vatNumber\": \"{{vatNumber}}\",\n    \"taxOffice\": \"Α΄ Αθηνών\",\n    \"transactionTypes\": [\n      \"B2B\",\n      \"B2C\"\n    ]\n  },\n  \"address\": {\n    \"city\": \"Αθήνα\",\n    \"streetAddress\": \"Ερμού 15\",\n    \"postalCode\": \"10563\"\n  },\n  \"contactInfo\": {\n    \"email\": \"info@papacorp.gr\",\n    \"phone\": \"2101234567\",\n    \"backupPhone\": \"6971234567\"\n  },\n  \"administrator\": {\n    \"fullName\": \"Γιάννης Παπαδόπουλος\",\n    \"vatNumber\": \"090000045\"\n  },\n  \"ispDetails\": {\n    \"providerName\": \"Cosmote\",\n    \"contractNumber\": \"CSM-2026-1\",\n    \"contractDate\": \"2026-09-01\"\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": [],
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Ένα Idempotency-Key ανά πελάτη, σταθερό σε όλα τα retries.",
                  "if (!pm.collectionVariables.get('idempotencyKey')) {",
                  "    pm.collectionVariables.set('idempotencyKey', pm.variables.replaceIn('{{$guid}}'));",
                  "}"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('201 Created (ή 200 σε retry με το ίδιο Idempotency-Key)', function () {",
                  "    pm.expect(pm.response.code).to.be.oneOf([200, 201]);",
                  "});",
                  "",
                  "const j = pm.response.json();",
                  "if (j.success) {",
                  "    pm.collectionVariables.set('requestId', j.data.requestId);",
                  "    console.log('requestId:', j.data.requestId, '| type:', j.data.requestType, '| status:', j.data.status);",
                  "",
                  "    pm.test('Αποθηκεύτηκε το requestId', function () {",
                  "        pm.expect(j.data.requestId).to.be.a('string');",
                  "    });",
                  "",
                  "    pm.test('Σε LINK_EXISTING το contract είναι null — δεν είναι σφάλμα', function () {",
                  "        if (j.data.requestType === 'LINK_EXISTING') {",
                  "            pm.expect(j.data.contract).to.equal(null);",
                  "        }",
                  "    });",
                  "} else {",
                  "    // 409 DUPLICATE_OPEN_REQUEST δίνει το υπάρχον requestId στο context",
                  "    if (j.error && j.error.context && j.error.context.existingRequestId) {",
                  "        pm.collectionVariables.set('requestId', j.error.context.existingRequestId);",
                  "        console.log('Υπάρχει ήδη ανοιχτή αίτηση:', j.error.context.existingRequestId);",
                  "    }",
                  "}"
                ]
              }
            }
          ]
        },
        {
          "name": "1.2 Λήψη σύμβασης (PDF)",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/pdf"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/api/v1/requests/{{requestId}}/contract?kind=unsigned",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "v1",
                "requests",
                "{{requestId}}",
                "contract"
              ],
              "query": [
                {
                  "key": "kind",
                  "value": "unsigned",
                  "description": "unsigned (default) ή signed"
                }
              ]
            },
            "description": "Κατεβάζει τη σύμβαση. **Θέλει το API key σας** — δεν είναι δημόσιο link.\nΚατεβάστε το PDF και προωθήστε το εσείς στον πελάτη.\n\n`kind=unsigned` (default) ή `kind=signed`. Με `version` παίρνετε συγκεκριμένη έκδοση\nτου υπογεγραμμένου· χωρίς αυτό, την τελευταία."
          },
          "response": [],
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Επιστρέφει PDF', function () {",
                  "    pm.response.to.have.status(200);",
                  "    pm.expect(pm.response.headers.get('Content-Type')).to.include('application/pdf');",
                  "});"
                ]
              }
            }
          ]
        },
        {
          "name": "1.3 Ανέβασμα υπογεγραμμένης",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/api/v1/requests/{{requestId}}/signed-contract",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "v1",
                "requests",
                "{{requestId}}",
                "signed-contract"
              ]
            },
            "description": "`multipart/form-data`, ένα αρχείο στο πεδίο **`contractFile`**.\n\nΕπιλέξτε το αρχείο στο tab **Body → form-data** πριν στείλετε.\n\nΚανόνες: πραγματικό PDF (ελέγχονται τα magic bytes), έως **15 MB**, όχι κλειδωμένο,\nstatus `PENDING_SIGNATURE` ή `ACTION_REQUIRED`, `requestType = NEW_CONTRACT`.\n\nΤα παλιά αρχεία δεν σβήνονται — κάθε ανέβασμα είναι νέα έκδοση.",
            "body": {
              "mode": "formdata",
              "formdata": [
                {
                  "key": "contractFile",
                  "type": "file",
                  "src": [],
                  "description": "Το υπογεγραμμένο PDF, έως 15 MB."
                }
              ]
            }
          },
          "response": [],
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('200 και status UNDER_REVIEW', function () {",
                  "    pm.response.to.have.status(200);",
                  "    const j = pm.response.json();",
                  "    pm.expect(j.data.status).to.equal('UNDER_REVIEW');",
                  "});",
                  "",
                  "const j = pm.response.json();",
                  "if (j.success && j.data.contract) {",
                  "    console.log('signedVersion:', j.data.contract.signedVersion, '| sha256:', j.data.contract.sha256);",
                  "}"
                ]
              }
            }
          ]
        },
        {
          "name": "1.4 Κατάσταση αίτησης",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/api/v1/requests/{{requestId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "v1",
                "requests",
                "{{requestId}}"
              ]
            },
            "description": "Η **πραγματική** κατάσταση. Μετά το `APPROVED` εμφανίζονται τα `provisioning` και\n`aadeStatement`.\n\n`provisioning.status = COMPLETED` σημαίνει ότι ο πελάτης είναι ενεργός και μπορεί να εκδίδει.\n`FAILED` το αναλαμβάνει η Novus — μην ξαναστείλετε αίτηση."
          },
          "response": [],
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const j = pm.response.json();",
                  "pm.test('200 OK', function () { pm.response.to.have.status(200); });",
                  "",
                  "if (j.success) {",
                  "    console.log('status:', j.data.status);",
                  "    if (j.data.provisioning) {",
                  "        console.log('provisioning:', j.data.provisioning.status);",
                  "    }",
                  "    if (j.data.aadeStatement) {",
                  "        console.log('Δήλωση ΑΑΔΕ:', j.data.aadeStatement.status);",
                  "    }",
                  "",
                  "    pm.test('Ο πελάτης είναι ενεργός', function () {",
                  "        pm.expect(j.data.provisioning && j.data.provisioning.status).to.equal('COMPLETED');",
                  "    });",
                  "}"
                ]
              }
            }
          ]
        }
      ]
    },
    {
      "name": "2. Αιτήσεις — λίστα, ιστορικό, ακύρωση",
      "item": [
        {
          "name": "Λίστα αιτήσεων",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/api/v1/requests?status=UNDER_REVIEW,ACTION_REQUIRED&page=1&pageSize=50&sort=createdAt:desc",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "v1",
                "requests"
              ],
              "query": [
                {
                  "key": "status",
                  "value": "UNDER_REVIEW,ACTION_REQUIRED",
                  "description": "Ένα ή περισσότερα, χωρισμένα με κόμμα"
                },
                {
                  "key": "page",
                  "value": "1",
                  "description": "Από 1"
                },
                {
                  "key": "pageSize",
                  "value": "50",
                  "description": "Max 200"
                },
                {
                  "key": "sort",
                  "value": "createdAt:desc",
                  "description": "createdAt|updatedAt, :asc|:desc"
                }
              ]
            },
            "description": "Επιστρέφει **μόνο τις δικές σας** αιτήσεις. Αίτηση άλλου software house δίνει `404`."
          },
          "response": [],
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('200 με items/total/page/pageSize', function () {",
                  "    pm.response.to.have.status(200);",
                  "    const d = pm.response.json().data;",
                  "    pm.expect(d).to.have.all.keys('items', 'total', 'page', 'pageSize');",
                  "});"
                ]
              }
            }
          ]
        },
        {
          "name": "Λίστα — φιλτράρισμα ανά ΑΦΜ",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/api/v1/requests?vatNumber={{vatNumber}}&createdFrom=2026-01-01&createdTo=2026-12-31",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "v1",
                "requests"
              ],
              "query": [
                {
                  "key": "vatNumber",
                  "value": "{{vatNumber}}",
                  "description": "ΑΦΜ πελάτη"
                },
                {
                  "key": "createdFrom",
                  "value": "2026-01-01",
                  "description": "YYYY-MM-DD"
                },
                {
                  "key": "createdTo",
                  "value": "2026-12-31",
                  "description": "YYYY-MM-DD"
                }
              ]
            },
            "description": "Ίδιο endpoint, με φίλτρο ΑΦΜ και ημερομηνιών."
          },
          "response": []
        },
        {
          "name": "Ιστορικό αίτησης",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/api/v1/requests/{{requestId}}/history",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "v1",
                "requests",
                "{{requestId}}",
                "history"
              ]
            },
            "description": "Χρονολογική λίστα των αλλαγών status — χρήσιμο όταν ο πελάτης ανέβασε μόνος του τη σύμβαση."
          },
          "response": []
        },
        {
          "name": "Ακύρωση αίτησης",
          "request": {
            "method": "DELETE",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/api/v1/requests/{{requestId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "v1",
                "requests",
                "{{requestId}}"
              ]
            },
            "description": "Επιτρέπεται **μόνο πριν την έγκριση**.\n\nΣε `APPROVED` επιστρέφει `409` — η διακοπή συνεργασίας γίνεται με επικοινωνία\nμε τη Novus, όχι από το API.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"reason\": \"Ο πελάτης άλλαξε γνώμη\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": [],
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('200 ή 409 αν είναι ήδη εγκεκριμένη', function () {",
                  "    pm.expect(pm.response.code).to.be.oneOf([200, 409]);",
                  "});"
                ]
              }
            }
          ]
        }
      ]
    },
    {
      "name": "3. Webhooks",
      "description": "Προαιρετικά, αντί για polling. Πάντα επαληθεύετε την υπογραφή και επιβεβαιώνετε με GET.",
      "item": [
        {
          "name": "Εγγραφή webhook",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/api/v1/webhooks",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "v1",
                "webhooks"
              ]
            },
            "description": "Η απάντηση περιέχει ένα **`secret` που επιστρέφεται μόνο μία φορά**.\nΤο script το αποθηκεύει στη μεταβλητή `webhookSecret`.\n\nΜε αυτό επαληθεύετε το header `X-Novus-Signature`:\n`sha256=<HMAC-SHA256 του ακριβούς body>`.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"url\": \"https://to-logismiko-sas.gr/novus-events\",\n  \"events\": [\n    \"request.status_changed\",\n    \"request.provisioned\"\n  ]\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": [],
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const j = pm.response.json();",
                  "if (j.success) {",
                  "    pm.collectionVariables.set('endpointId', j.data.endpointId);",
                  "    if (j.data.secret) {",
                  "        pm.collectionVariables.set('webhookSecret', j.data.secret);",
                  "        console.log('Το secret αποθηκεύτηκε — δεν θα ξαναδοθεί.');",
                  "    }",
                  "}",
                  "",
                  "pm.test('201 με endpointId και secret', function () {",
                  "    pm.response.to.have.status(201);",
                  "    pm.expect(j.data).to.have.property('endpointId');",
                  "    pm.expect(j.data).to.have.property('secret');",
                  "});"
                ]
              }
            }
          ]
        },
        {
          "name": "Τα webhook endpoints μου",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/api/v1/webhooks",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "v1",
                "webhooks"
              ]
            },
            "description": "Λίστα των endpoints σας. Τα secrets δεν επιστρέφονται."
          },
          "response": []
        },
        {
          "name": "Ιστορικό αποστολών (debugging)",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/api/v1/webhooks/deliveries?endpointId={{endpointId}}&page=1&pageSize=50",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "v1",
                "webhooks",
                "deliveries"
              ],
              "query": [
                {
                  "key": "endpointId",
                  "value": "{{endpointId}}",
                  "description": "Προαιρετικό φίλτρο"
                },
                {
                  "key": "page",
                  "value": "1",
                  "description": ""
                },
                {
                  "key": "pageSize",
                  "value": "50",
                  "description": ""
                }
              ]
            },
            "description": "Δείχνει τι στάλθηκε και τι απαντήσατε. Retries: 1m, 5m, 30m, 2h, 12h και μετά εγκατάλειψη."
          },
          "response": []
        },
        {
          "name": "Διαγραφή webhook",
          "request": {
            "method": "DELETE",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/api/v1/webhooks/{{endpointId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "v1",
                "webhooks",
                "{{endpointId}}"
              ]
            },
            "description": "Σταματά τις ειδοποιήσεις προς αυτό το endpoint."
          },
          "response": [],
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('204 No Content', function () { pm.response.to.have.status(204); });"
                ]
              }
            }
          ]
        }
      ]
    },
    {
      "name": "4. Σενάρια σφαλμάτων (για δοκιμή του χειρισμού σας)",
      "description": "Σκόπιμα λανθασμένες κλήσεις, για να δείτε πώς απαντά το API και να δοκιμάσετε τον δικό σας κώδικα.",
      "item": [
        {
          "name": "422 — ΑΦΜ με λάθος check digit",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/api/v1/requests",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "v1",
                "requests"
              ]
            },
            "description": "Το «123456789» περνάει τον έλεγχο των 9 ψηφίων αλλά κόβεται στο check digit. Δείτε το `details[]`.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"companyDetails\": {\n    \"legalName\": \"Παπαδόπουλος Α.Ε.\",\n    \"tradeName\": \"PapaCorp\",\n    \"vatNumber\": \"123456789\",\n    \"taxOffice\": \"Α΄ Αθηνών\",\n    \"transactionTypes\": [\n      \"B2B\",\n      \"B2C\"\n    ]\n  },\n  \"address\": {\n    \"city\": \"Αθήνα\",\n    \"streetAddress\": \"Ερμού 15\",\n    \"postalCode\": \"10563\"\n  },\n  \"contactInfo\": {\n    \"email\": \"info@papacorp.gr\",\n    \"phone\": \"2101234567\",\n    \"backupPhone\": \"6971234567\"\n  },\n  \"administrator\": {\n    \"fullName\": \"Γιάννης Παπαδόπουλος\",\n    \"vatNumber\": \"090000045\"\n  },\n  \"ispDetails\": {\n    \"providerName\": \"Cosmote\",\n    \"contractNumber\": \"CSM-2026-1\",\n    \"contractDate\": \"2026-09-01\"\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": [],
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('422 VALIDATION_ERROR', function () {",
                  "    pm.response.to.have.status(422);",
                  "    const e = pm.response.json().error;",
                  "    pm.expect(e.code).to.equal('VALIDATION_ERROR');",
                  "    pm.expect(e.details).to.be.an('array').that.is.not.empty;",
                  "    console.log('Πεδίο που φταίει:', e.details[0].field);",
                  "});"
                ]
              }
            }
          ]
        },
        {
          "name": "422 — B2C χωρίς ispDetails",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/api/v1/requests",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "v1",
                "requests"
              ]
            },
            "description": "Όταν στα `transactionTypes` υπάρχει `B2C`, το `ispDetails` είναι υποχρεωτικό και θέλει και τα τρία πεδία.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"companyDetails\": {\n    \"legalName\": \"Παπαδόπουλος Α.Ε.\",\n    \"tradeName\": \"PapaCorp\",\n    \"vatNumber\": \"{{vatNumber}}\",\n    \"taxOffice\": \"Α΄ Αθηνών\",\n    \"transactionTypes\": [\n      \"B2B\",\n      \"B2C\"\n    ]\n  },\n  \"address\": {\n    \"city\": \"Αθήνα\",\n    \"streetAddress\": \"Ερμού 15\",\n    \"postalCode\": \"10563\"\n  },\n  \"contactInfo\": {\n    \"email\": \"info@papacorp.gr\",\n    \"phone\": \"2101234567\",\n    \"backupPhone\": \"6971234567\"\n  },\n  \"administrator\": {\n    \"fullName\": \"Γιάννης Παπαδόπουλος\",\n    \"vatNumber\": \"090000045\"\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": [],
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('422 VALIDATION_ERROR', function () { pm.response.to.have.status(422); });"
                ]
              }
            }
          ]
        },
        {
          "name": "422 — B2G δεν γίνεται δεκτό",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/api/v1/requests",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "v1",
                "requests"
              ]
            },
            "description": "Μόνο `B2B` και `B2C`.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"companyDetails\": {\n    \"legalName\": \"Παπαδόπουλος Α.Ε.\",\n    \"tradeName\": \"PapaCorp\",\n    \"vatNumber\": \"{{vatNumber}}\",\n    \"taxOffice\": \"Α΄ Αθηνών\",\n    \"transactionTypes\": [\n      \"B2B\",\n      \"B2G\"\n    ]\n  },\n  \"address\": {\n    \"city\": \"Αθήνα\",\n    \"streetAddress\": \"Ερμού 15\",\n    \"postalCode\": \"10563\"\n  },\n  \"contactInfo\": {\n    \"email\": \"info@papacorp.gr\",\n    \"phone\": \"2101234567\",\n    \"backupPhone\": \"6971234567\"\n  },\n  \"administrator\": {\n    \"fullName\": \"Γιάννης Παπαδόπουλος\",\n    \"vatNumber\": \"090000045\"\n  },\n  \"ispDetails\": {\n    \"providerName\": \"Cosmote\",\n    \"contractNumber\": \"CSM-2026-1\",\n    \"contractDate\": \"2026-09-01\"\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": [],
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('422 VALIDATION_ERROR', function () { pm.response.to.have.status(422); });"
                ]
              }
            }
          ]
        },
        {
          "name": "401 — χωρίς API key",
          "request": {
            "method": "GET",
            "auth": {
              "type": "noauth"
            },
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/api/v1/requests",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "v1",
                "requests"
              ]
            },
            "description": "Χωρίς το header `API-KEY` το API απαντά `401 UNAUTHORIZED`."
          },
          "response": [],
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('401 UNAUTHORIZED', function () {",
                  "    pm.response.to.have.status(401);",
                  "    pm.expect(pm.response.json().error.code).to.equal('UNAUTHORIZED');",
                  "});"
                ]
              }
            }
          ]
        },
        {
          "name": "404 — αίτηση άλλου software house",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/api/v1/requests/req_doesnotexist",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "v1",
                "requests",
                "req_doesnotexist"
              ]
            },
            "description": "Επιστρέφεται `404`, όχι `403`, ώστε να μη διαρρέει ούτε η ύπαρξη της αίτησης."
          },
          "response": [],
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('404 NOT_FOUND', function () {",
                  "    pm.response.to.have.status(404);",
                  "    pm.expect(pm.response.json().error.code).to.equal('NOT_FOUND');",
                  "});"
                ]
              }
            }
          ]
        },
        {
          "name": "409 — διπλή ανοιχτή αίτηση",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/api/v1/requests",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "v1",
                "requests"
              ]
            },
            "description": "Δεύτερη αίτηση για το ίδιο ΑΦΜ **χωρίς** `Idempotency-Key`.\n\nΤο API απαντά `409 DUPLICATE_OPEN_REQUEST` και δίνει το υπάρχον `requestId`\nστο `context.existingRequestId` — δεν δημιουργείται διπλή αίτηση.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"companyDetails\": {\n    \"legalName\": \"Παπαδόπουλος Α.Ε.\",\n    \"tradeName\": \"PapaCorp\",\n    \"vatNumber\": \"{{vatNumber}}\",\n    \"taxOffice\": \"Α΄ Αθηνών\",\n    \"transactionTypes\": [\n      \"B2B\",\n      \"B2C\"\n    ]\n  },\n  \"address\": {\n    \"city\": \"Αθήνα\",\n    \"streetAddress\": \"Ερμού 15\",\n    \"postalCode\": \"10563\"\n  },\n  \"contactInfo\": {\n    \"email\": \"info@papacorp.gr\",\n    \"phone\": \"2101234567\",\n    \"backupPhone\": \"6971234567\"\n  },\n  \"administrator\": {\n    \"fullName\": \"Γιάννης Παπαδόπουλος\",\n    \"vatNumber\": \"090000045\"\n  },\n  \"ispDetails\": {\n    \"providerName\": \"Cosmote\",\n    \"contractNumber\": \"CSM-2026-1\",\n    \"contractDate\": \"2026-09-01\"\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": [],
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('409 DUPLICATE_OPEN_REQUEST με existingRequestId', function () {",
                  "    pm.response.to.have.status(409);",
                  "    const e = pm.response.json().error;",
                  "    pm.expect(e.code).to.equal('DUPLICATE_OPEN_REQUEST');",
                  "    pm.expect(e.context).to.have.property('existingRequestId');",
                  "});"
                ]
              }
            }
          ]
        },
        {
          "name": "415 — αρχείο που δεν είναι PDF",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/api/v1/requests/{{requestId}}/signed-contract",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "v1",
                "requests",
                "{{requestId}}",
                "signed-contract"
              ]
            },
            "description": "Ανεβάστε π.χ. ένα .jpg μετονομασμένο σε .pdf — ελέγχονται τα magic bytes, όχι η κατάληξη.",
            "body": {
              "mode": "formdata",
              "formdata": [
                {
                  "key": "contractFile",
                  "type": "file",
                  "src": [],
                  "description": "Επιλέξτε κάτι που δεν είναι PDF."
                }
              ]
            }
          },
          "response": [],
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('415 UNSUPPORTED_FILE_TYPE', function () {",
                  "    pm.response.to.have.status(415);",
                  "});"
                ]
              }
            }
          ]
        }
      ]
    },
    {
      "name": "5. Παραλλαγές",
      "item": [
        {
          "name": "Δημιουργία αίτησης — μόνο B2B",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Idempotency-Key",
                "value": "{{idempotencyKey}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/api/v1/requests",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "v1",
                "requests"
              ]
            },
            "description": "Χωρίς `B2C`, το `ispDetails` είναι προαιρετικό και παραλείπεται.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"companyDetails\": {\n    \"legalName\": \"Παπαδόπουλος Α.Ε.\",\n    \"vatNumber\": \"{{vatNumber}}\",\n    \"taxOffice\": \"Α΄ Αθηνών\",\n    \"transactionTypes\": [\n      \"B2B\"\n    ]\n  },\n  \"address\": {\n    \"city\": \"Αθήνα\",\n    \"streetAddress\": \"Ερμού 15\",\n    \"postalCode\": \"10563\"\n  },\n  \"contactInfo\": {\n    \"email\": \"info@papacorp.gr\",\n    \"phone\": \"2101234567\"\n  },\n  \"administrator\": {\n    \"fullName\": \"Γιάννης Παπαδόπουλος\",\n    \"vatNumber\": \"090000045\"\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": []
        },
        {
          "name": "Λήψη υπογεγραμμένης — συγκεκριμένη έκδοση",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/pdf"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/api/v1/requests/{{requestId}}/contract?kind=signed&version=1",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "api",
                "v1",
                "requests",
                "{{requestId}}",
                "contract"
              ],
              "query": [
                {
                  "key": "kind",
                  "value": "signed",
                  "description": "signed"
                },
                {
                  "key": "version",
                  "value": "1",
                  "description": "Αριθμός έκδοσης"
                }
              ]
            },
            "description": "Κάθε ανέβασμα είναι νέα έκδοση και τα παλιά αρχεία δεν σβήνονται."
          },
          "response": []
        }
      ]
    }
  ]
}