{
  "openapi": "3.0.3",
  "info": {
    "title": "ERPly Pro PSFE API",
    "version": "0.1.0-dev",
    "description": "REST API for the ERPly Pro PSFE (Proveedor de Servicios de Facturación\nElectrónica) covering issuance, polling, reception, certification, and\noperational status. All write operations follow RFC 9457 (`application/problem+json`)\nfor error responses and are idempotent via the `Idempotency-Key` header.\n\nErrors share the `Problem` schema; consult [`/docs/errors`](https://api.erply.pro/docs/errors)\nfor the full DGII error dictionary.\n",
    "contact": {
      "name": "ERPly Pro Support",
      "url": "https://api.erply.pro/docs/support",
      "email": "soporte@erply.pro"
    },
    "license": {
      "name": "Apache-2.0",
      "url": "https://www.apache.org/licenses/LICENSE-2.0"
    }
  },
  "servers": [
    {
      "url": "https://api.erply.pro",
      "description": "Production"
    },
    {
      "url": "https://staging.api.erply.pro",
      "description": "Staging"
    },
    {
      "url": "https://dev.api.erply.pro",
      "description": "Development sandbox"
    },
    {
      "url": "https://sandbox.api.erply.pro",
      "description": "Public sandbox (rate-limited demo key)"
    }
  ],
  "tags": [
    {
      "name": "Companies",
      "description": "Tenant company configuration."
    },
    {
      "name": "Certification",
      "description": "DGII set-de-pruebas certification orchestration."
    },
    {
      "name": "Issuance",
      "description": "e-CF issuance."
    },
    {
      "name": "Status Polling",
      "description": "DGII validation status retrieval."
    },
    {
      "name": "Communication",
      "description": "Email/PDF delivery."
    },
    {
      "name": "Reception",
      "description": "Inbound documents addressed to the tenant."
    },
    {
      "name": "Approvals",
      "description": "Commercial approvals from buyers."
    },
    {
      "name": "Reporting",
      "description": "Historical totals."
    },
    {
      "name": "System Health",
      "description": "DGII operational status."
    },
    {
      "name": "Identity Validation",
      "description": "RNC/Cédula lookup and prefix search against the centralised DGII registry."
    }
  ],
  "security": [
    {
      "bearerAuth": [],
      "apiKeyAuth": []
    }
  ],
  "paths": {
    "/v1/company": {
      "post": {
        "operationId": "createCompany",
        "tags": [
          "Companies"
        ],
        "summary": "Register a new tenant company",
        "security": [
          {
            "apiKeyAuth": []
          }
        ],
        "description": "Registers a new tenant company within the middleware, securely\nconfiguring its RNC and initial environment variables. The RNC must\nbe valid per DGII registry; the response returns the canonical\n`tenantId` used for all subsequent calls.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateCompanyRequest"
              },
              "examples": {
                "demo": {
                  "$ref": "#/components/examples/CreateCompanyDemo"
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Company created.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Company"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        }
      }
    },
    "/v1/company/{id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/TenantId"
        }
      ],
      "get": {
        "operationId": "getCompany",
        "tags": [
          "Companies"
        ],
        "summary": "Fetch company configuration",
        "description": "Retrieves configuration for an existing tenant.",
        "responses": {
          "200": {
            "description": "Company.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Company"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        }
      },
      "patch": {
        "operationId": "patchCompany",
        "tags": [
          "Companies"
        ],
        "summary": "Update company configuration",
        "description": "Updates webhook URLs, base64 PDF logo, and other tenant config.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PatchCompanyRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated company.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Company"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        }
      }
    },
    "/v1/check-set-tests": {
      "post": {
        "operationId": "createCheckSetTests",
        "tags": [
          "Certification"
        ],
        "summary": "Initiate a DGII set-de-pruebas",
        "description": "Initiates the generation of the mandatory test set required by DGII\nto certify a company as an electronic issuer.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateCheckSetTestsRequest"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Test set submitted; poll status with `getCheckSetTests`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CheckSetTests"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        }
      }
    },
    "/v1/check-set-tests/{id}/idCompany/{idCompany}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/SetTestId"
        },
        {
          "$ref": "#/components/parameters/IdCompany"
        }
      ],
      "get": {
        "operationId": "getCheckSetTests",
        "tags": [
          "Certification"
        ],
        "summary": "Query set-de-pruebas status",
        "description": "Queries the validation status of a submitted test set.",
        "responses": {
          "200": {
            "description": "Test-set status.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CheckSetTests"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        }
      }
    },
    "/v1/provider-info": {
      "get": {
        "operationId": "getProviderInfo",
        "tags": [
          "Certification"
        ],
        "summary": "PSFE provider info",
        "description": "Retrieves the registered information of the certified PSFE provider (ERPly Pro).",
        "responses": {
          "200": {
            "description": "Provider info.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProviderInfo"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        }
      }
    },
    "/v1/invoices": {
      "post": {
        "operationId": "postInvoice",
        "tags": [
          "Issuance"
        ],
        "summary": "Emit an e-CF",
        "description": "Emite un comprobante fiscal electrónico (e-CF) firmado XAdES-BES y lo\nentrega a la DGII. Soporta los tipos 31 (Crédito Fiscal), 32 (Consumo),\n33 (Nota de Débito), 34 (Nota de Crédito), 41 (Compras), y 44 (Régimen\nEspecial). Esta operación es **idempotente** vía el header\n`Idempotency-Key`: la misma clave devuelve la respuesta original\ndurante 24 horas.\n\n---\n\nIssues a signed e-CF (XAdES-BES) and submits it to DGII. Supported\ntypes: 31 (Fiscal Credit), 32 (Consumption), 33 (Debit Note),\n34 (Credit Note), 41 (Purchases), 44 (Special Regime).\n",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/InvoiceRequest"
              },
              "examples": {
                "type31": {
                  "$ref": "#/components/examples/InvoiceType31"
                },
                "type32": {
                  "$ref": "#/components/examples/InvoiceType32"
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Replay (matches a previous Idempotency-Key).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InvoiceAccepted"
                }
              }
            }
          },
          "202": {
            "description": "Submitted; poll status via `getFiscalInvoice` or `getInvoiceByCompany`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InvoiceAccepted"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "504": {
            "$ref": "#/components/responses/GatewayTimeout"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl -X POST https://sandbox.api.erply.pro/v1/invoices \\\n  -H \"Authorization: Bearer $JWT\" \\\n  -H \"x-api-key: $API_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -H \"Idempotency-Key: $(uuidgen)\" \\\n  -d @invoice.json\n"
          },
          {
            "lang": "python",
            "label": "Python (httpx)",
            "source": "import os, uuid, httpx\nr = httpx.post(\n    \"https://sandbox.api.erply.pro/v1/invoices\",\n    headers={\n        \"Authorization\": f\"Bearer {os.environ['JWT']}\",\n        \"x-api-key\": os.environ[\"API_KEY\"],\n        \"Idempotency-Key\": str(uuid.uuid4()),\n    },\n    json=payload,\n    timeout=10,\n)\nr.raise_for_status()\n"
          },
          {
            "lang": "javascript",
            "label": "Node (axios)",
            "source": "import axios from \"axios\";\nimport { randomUUID } from \"node:crypto\";\nawait axios.post(\"https://sandbox.api.erply.pro/v1/invoices\", payload, {\n  headers: {\n    Authorization: `Bearer ${process.env.JWT}`,\n    \"x-api-key\": process.env.API_KEY,\n    \"Idempotency-Key\": randomUUID(),\n  },\n  timeout: 10_000,\n});\n"
          },
          {
            "lang": "python",
            "label": "Odoo (requests)",
            "source": "import uuid, requests\nself.env[\"ir.config_parameter\"].sudo().get_param(\"erplypro.endpoint\")\nrequests.post(\n    f\"{endpoint}/v1/invoices\",\n    headers={\n        \"Authorization\": f\"Bearer {jwt}\",\n        \"x-api-key\": api_key,\n        \"Idempotency-Key\": str(uuid.uuid4()),\n    },\n    json=payload,\n    timeout=10,\n).raise_for_status()\n"
          }
        ]
      }
    },
    "/v1/fiscal-invoices/{id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/InvoiceId"
        }
      ],
      "get": {
        "operationId": "getFiscalInvoice",
        "tags": [
          "Status Polling"
        ],
        "summary": "Get fiscal invoice (Type 31) status",
        "description": "Retrieves the DGII validation status of a Fiscal Credit e-CF (Type 31).",
        "responses": {
          "200": {
            "description": "Fiscal invoice status.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InvoiceStatus"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl https://sandbox.api.erply.pro/v1/fiscal-invoices/$ID \\\n  -H \"Authorization: Bearer $JWT\" -H \"x-api-key: $API_KEY\"\n"
          },
          {
            "lang": "python",
            "label": "Python (httpx)",
            "source": "import httpx\nr = httpx.get(f\"{base}/v1/fiscal-invoices/{id}\", headers=headers, timeout=5)\n"
          },
          {
            "lang": "javascript",
            "label": "Node (axios)",
            "source": "const r = await axios.get(`${base}/v1/fiscal-invoices/${id}`, { headers });\n"
          },
          {
            "lang": "python",
            "label": "Odoo (requests)",
            "source": "requests.get(f\"{base}/v1/fiscal-invoices/{id}\", headers=headers, timeout=5)\n"
          }
        ]
      }
    },
    "/v1/invoices/{id}/idCompany/{idCompany}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/InvoiceId"
        },
        {
          "$ref": "#/components/parameters/IdCompany"
        }
      ],
      "get": {
        "operationId": "getInvoiceByCompany",
        "tags": [
          "Status Polling"
        ],
        "summary": "Get consumption invoice (Type 32) status",
        "description": "Retrieves the validation status of standard Consumption e-CFs (Type 32),\nutilising the company ID for precise multi-tenant context routing.\n",
        "responses": {
          "200": {
            "description": "Invoice status.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InvoiceStatus"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        }
      }
    },
    "/v1/fiscal-invoices/notify-by-email": {
      "post": {
        "operationId": "notifyFiscalInvoiceByEmail",
        "tags": [
          "Communication"
        ],
        "summary": "Email XML/PDF containers to recipient",
        "description": "Triggers the middleware to generate XML and PDF containers for a\npreviously emitted e-CF and email them to the recipient.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/NotifyByEmailRequest"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Notification queued.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/NotificationAccepted"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        }
      }
    },
    "/v1/received-documents": {
      "get": {
        "operationId": "listReceivedDocuments",
        "tags": [
          "Reception"
        ],
        "summary": "List inbound e-CFs",
        "description": "Lists inbound documents where the tenant acts as the buyer.",
        "parameters": [
          {
            "in": "query",
            "name": "from",
            "schema": {
              "type": "string",
              "format": "date"
            },
            "description": "Start of date range (inclusive)."
          },
          {
            "in": "query",
            "name": "to",
            "schema": {
              "type": "string",
              "format": "date"
            },
            "description": "End of date range (inclusive)."
          },
          {
            "in": "query",
            "name": "rnc",
            "schema": {
              "type": "string",
              "pattern": "^[0-9]{9,11}$"
            },
            "description": "Issuer RNC filter."
          },
          {
            "in": "query",
            "name": "limit",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            }
          },
          {
            "in": "query",
            "name": "cursor",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated received documents.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ReceivedDocumentsPage"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        }
      }
    },
    "/v1/received-documents/{id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/ReceivedDocumentId"
        }
      ],
      "get": {
        "operationId": "getReceivedDocument",
        "tags": [
          "Reception"
        ],
        "summary": "Fetch a received document",
        "description": "Retrieves details and the XML payload of an individual received e-CF.",
        "responses": {
          "200": {
            "description": "Received document.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ReceivedDocument"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        }
      }
    },
    "/v1/reception/dgii/receive-document": {
      "post": {
        "operationId": "receiveDgiiDocument",
        "tags": [
          "Reception"
        ],
        "summary": "Inbound DGII reception webhook",
        "description": "Webhook or manual trigger to handle inbound reception of an e-CF directly from DGII.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ReceivedDocument"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Document accepted for processing.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ReceiveAccepted"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        }
      }
    },
    "/v1/received-commercial-approvals/{id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/InvoiceId"
        }
      ],
      "get": {
        "operationId": "getCommercialApproval",
        "tags": [
          "Approvals"
        ],
        "summary": "Query commercial approval status",
        "description": "Queries the status of commercial approvals from buyers for previously issued e-CFs.",
        "responses": {
          "200": {
            "description": "Commercial-approval status.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CommercialApproval"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        }
      }
    },
    "/v1/companies/{id}/emitted-documents": {
      "parameters": [
        {
          "$ref": "#/components/parameters/TenantId"
        }
      ],
      "get": {
        "operationId": "getCompanyEmittedDocuments",
        "tags": [
          "Reporting"
        ],
        "summary": "Total emitted documents for a tenant",
        "description": "Retrieves the historical total of e-CFs successfully emitted by a tenant.",
        "parameters": [
          {
            "in": "query",
            "name": "from",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "in": "query",
            "name": "to",
            "schema": {
              "type": "string",
              "format": "date"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Emitted-document totals.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EmittedDocumentsReport"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        }
      }
    },
    "/v1/check-dgii-status/idCompany/{idCompany}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/IdCompany"
        }
      ],
      "get": {
        "operationId": "getCheckDgiiStatus",
        "tags": [
          "System Health"
        ],
        "summary": "DGII operational status",
        "description": "Queries the current operational status and maintenance windows of the\nDGII web services so callers can pre-emptively handle transmission\ntimeouts.\n",
        "responses": {
          "200": {
            "description": "DGII status snapshot.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DgiiStatus"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "500": {
            "$ref": "#/components/responses/InternalServerError"
          }
        },
        "x-codeSamples": [
          {
            "lang": "curl",
            "label": "curl",
            "source": "curl https://sandbox.api.erply.pro/v1/check-dgii-status/idCompany/$ID \\\n  -H \"Authorization: Bearer $JWT\" -H \"x-api-key: $API_KEY\"\n"
          },
          {
            "lang": "python",
            "label": "Python (httpx)",
            "source": "import httpx\nr = httpx.get(f\"{base}/v1/check-dgii-status/idCompany/{id}\", headers=headers, timeout=5)\n"
          },
          {
            "lang": "javascript",
            "label": "Node (axios)",
            "source": "const r = await axios.get(`${base}/v1/check-dgii-status/idCompany/${id}`, { headers });\n"
          },
          {
            "lang": "python",
            "label": "Odoo (requests)",
            "source": "requests.get(f\"{base}/v1/check-dgii-status/idCompany/{id}\", headers=headers, timeout=5)\n"
          }
        ]
      }
    },
    "/v1/dgii/document/{document}": {
      "get": {
        "operationId": "getDocument",
        "tags": [
          "Identity Validation"
        ],
        "summary": "Lookup a single taxpayer by RNC or Cédula",
        "description": "Returns the DGII registry entry for the given RNC (9 or 11 digits) or Cédula (11 digits). Accepts with or without hyphens.\nResponse is CDN-cacheable (Cache-Control: public, max-age=300).\n",
        "parameters": [
          {
            "name": "document",
            "in": "path",
            "required": true,
            "description": "RNC (9 or 11 digits) or Cédula (11 digits with optional hyphens).",
            "schema": {
              "type": "string",
              "pattern": "^\\d{9,11}$|^\\d{3}-\\d{7}-\\d{1}$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Registry entry found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RegistryRow"
                }
              }
            }
          },
          "400": {
            "description": "Invalid document format.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "No registry entry for this document.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "shell",
            "label": "cURL",
            "source": "curl -s https://api.erply.pro/v1/dgii/document/130862346 -H \"x-api-key: $KEY\"\n"
          },
          {
            "lang": "python",
            "label": "Python (requests)",
            "source": "requests.get(f\"{base}/v1/dgii/document/130862346\", headers=headers, timeout=10)\n"
          },
          {
            "lang": "javascript",
            "label": "Node.js (fetch)",
            "source": "await fetch(`${base}/v1/dgii/document/130862346`, { headers })\n"
          }
        ]
      }
    },
    "/v1/dgii/document\\:search": {
      "get": {
        "operationId": "searchDocuments",
        "tags": [
          "Identity Validation"
        ],
        "summary": "Search taxpayers by name, trade name, economic activity, or document prefix",
        "description": "Prefix search across the DGII registry. Minimum query length is 3 characters.\nResults are normalised (uppercase, no accents). The `partial` flag indicates\nmore results exist beyond the limit.\n",
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "required": true,
            "description": "Search text (minimum 3 characters).",
            "schema": {
              "type": "string",
              "minLength": 3
            }
          },
          {
            "name": "field",
            "in": "query",
            "required": true,
            "description": "Field to search.",
            "schema": {
              "type": "string",
              "enum": [
                "name",
                "trade_name",
                "economic_activity",
                "document",
                "any"
              ]
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Maximum results (1–100, default 25).",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 25
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Search results.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SearchResult"
                }
              }
            }
          },
          "400": {
            "description": "Invalid parameters (query too short or invalid field).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "shell",
            "label": "cURL",
            "source": "curl -s \"https://api.erply.pro/v1/dgii/document:search?q=ERPLY&field=name&limit=5\" -H \"x-api-key: $KEY\"\n"
          },
          {
            "lang": "python",
            "label": "Python (requests)",
            "source": "requests.get(f\"{base}/v1/dgii/document:search\", params={\"q\": \"ERPLY\", \"field\": \"name\", \"limit\": 5}, headers=headers, timeout=10)\n"
          },
          {
            "lang": "javascript",
            "label": "Node.js (fetch)",
            "source": "await fetch(`${base}/v1/dgii/document:search?q=ERPLY&field=name&limit=5`, { headers })\n"
          }
        ]
      }
    },
    "/v1/dgii/registry/status": {
      "get": {
        "operationId": "getRegistryStatus",
        "tags": [
          "Identity Validation"
        ],
        "summary": "Get DGII registry freshness and ingest metadata",
        "description": "Returns the timestamp and record count of the latest successful daily ingest.\nUseful for validating data freshness before bulk operations.\nIf `stale` is true, the registry has not been updated in over 36 hours.\n",
        "responses": {
          "200": {
            "description": "Registry status.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RegistryStatus"
                }
              }
            }
          }
        },
        "x-codeSamples": [
          {
            "lang": "shell",
            "label": "cURL",
            "source": "curl -s https://api.erply.pro/v1/dgii/registry/status -H \"x-api-key: $KEY\"\n"
          },
          {
            "lang": "python",
            "label": "Python (requests)",
            "source": "requests.get(f\"{base}/v1/dgii/registry/status\", headers=headers, timeout=10)\n"
          },
          {
            "lang": "javascript",
            "label": "Node.js (fetch)",
            "source": "await fetch(`${base}/v1/dgii/registry/status`, { headers })\n"
          }
        ]
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "JWT",
        "description": "Cognito-issued access token (custom Lambda authoriser; 60 s API GW result cache)."
      },
      "apiKeyAuth": {
        "type": "apiKey",
        "in": "header",
        "name": "x-api-key",
        "description": "Per-tenant API key. Production callers MUST also send a signed\n`X-ErplyPro-Signature: t=<unix>,v1=<hmac-sha256-hex>` header (±300 s skew);\nsee `x-erplypro-signature-spec` extension and `/docs/concepts#hmac`.\n",
        "x-erplypro-signature-spec": "https://api.erply.pro/docs/concepts#hmac"
      }
    },
    "parameters": {
      "TenantId": {
        "name": "id",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string",
          "pattern": "^[a-z0-9-]{3,64}$"
        },
        "description": "Canonical tenant ID.",
        "example": "tenant-130000000"
      },
      "IdCompany": {
        "name": "idCompany",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string",
          "pattern": "^[a-z0-9-]{3,64}$"
        },
        "description": "Multi-tenant routing key.",
        "example": "tenant-130000000"
      },
      "InvoiceId": {
        "name": "id",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string",
          "pattern": "^[A-Za-z0-9-]{1,64}$"
        },
        "description": "Internal invoice identifier returned by `postInvoice`.",
        "example": "encf-1a2b3c4d"
      },
      "ReceivedDocumentId": {
        "name": "id",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string",
          "pattern": "^[A-Za-z0-9-]{1,64}$"
        }
      },
      "SetTestId": {
        "name": "id",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string",
          "pattern": "^[A-Za-z0-9-]{1,64}$"
        }
      },
      "IdempotencyKey": {
        "name": "Idempotency-Key",
        "in": "header",
        "required": false,
        "schema": {
          "type": "string",
          "format": "uuid"
        },
        "description": "Client-generated UUID. The same key replays the original response for\n24 hours; mismatched payloads return `409 Conflict`.\n"
      }
    },
    "responses": {
      "BadRequest": {
        "description": "Validation failed.",
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            }
          }
        }
      },
      "Unauthorized": {
        "description": "Missing or invalid credentials.",
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            }
          }
        }
      },
      "NotFound": {
        "description": "Resource not found.",
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            }
          }
        }
      },
      "Conflict": {
        "description": "Conflict with existing resource or idempotency mismatch.",
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            }
          }
        }
      },
      "UnprocessableEntity": {
        "description": "Business-rule violation (e.g., DGII rejected, math discrepancy).",
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            }
          }
        }
      },
      "GatewayTimeout": {
        "description": "Upstream DGII unavailable.",
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            }
          }
        }
      },
      "InternalServerError": {
        "description": "Unhandled server error.",
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            }
          }
        }
      }
    },
    "examples": {
      "CreateCompanyDemo": {
        "summary": "Demo company registration",
        "value": {
          "rnc": "130000000",
          "name": "Demo Compañía S.R.L.",
          "webhookUrl": "https://demo.example/erplypro/webhook"
        }
      },
      "InvoiceType31": {
        "summary": "Type 31 — Fiscal credit (canonical fixture)",
        "value": {
          "type": "31",
          "encf": "E310000000001",
          "client_request_id": "req-2026-05-01-001",
          "company": {
            "rnc": "131000001",
            "name": "ERPly Pro Demo S.R.L."
          },
          "customer": {
            "rnc": "131000002",
            "name": "Cliente Demo S.R.L."
          },
          "lines": [
            {
              "line_number": 1,
              "description": "Servicio de consultoria",
              "quantity": "1",
              "unit_price": "5000.00",
              "subtotal": "5000.00"
            }
          ],
          "totals": {
            "subtotal": "5000.00",
            "tax": "900.00",
            "grand_total": "5900.00"
          }
        }
      },
      "InvoiceType32": {
        "summary": "Type 32 — Consumption (issuer + recipient still required)",
        "value": {
          "type": "32",
          "encf": "E320000000001",
          "client_request_id": "req-2026-05-01-002",
          "company": {
            "rnc": "131000001",
            "name": "ERPly Pro Demo S.R.L."
          },
          "customer": {
            "rnc": "131000003",
            "name": "Cliente Final S.R.L."
          },
          "lines": [
            {
              "line_number": 1,
              "description": "Producto de consumo",
              "quantity": "2",
              "unit_price": "250.00",
              "subtotal": "500.00"
            }
          ],
          "totals": {
            "subtotal": "500.00",
            "tax": "90.00",
            "grand_total": "590.00"
          }
        }
      }
    },
    "schemas": {
      "Problem": {
        "type": "object",
        "description": "RFC 9457 problem details. Returned for every 4xx and 5xx response.",
        "additionalProperties": true,
        "required": [
          "type",
          "title",
          "status"
        ],
        "properties": {
          "type": {
            "type": "string",
            "format": "uri",
            "example": "https://errors.api.erply.pro/validation-error"
          },
          "title": {
            "type": "string",
            "example": "Validation failed"
          },
          "status": {
            "type": "integer",
            "minimum": 400,
            "maximum": 599,
            "example": 400
          },
          "detail": {
            "type": "string",
            "example": "lines[0].qty must be a positive number"
          },
          "instance": {
            "type": "string",
            "format": "uri",
            "example": "/v1/invoices"
          },
          "traceId": {
            "type": "string",
            "example": "1-65fa7c3a-6f9c2d8e0a1b2c3d4e5f6789"
          },
          "errors": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "field": {
                  "type": "string"
                },
                "message": {
                  "type": "string"
                }
              }
            }
          }
        }
      },
      "Company": {
        "type": "object",
        "required": [
          "tenantId",
          "rnc",
          "name",
          "status"
        ],
        "properties": {
          "tenantId": {
            "type": "string",
            "example": "tenant-130000000"
          },
          "rnc": {
            "type": "string",
            "example": "130000000"
          },
          "name": {
            "type": "string",
            "example": "Demo Compañía S.R.L."
          },
          "webhookUrl": {
            "type": "string",
            "format": "uri",
            "nullable": true
          },
          "status": {
            "type": "string",
            "enum": [
              "active",
              "suspended",
              "pending"
            ]
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "CreateCompanyRequest": {
        "type": "object",
        "required": [
          "rnc",
          "name"
        ],
        "properties": {
          "rnc": {
            "type": "string",
            "pattern": "^[0-9]{9,11}$"
          },
          "name": {
            "type": "string",
            "minLength": 3,
            "maxLength": 200
          },
          "webhookUrl": {
            "type": "string",
            "format": "uri"
          }
        }
      },
      "PatchCompanyRequest": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "minLength": 3,
            "maxLength": 200
          },
          "webhookUrl": {
            "type": "string",
            "format": "uri"
          },
          "logoBase64": {
            "type": "string",
            "format": "byte",
            "description": "Optional base64-encoded PNG/JPEG logo, ≤ 200 KB."
          }
        }
      },
      "CreateCheckSetTestsRequest": {
        "type": "object",
        "required": [
          "tenantId"
        ],
        "properties": {
          "tenantId": {
            "type": "string",
            "example": "tenant-130000000"
          }
        }
      },
      "CheckSetTests": {
        "type": "object",
        "required": [
          "id",
          "tenantId",
          "status"
        ],
        "properties": {
          "id": {
            "type": "string",
            "example": "settest-1234"
          },
          "tenantId": {
            "type": "string",
            "example": "tenant-130000000"
          },
          "status": {
            "type": "string",
            "enum": [
              "submitted",
              "processing",
              "accepted",
              "rejected"
            ]
          },
          "submittedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "ProviderInfo": {
        "type": "object",
        "required": [
          "providerName",
          "providerRnc",
          "certifiedAt"
        ],
        "properties": {
          "providerName": {
            "type": "string",
            "example": "ERPly Pro S.R.L."
          },
          "providerRnc": {
            "type": "string",
            "example": "130000000"
          },
          "certifiedAt": {
            "type": "string",
            "format": "date"
          },
          "certificationNumber": {
            "type": "string",
            "example": "PSFE-2026-001"
          }
        }
      },
      "InvoiceLine": {
        "type": "object",
        "description": "Line item. Decimal fields are accepted as JSON strings to avoid\nclient-side rounding error; the server validates that\n``quantity * unit_price == subtotal ± 0.01`` (DGII rule).\n",
        "required": [
          "line_number",
          "description",
          "quantity",
          "unit_price",
          "subtotal"
        ],
        "properties": {
          "line_number": {
            "type": "integer",
            "minimum": 1,
            "maximum": 999
          },
          "description": {
            "type": "string",
            "minLength": 1,
            "maxLength": 80
          },
          "quantity": {
            "type": "string",
            "pattern": "^[0-9]+(\\.[0-9]+)?$",
            "example": "1"
          },
          "unit_price": {
            "type": "string",
            "pattern": "^[0-9]+(\\.[0-9]{1,2})?$",
            "example": "5000.00"
          },
          "subtotal": {
            "type": "string",
            "pattern": "^[0-9]+(\\.[0-9]{1,2})?$",
            "example": "5000.00"
          }
        }
      },
      "InvoiceParty": {
        "type": "object",
        "description": "Issuer (`company`) or recipient (`customer`) identification.",
        "required": [
          "rnc",
          "name"
        ],
        "properties": {
          "rnc": {
            "type": "string",
            "pattern": "^[0-9]{9}([0-9]{2})?$"
          },
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 150
          }
        }
      },
      "InvoiceTotals": {
        "type": "object",
        "required": [
          "subtotal",
          "tax",
          "grand_total"
        ],
        "properties": {
          "subtotal": {
            "type": "string",
            "pattern": "^[0-9]+(\\.[0-9]{1,2})?$"
          },
          "tax": {
            "type": "string",
            "pattern": "^[0-9]+(\\.[0-9]{1,2})?$"
          },
          "grand_total": {
            "type": "string",
            "pattern": "^[0-9]+(\\.[0-9]{1,2})?$"
          }
        }
      },
      "InvoiceRequest": {
        "type": "object",
        "description": "Inbound invoice payload for `POST /v1/invoices`. Mirrors the\nPydantic `InvoicePayload` model in `lambdas.shared.models.invoice`.\nTenant identity is **NOT** in the body — it is derived from the\nJWT claims (`tenant_id`, `environment`) by `TenantContext.from_event`.\nSee BUG-EP-03 (bugs.md) for the previous OpenAPI/Pydantic drift.\n",
        "required": [
          "type",
          "encf",
          "client_request_id",
          "company",
          "customer",
          "lines",
          "totals"
        ],
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "31",
              "32",
              "33",
              "34",
              "41",
              "44"
            ]
          },
          "encf": {
            "type": "string",
            "pattern": "^E[0-9]{12}$",
            "example": "E310000000001"
          },
          "client_request_id": {
            "type": "string",
            "minLength": 8,
            "maxLength": 64,
            "description": "Body-level idempotency key (per-tenant). The HTTP `Idempotency-Key` header covers the network retry layer."
          },
          "company": {
            "$ref": "#/components/schemas/InvoiceParty"
          },
          "customer": {
            "$ref": "#/components/schemas/InvoiceParty"
          },
          "parent_encf": {
            "type": "string",
            "nullable": true,
            "description": "Required for credit/debit notes (types 33, 34)."
          },
          "lines": {
            "type": "array",
            "minItems": 1,
            "maxItems": 1000,
            "items": {
              "$ref": "#/components/schemas/InvoiceLine"
            }
          },
          "totals": {
            "$ref": "#/components/schemas/InvoiceTotals"
          }
        }
      },
      "InvoiceAccepted": {
        "type": "object",
        "required": [
          "docId",
          "status"
        ],
        "properties": {
          "docId": {
            "type": "string",
            "example": "encf-1a2b3c4d"
          },
          "trackId": {
            "type": "string",
            "nullable": true
          },
          "status": {
            "type": "string",
            "enum": [
              "accepted",
              "processing",
              "replay"
            ]
          },
          "encf": {
            "type": "string",
            "example": "E310000000001"
          },
          "receivedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "InvoiceStatus": {
        "type": "object",
        "required": [
          "docId",
          "status"
        ],
        "properties": {
          "docId": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "processing",
              "accepted",
              "rejected",
              "stuck"
            ]
          },
          "dgiiTrackId": {
            "type": "string",
            "nullable": true
          },
          "dgiiResponseAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "rejectionReason": {
            "type": "string",
            "nullable": true
          }
        }
      },
      "NotifyByEmailRequest": {
        "type": "object",
        "required": [
          "docId",
          "recipientEmail"
        ],
        "properties": {
          "docId": {
            "type": "string"
          },
          "recipientEmail": {
            "type": "string",
            "format": "email"
          },
          "cc": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "email"
            }
          }
        }
      },
      "NotificationAccepted": {
        "type": "object",
        "required": [
          "notificationId"
        ],
        "properties": {
          "notificationId": {
            "type": "string"
          },
          "queuedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "ReceivedDocument": {
        "type": "object",
        "required": [
          "id",
          "issuerRnc",
          "encf",
          "receivedAt"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "issuerRnc": {
            "type": "string",
            "pattern": "^[0-9]{9,11}$"
          },
          "encf": {
            "type": "string",
            "pattern": "^E[0-9]{12}$"
          },
          "ecfType": {
            "type": "string"
          },
          "receivedAt": {
            "type": "string",
            "format": "date-time"
          },
          "xmlBase64": {
            "type": "string",
            "format": "byte",
            "nullable": true
          }
        }
      },
      "ReceivedDocumentsPage": {
        "type": "object",
        "required": [
          "items"
        ],
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ReceivedDocument"
            }
          },
          "nextCursor": {
            "type": "string",
            "nullable": true
          }
        }
      },
      "ReceiveAccepted": {
        "type": "object",
        "required": [
          "id"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "receivedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "CommercialApproval": {
        "type": "object",
        "required": [
          "docId",
          "status"
        ],
        "properties": {
          "docId": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "approved",
              "rejected"
            ]
          },
          "decidedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          }
        }
      },
      "EmittedDocumentsReport": {
        "type": "object",
        "required": [
          "tenantId",
          "totalEmitted"
        ],
        "properties": {
          "tenantId": {
            "type": "string"
          },
          "totalEmitted": {
            "type": "integer",
            "minimum": 0
          },
          "byType": {
            "type": "object",
            "additionalProperties": {
              "type": "integer",
              "minimum": 0
            }
          },
          "from": {
            "type": "string",
            "format": "date",
            "nullable": true
          },
          "to": {
            "type": "string",
            "format": "date",
            "nullable": true
          }
        }
      },
      "DgiiStatus": {
        "type": "object",
        "required": [
          "tenantId",
          "dgiiOperational"
        ],
        "properties": {
          "tenantId": {
            "type": "string"
          },
          "dgiiOperational": {
            "type": "boolean"
          },
          "message": {
            "type": "string",
            "nullable": true
          },
          "nextMaintenanceWindow": {
            "type": "object",
            "nullable": true,
            "properties": {
              "from": {
                "type": "string",
                "format": "date-time"
              },
              "to": {
                "type": "string",
                "format": "date-time"
              }
            }
          },
          "polledAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "RegistryRow": {
        "type": "object",
        "required": [
          "rnc",
          "name",
          "status"
        ],
        "properties": {
          "rnc": {
            "type": "string",
            "description": "11-digit zero-padded RNC."
          },
          "name": {
            "type": "string",
            "description": "Legal name (razón social)."
          },
          "trade_name": {
            "type": "string",
            "description": "Trade name (nombre comercial).",
            "nullable": true
          },
          "economic_activity": {
            "type": "string",
            "description": "Economic activity classification.",
            "nullable": true
          },
          "status": {
            "type": "string",
            "description": "DGII taxpayer status (ACTIVO, SUSPENDIDO, DADO DE BAJA, etc.)."
          },
          "payment_regime": {
            "type": "string",
            "description": "Payment regime (ORDINARIO, SIMPLIFICADO, etc.).",
            "nullable": true
          }
        }
      },
      "SearchResult": {
        "type": "object",
        "required": [
          "items",
          "count",
          "partial"
        ],
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/RegistryRow"
            }
          },
          "count": {
            "type": "integer",
            "minimum": 0,
            "description": "Number of results in this response."
          },
          "partial": {
            "type": "boolean",
            "description": "True if more results exist beyond the limit."
          }
        }
      },
      "RegistryStatus": {
        "type": "object",
        "required": [
          "last_ingest",
          "record_count",
          "stale"
        ],
        "properties": {
          "last_ingest": {
            "type": "string",
            "format": "date-time",
            "description": "Timestamp of the last successful ingest run."
          },
          "record_count": {
            "type": "integer",
            "minimum": 0,
            "description": "Total records in the registry."
          },
          "source_etag": {
            "type": "string",
            "description": "ETag of the DGII CSV source file.",
            "nullable": true
          },
          "source_sha256": {
            "type": "string",
            "description": "SHA-256 hash of the downloaded source file.",
            "nullable": true
          },
          "age_seconds": {
            "type": "integer",
            "minimum": 0,
            "description": "Seconds since the last successful ingest."
          },
          "stale": {
            "type": "boolean",
            "description": "True if age_seconds exceeds 129600 (36 hours)."
          }
        }
      }
    }
  }
}