{
  "info": {
    "_postman_id": "efac-v1-tenant-api",
    "name": "G-eFac Tenant API (/v1)",
    "description": "Tenant-facing API for e-CF emission and consulta. Tenant identity comes from the access token's `tenant` claim, never from the URL. Errors use RFC 7807 (`application/problem+json`).\n\n`/v1` is PROVISIONAL \u2014 not yet frozen. See docs/API-VERSIONING-POLICY.md.\n\nSet `baseUrl`, `clientId` and `clientSecret` as collection variables, then run **Getting started** top to bottom.",
    "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
  },
  "auth": {
    "type": "bearer",
    "bearer": [
      {
        "key": "token",
        "value": "{{accessToken}}",
        "type": "string"
      }
    ]
  },
  "variable": [
    {
      "key": "baseUrl",
      "value": "https://api.g-efac.com",
      "type": "string"
    },
    {
      "key": "clientId",
      "value": "",
      "type": "string"
    },
    {
      "key": "clientSecret",
      "value": "",
      "type": "string"
    },
    {
      "key": "accessToken",
      "value": "",
      "type": "string"
    },
    {
      "key": "redirectUri",
      "value": "",
      "type": "string"
    },
    {
      "key": "codeVerifier",
      "value": "",
      "type": "string"
    },
    {
      "key": "authorizationCode",
      "value": "",
      "type": "string"
    },
    {
      "key": "encf",
      "value": "",
      "type": "string"
    },
    {
      "key": "webhookId",
      "value": "",
      "type": "string"
    },
    {
      "key": "heldInvoiceId",
      "value": "",
      "type": "string"
    }
  ],
  "item": [
    {
      "name": "Getting started",
      "description": "Run these three requests top to bottom against a freshly provisioned client. Step 2 assumes an e-NCF sequence range for tipo 31 has already been authorized \u2014 if you have not done that yet, run Sequences > \"Authorize an e-NCF sequence range\" first.\n\nThe Representaci\u00f3n Impresa (RI) ingredients \u2014 `qrImage` and `securityCode` (plus `qrUrl` and `trackId`) \u2014 come back **inline in the emission response** (step 2). There is no separate RI-download endpoint: an emission\u2192poll\u2192download flow is not part of this contract.",
      "item": [
        {
          "name": "1. Get an access token",
          "request": {
            "auth": {
              "type": "noauth"
            },
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/x-www-form-urlencoded"
              }
            ],
            "body": {
              "mode": "urlencoded",
              "urlencoded": [
                {
                  "key": "grant_type",
                  "value": "client_credentials",
                  "type": "text"
                },
                {
                  "key": "client_id",
                  "value": "{{clientId}}",
                  "type": "text"
                },
                {
                  "key": "client_secret",
                  "value": "{{clientSecret}}",
                  "type": "text"
                },
                {
                  "key": "scope",
                  "value": "efac.v1",
                  "type": "text"
                }
              ]
            },
            "url": {
              "raw": "{{baseUrl}}/connect/token",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "connect",
                "token"
              ]
            },
            "description": "OAuth2 client-credentials grant. Described by the OpenAPI security scheme, not by a `/v1` path \u2014 it is not one of the contract's 9 paths, but it is how every request below gets its bearer token."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.collectionVariables.set(\"accessToken\", pm.response.json().access_token);"
                ]
              }
            }
          ],
          "response": []
        },
        {
          "name": "1b. Get an access token (authorization code + PKCE)",
          "request": {
            "auth": {
              "type": "noauth"
            },
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/x-www-form-urlencoded"
              }
            ],
            "body": {
              "mode": "urlencoded",
              "urlencoded": [
                {
                  "key": "grant_type",
                  "value": "authorization_code",
                  "type": "text"
                },
                {
                  "key": "code",
                  "value": "{{authorizationCode}}",
                  "type": "text"
                },
                {
                  "key": "redirect_uri",
                  "value": "{{redirectUri}}",
                  "type": "text"
                },
                {
                  "key": "code_verifier",
                  "value": "{{codeVerifier}}",
                  "type": "text"
                },
                {
                  "key": "client_id",
                  "value": "{{clientId}}",
                  "type": "text"
                }
              ]
            },
            "url": {
              "raw": "{{baseUrl}}/connect/token",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "connect",
                "token"
              ]
            },
            "description": "OAuth2 authorization-code grant with PKCE (the browser login flow used by the portals). This alternative to request 1 is a second way to obtain a bearer token; both grants land on the same `/connect/token` endpoint and issue tokens with the same `efac.v1` scope. PKCE `S256` is mandatory \u2014 `plain` is rejected.\n\nBefore running this request: generate a `code_verifier` and its `S256` challenge, then open\n`{{baseUrl}}/connect/authorize?response_type=code&client_id={{clientId}}&redirect_uri={{redirectUri}}&scope=efac.v1&code_challenge=<challenge>&code_challenge_method=S256`\nin a browser, log in, and capture the `code` query parameter from the redirect back to `redirectUri`. Set `authorizationCode` and `codeVerifier` as collection variables from that exchange, then run this request. `/connect/authorize` is described by the OpenAPI security scheme, not by a `/v1` path, so it does not appear as a request in this collection \u2014 it is a browser redirect, not a scripted call."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.collectionVariables.set(\"accessToken\", pm.response.json().access_token);"
                ]
              }
            }
          ],
          "response": []
        },
        {
          "name": "2. Emit a tipo 31 invoice (Factura de Cr\u00e9dito Fiscal)",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Idempotency-Key",
                "value": "",
                "disabled": true,
                "description": "Optional, 1-255 chars. Resending the same key with the same body replays the original response (Idempotent-Replayed: true) instead of re-emitting the e-CF, so a retried timeout cannot burn a second e-NCF. Reusing the key with a different body returns 409."
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"type\": \"31\",\n  \"seller\": { \"rnc\": \"101672919\", \"name\": \"Efac Test Emisor SRL\", \"address\": \"Av. Test 1\" },\n  \"buyer\": { \"rnc\": \"130862346\", \"name\": \"Cliente Test SA\" },\n  \"items\": [\n    { \"name\": \"Servicio de prueba\", \"isService\": true, \"quantity\": 1, \"unitPrice\": 100, \"tax\": \"Itbis18\", \"discountAmount\": 10, \"amount\": 90, \"propinaLegal\": false }\n  ],\n  \"adjustments\": [\n    { \"kind\": \"discount\", \"amount\": 20, \"tax\": \"Itbis18\", \"description\": \"Descuento por pronto pago\" }\n  ],\n  \"payment\": { \"method\": \"1\" },\n  \"issueDate\": \"2026-07-13\",\n  \"sequenceExpiry\": \"2027-12-31\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{baseUrl}}/v1/invoices",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "invoices"
              ]
            },
            "description": "Emits an e-CF from clean JSON. Returns `200` on a terminal estado or `202` (queued) with `encf`, `status`, `trackId`, `securityCode`, `qrUrl` and `qrImage` in the body."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.collectionVariables.set(\"encf\", pm.response.json().encf);"
                ]
              }
            }
          ],
          "response": []
        },
        {
          "name": "3. Check e-CF status by e-NCF",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/invoices/:encf",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "invoices",
                ":encf"
              ],
              "variable": [
                {
                  "key": "encf",
                  "value": "{{encf}}"
                }
              ]
            },
            "description": "Local and DGII status for the e-NCF captured from step 2."
          },
          "response": []
        }
      ]
    },
    {
      "name": "Invoices",
      "description": "One request per documented /v1/invoices* operation.",
      "item": [
        {
          "name": "Emit an e-CF",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Idempotency-Key",
                "value": "",
                "disabled": true,
                "description": "Optional, 1-255 chars. Resending the same key with the same body replays the original response (Idempotent-Replayed: true) instead of re-emitting the e-CF, so a retried timeout cannot burn a second e-NCF. Reusing the key with a different body returns 409."
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"type\": \"31\",\n  \"seller\": { \"rnc\": \"101672919\", \"name\": \"Efac Test Emisor SRL\", \"address\": \"Av. Test 1\" },\n  \"buyer\": { \"rnc\": \"130862346\", \"name\": \"Cliente Test SA\" },\n  \"items\": [\n    { \"name\": \"Servicio de prueba\", \"isService\": true, \"quantity\": 1, \"unitPrice\": 100, \"tax\": \"Itbis18\", \"discountAmount\": 10, \"amount\": 90, \"propinaLegal\": false }\n  ],\n  \"adjustments\": [\n    { \"kind\": \"discount\", \"amount\": 20, \"tax\": \"Itbis18\", \"description\": \"Descuento por pronto pago\" }\n  ],\n  \"payment\": { \"method\": \"1\" },\n  \"issueDate\": \"2026-07-13\",\n  \"sequenceExpiry\": \"2027-12-31\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{baseUrl}}/v1/invoices",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "invoices"
              ]
            },
            "description": "All ten of DGII's tipos are supported: 31, 32, 33, 34, 41, 43, 44, 45, 46 and 47. Tipos 33/34 require a `modifies` block; tipo 41 requires `buyer`."
          },
          "response": []
        },
        {
          "name": "List queued invoices",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/invoices?status=queued&limit=20&cursor=",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "invoices"
              ],
              "query": [
                {
                  "key": "status",
                  "value": "queued"
                },
                {
                  "key": "limit",
                  "value": "20"
                },
                {
                  "key": "cursor",
                  "value": ""
                }
              ]
            },
            "description": "This tenant's queued/pending invoices, paginated by cursor."
          },
          "response": []
        },
        {
          "name": "Get e-CF status",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/invoices/:encf",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "invoices",
                ":encf"
              ],
              "variable": [
                {
                  "key": "encf",
                  "value": "{{encf}}"
                }
              ]
            },
            "description": "Status of an issued e-CF, local and at DGII."
          },
          "response": []
        },
        {
          "name": "Get QR / RI ingredients (reprints)",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/invoices/:encf/qr",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "invoices",
                ":encf",
                "qr"
              ],
              "variable": [
                {
                  "key": "encf",
                  "value": "{{encf}}"
                }
              ]
            },
            "description": "QR / Representaci\u00f3n Impresa ingredients for an already-emitted e-CF. Reproduces the emission-time qrUrl exactly, so a reprint matches the original."
          },
          "response": []
        },
        {
          "name": "List issued invoices (history)",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/issued-invoices?q=&tipo=&estado=&page=1&pageSize=25",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "issued-invoices"
              ],
              "query": [
                {
                  "key": "q",
                  "value": ""
                },
                {
                  "key": "tipo",
                  "value": ""
                },
                {
                  "key": "estado",
                  "value": ""
                },
                {
                  "key": "page",
                  "value": "1"
                },
                {
                  "key": "pageSize",
                  "value": "25"
                }
              ]
            },
            "description": "This tenant's issued e-CF history, paged and searchable. `estado` is server-mapped to Aceptado / En proceso / Rechazado."
          },
          "response": []
        }
      ]
    },
    {
      "name": "Held invoices",
      "item": [
        {
          "name": "Hold a tipo-32 invoice",
          "request": {
            "method": "POST",
            "header": [
              { "key": "Content-Type", "value": "application/json" },
              { "key": "Idempotency-Key", "value": "", "disabled": true, "description": "Optional, 1-255 chars. Same key + same body replays the 201 (Idempotent-Replayed: true); a different body under the same key is 409." }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"type\": \"32\",\n  \"seller\": { \"rnc\": \"101672919\", \"name\": \"Efac Test Emisor SRL\", \"address\": \"Av. Test 1\" },\n  \"buyer\": null,\n  \"items\": [\n    { \"name\": \"Consumo\", \"isService\": false, \"quantity\": 1, \"unitPrice\": 100, \"tax\": \"Exento\", \"amount\": 100, \"propinaLegal\": false }\n  ],\n  \"payment\": { \"method\": \"1\" },\n  \"issueDate\": \"2026-09-03\"\n}",
              "options": { "raw": { "language": "json" } }
            },
            "url": { "raw": "{{baseUrl}}/v1/held-invoices", "host": ["{{baseUrl}}"], "path": ["v1", "held-invoices"] },
            "description": "Signs the invoice with the zero e-NCF (E320000000000) and returns a PROVISIONAL securityCode and QR. No e-NCF is consumed and nothing enters the fiscal record until you emit. Only type 32 is accepted; `encf` is refused. The provisional QR is not verifiable at DGII and changes on release."
          },
          "response": []
        },
        {
          "name": "List held invoices",
          "request": {
            "method": "GET",
            "header": [],
            "url": { "raw": "{{baseUrl}}/v1/held-invoices?status=Held&limit=50", "host": ["{{baseUrl}}"], "path": ["v1", "held-invoices"],
              "query": [ { "key": "status", "value": "Held", "description": "Held | Emitted | Abandoned (optional)" }, { "key": "limit", "value": "50", "description": "1-200, default 50" }, { "key": "cursor", "value": "", "disabled": true, "description": "nextCursor from the previous page" } ] },
            "description": "Oldest first. Page shape: { items, nextCursor, hasMore }."
          },
          "response": []
        },
        {
          "name": "Get a held invoice",
          "request": {
            "method": "GET",
            "header": [],
            "url": { "raw": "{{baseUrl}}/v1/held-invoices/:id", "host": ["{{baseUrl}}"], "path": ["v1", "held-invoices", ":id"], "variable": [ { "key": "id", "value": "{{heldInvoiceId}}" } ] },
            "description": "Status, provisional fields, the stored request, and emittedEncf once released. qrUrl/qrImage are null unless the status is Held."
          },
          "response": []
        },
        {
          "name": "Get the provisional QR",
          "request": {
            "method": "GET",
            "header": [],
            "url": { "raw": "{{baseUrl}}/v1/held-invoices/:id/qr", "host": ["{{baseUrl}}"], "path": ["v1", "held-invoices", ":id", "qr"], "variable": [ { "key": "id", "value": "{{heldInvoiceId}}" } ] },
            "description": "Served only while Held. 409 with the resulting encf after release (use /v1/invoices/{encf}/qr), 410 after abandonment."
          },
          "response": []
        },
        {
          "name": "Emit (release) a held invoice",
          "request": {
            "method": "POST",
            "header": [
              { "key": "Content-Type", "value": "application/json" },
              { "key": "Idempotency-Key", "value": "", "disabled": true, "description": "Optional. Protects against a double release: the fingerprint includes the hold id, so one key cannot replay another hold's response." }
            ],
            "body": { "mode": "raw", "raw": "{}", "options": { "raw": { "language": "json" } } },
            "url": { "raw": "{{baseUrl}}/v1/held-invoices/:id/emit", "host": ["{{baseUrl}}"], "path": ["v1", "held-invoices", ":id", "emit"], "variable": [ { "key": "id", "value": "{{heldInvoiceId}}" } ] },
            "description": "Allocates the e-NCF and emits exactly as POST /v1/invoices would, on the day of release. Body is `{}` under G-eFac numbering, or `{ \"encf\": \"E32…\" }` under external numbering. 202 + Location: /v1/invoices/{encf}. 409 (with encf) if already emitted, 410 if abandoned."
          },
          "response": []
        },
        {
          "name": "Abandon a held invoice",
          "request": {
            "method": "DELETE",
            "header": [],
            "url": { "raw": "{{baseUrl}}/v1/held-invoices/:id", "host": ["{{baseUrl}}"], "path": ["v1", "held-invoices", ":id"], "variable": [ { "key": "id", "value": "{{heldInvoiceId}}" } ] },
            "description": "204. Idempotent. 409 once the hold has been emitted — voiding a fiscal document is an anulación."
          },
          "response": []
        }
      ]
    },
    {
      "name": "Sequences",
      "description": "Authorize and inspect e-NCF sequence ranges. An invoice cannot be emitted for a tipo without an authorized, unexpired range.",
      "item": [
        {
          "name": "Authorize an e-NCF sequence range",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"type\": \"31\",\n  \"from\": 1,\n  \"to\": 1000,\n  \"expiry\": \"2027-12-31\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{baseUrl}}/v1/sequences",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "sequences"
              ]
            },
            "description": "Registers the DGII-authorized [from, to] e-NCF range for a tipo. Add an optional \"environment\" (TesteCF | CerteCF | ECF) to authorize the range in the tenant's NEXT step on the DGII ladder before it has been promoted there — production readiness asks for a range in the environment being entered. Omit it and the range lands in the environment this token addresses. Those two are the only accepted values; GET /v1/sequences/all reports both as \"environment\" and \"nextEnvironment\"."
          },
          "response": []
        },
        {
          "name": "Inspect authorized e-NCF ranges for a tipo",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/sequences?type=31",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "sequences"
              ],
              "query": [
                {
                  "key": "type",
                  "value": "31"
                },
                {
                  "key": "environment",
                  "value": "",
                  "disabled": true,
                  "description": "Optional. TesteCF | CerteCF | ECF. Reads the ranges of this token's own environment, or of the tenant's next step on the DGII ladder — the same two values POST /v1/sequences accepts, so a range pre-authorized for the next environment can be read back. Anything else is a 400."
                }
              ]
            }
          },
          "response": []
        },
        {
          "name": "List every authorized e-NCF range set for this tenant",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/sequences/all",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "sequences",
                "all"
              ],
              "query": [
                {
                  "key": "environment",
                  "value": "",
                  "disabled": true,
                  "description": "Optional, same two accepted values as on GET /v1/sequences. The response names the environment it describes (\"environment\") and the one that may be pre-authorized (\"nextEnvironment\", null when the tenant has no next step)."
                }
              ]
            }
          },
          "response": []
        }
      ]
    },
    {
      "name": "Directory",
      "description": "DGII directory lookups: this tenant's own descriptor, and lookups of counterparties.",
      "item": [
        {
          "name": "This tenant's directory descriptor",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/directory-registration",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "directory-registration"
              ]
            }
          },
          "response": []
        },
        {
          "name": "Look up a counterparty in the DGII directory",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/directory/:rnc",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "directory",
                ":rnc"
              ],
              "variable": [
                {
                  "key": "rnc",
                  "value": "130862346"
                }
              ]
            }
          },
          "response": []
        }
      ]
    },
    {
      "name": "Certificate",
      "description": "The tenant's own signing-certificate summary, metadata only.",
      "item": [
        {
          "name": "Read this tenant's current signing certificate summary",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/certificate",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "certificate"
              ]
            },
            "description": "Metadata only -- subject, thumbprint and expiry -- never the P12 or its passphrase. 404 when the tenant has no certificate on file; 501 when this host cannot answer at all (no ICertificateMetadataReader registered), which is a distinct condition from 404 for an alerting client that needs to tell 'nothing to report' from 'no way to know'."
          },
          "response": []
        }
      ]
    },
    {
      "name": "Archive",
      "description": "Fiscal archive reads for the tenant portal's Archivo fiscal: the 10-year retention window, per-month counts within a year, and the documents filed in a given month.",
      "item": [
        {
          "name": "10-year archive window with per-year totals",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/archive/years",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "archive",
                "years"
              ]
            },
            "description": "The 10 most recent calendar years (newest first), each with its issued-document total and the DGII-mandated retention expiry."
          },
          "response": []
        },
        {
          "name": "Per-month document counts for an archive year",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/archive/:year",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "archive",
                ":year"
              ],
              "variable": [
                {
                  "key": "year",
                  "value": "2026"
                }
              ]
            },
            "description": "12 months for the given year. A month later than today's is `null` (has not happened yet) rather than `0` (happened, nothing filed)."
          },
          "response": []
        },
        {
          "name": "Issued documents archived in a year/month",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/archive/:year/:month",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "archive",
                ":year",
                ":month"
              ],
              "variable": [
                {
                  "key": "year",
                  "value": "2026"
                },
                {
                  "key": "month",
                  "value": "3"
                }
              ]
            },
            "description": "Issued e-CF documents filed in the given month. `month` must be 1..12 (400 otherwise)."
          },
          "response": []
        }
      ]
    },
    {
      "name": "Void requests",
      "description": "Anulaci\u00f3n of an e-NCF range.",
      "item": [
        {
          "name": "Anular a range of e-NCFs",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"type\": \"31\",\n  \"from\": 1,\n  \"to\": 5\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{baseUrl}}/v1/void-requests",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "void-requests"
              ]
            }
          },
          "response": []
        }
      ]
    },
    {
      "name": "Commercial approvals",
      "description": "Send an outbound Aprobaci\u00f3n/Rechazo Comercial for a received e-CF.",
      "item": [
        {
          "name": "Send a commercial approval (or rejection)",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"sellerRnc\": \"130862346\",\n  \"encf\": \"E310000000001\",\n  \"approve\": true,\n  \"rejectionReason\": null\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{baseUrl}}/v1/commercial-approvals",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "commercial-approvals"
              ]
            },
            "description": "`rejectionReason` is required when `approve` is `false`."
          },
          "response": []
        }
      ]
    },
    {
      "name": "Received documents",
      "description": "Integrity check of an e-CF received from a counterparty.",
      "item": [
        {
          "name": "Integrity check of a received e-CF",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/received-documents?sellerRnc=130862346&encf=E310000000001",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "received-documents"
              ],
              "query": [
                {
                  "key": "sellerRnc",
                  "value": "130862346"
                },
                {
                  "key": "encf",
                  "value": "E310000000001"
                }
              ]
            }
          },
          "response": []
        },
        {
          "name": "List received invoices (history)",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/received-invoices",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "received-invoices"
              ]
            },
            "description": "This tenant's received e-CF history. `arecf` is always \"Recibido\" (only accepted docs are stored)."
          },
          "response": []
        }
      ]
    },
    {
      "name": "Webhooks",
      "description": "Push transport: signed callbacks. Endpoints are scoped to the token's environment, so a TesteCF credential registers a TesteCF endpoint and there is no way to express otherwise. Delivery is at-least-once and unordered \u2014 dedupe on the envelope's `id`.",
      "item": [
        {
          "name": "Register a callback endpoint",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"url\": \"https://erp.example.com/efac/callbacks\",\n  \"events\": [\n    \"invoice.accepted\",\n    \"invoice.rejected\"\n  ],\n  \"description\": \"ERP central\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{baseUrl}}/v1/webhooks",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "webhooks"
              ]
            },
            "description": "Registers a callback endpoint for the token's environment. The plaintext `secret` is returned ONCE, in this response, and is never recoverable afterwards \u2014 store it before you close the response, or rotate. `url` must be https, must not resolve to a private or link-local address, and is at most 2048 characters; `description` is at most 200. A 409 is either the per-environment endpoint cap or a URL this tenant already registered \u2014 the problem `type` fragment (`endpoint-limit` vs `duplicate-url`) distinguishes them. A 503 means this deployment has no certificate-vault master key configured; GET /v1/events serves the same events and needs no secret."
          },
          "response": []
        },
        {
          "name": "List callback endpoints",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/webhooks",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "webhooks"
              ]
            },
            "description": "This tenant's endpoints for the token's environment. Never returns a secret in any form. `disabledReason` and `disabledAt` are set when an endpoint was auto-disabled after repeated dead letters; re-enable it with PATCH."
          },
          "response": []
        },
        {
          "name": "Read one callback endpoint",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/webhooks/:id",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "webhooks",
                ":id"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "{{webhookId}}"
                }
              ]
            },
            "description": "An id belonging to another tenant answers 404, never 403, so this surface cannot be used to discover that another tenant's endpoint exists."
          },
          "response": []
        },
        {
          "name": "Update a callback endpoint",
          "request": {
            "method": "PATCH",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"url\": \"https://erp.example.com/efac/callbacks\",\n  \"events\": [\n    \"invoice.accepted\",\n    \"invoice.rejected\",\n    \"invoice.received\"\n  ],\n  \"enabled\": true\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{baseUrl}}/v1/webhooks/:id",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "webhooks",
                ":id"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "{{webhookId}}"
                }
              ]
            },
            "description": "Merge patch over `url`, `events` and `enabled` \u2014 send only what changes. This is also how an auto-disabled endpoint is re-enabled (`enabled: true`). `description` is NOT patchable: a request carrying it is refused with 400 rather than silently ignored. The URL policy and the duplicate-URL 409 apply here exactly as they do on create."
          },
          "response": []
        },
        {
          "name": "Rotate the signing secret",
          "request": {
            "method": "POST",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/webhooks/:id/rotate-secret",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "webhooks",
                ":id",
                "rotate-secret"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "{{webhookId}}"
                }
              ]
            },
            "description": "Issues a new secret, returned once, and keeps the previous one valid until `previousValidUntil`. Inside that window `X-Efac-Signature` carries two space-separated values \u2014 `v1=<new> v1=<old>` \u2014 and a correct verifier accepts if EITHER matches. Deploy the new secret before the window closes."
          },
          "response": []
        },
        {
          "name": "List recent delivery attempts",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/webhooks/:id/deliveries",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "webhooks",
                ":id",
                "deliveries"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "{{webhookId}}"
                }
              ]
            },
            "description": "Observability only \u2014 event id, event type, attempt count, last HTTP status, last error, `deliveredAt` and `deadLetteredAt`. No payload body and no replay. `eventType` reads `unknown` when the event itself has aged out of the retention window while a dead-lettered delivery for it was kept."
          },
          "response": []
        },
        {
          "name": "Delete a callback endpoint",
          "request": {
            "method": "DELETE",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/webhooks/:id",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "webhooks",
                ":id"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "{{webhookId}}"
                }
              ]
            },
            "description": "Removes the endpoint. Deliveries already queued for it are dropped rather than held."
          },
          "response": []
        }
      ]
    },
    {
      "name": "Events",
      "description": "The pull transport for the same events a webhook callback pushes.",
      "item": [
        {
          "name": "List events since a cursor",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/events?cursor=&limit=100&type=",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "events"
              ],
              "query": [
                {
                  "key": "cursor",
                  "value": ""
                },
                {
                  "key": "limit",
                  "value": "100"
                },
                {
                  "key": "type",
                  "value": "",
                  "disabled": true,
                  "description": "Optional, repeatable. invoice.accepted | invoice.rejected | invoice.received | commercial_approval.received | contingency.activated | sequence.low | certificate.expiring. Anything else is a 400."
                }
              ]
            },
            "description": "This tenant's event journal, oldest first \u2014 for clients that cannot host a webhook callback (a desktop ERP behind NAT, switched off at night). Send `nextCursor` back as `cursor` to resume, and treat it as opaque. `limit` is clamped to the server maximum, not rejected. A cursor older than the retention window answers 410 rather than a page with a hole in it: re-synchronise from /v1/invoices and /v1/received-invoices, then resume with no cursor."
          },
          "response": []
        }
      ]
    }
  ]
}
