openapi: 3.2.0 info: title: Debitura Test Cases API version: v1 description: 'Operations tagged TestCases across 2 of this provider''s published API definitions: debitura-collection-partner-api.json, debitura-customer-api.json. Each path carries the servers of the definition it was published in.' servers: - url: https://collectionpartner-api.debitura.com description: Production - url: https://customer-api.debitura.com description: Production tags: - name: Test Cases description: Test Cases paths: /test/cases/{id}/advance: post: tags: - Test Cases summary: Advance a test case to a known lifecycle state description: 'Drives a test case to a well-known lifecycle state in a single call. **Purpose:** Enables CI pipelines and integrators to exercise the full collection lifecycle — including real webhooks and events — without manual intervention. **Target States:** - `Active` — Activates the case from PendingVerification or PendingVerificationInternal. No-op if already Active or Closed. - `Closed:Paid` — Records a payment for `amount` and closes the case as Paid. Activates the case first if needed. `amount` is required. - `Closed:NoPayment` — Closes the case with PreLegalExhaustedNoPayment. Activates the case first if needed. **Guards:** - Returns 400 if the case is not a test case (Classification != Test). - Returns 400 if `amount` is missing when `to` is `Closed:Paid`. - Returns 400 if `to` is not one of the valid values. - Returns 400 if the case is already closed with a different close code (e.g. already Closed:NoPayment when requesting Closed:Paid). - Returns 404 if the case is not found or not owned by the calling partner. **No-op behaviour:** If the case is already at the requested state, the call returns 200 with `advanced: false` and the current state — no changes are made. **Webhooks:** Transitions use the real internal service pipeline. Webhook delivery per target: - `→ Active`: fires `case.updated` to Creditor, ReferralPartner, and **Collection Partner** subscriptions. - `→ Closed:Paid`: fires `payment.created` to Creditor and **Collection Partner**; fires `case.updated` and `case.closed` to Creditor, ReferralPartner, and **Collection Partner** subscriptions. - `→ Closed:NoPayment`: fires `case.updated` and `case.closed` to Creditor, ReferralPartner, and **Collection Partner** subscriptions. **Note:** `case.closed` is delivered to Collection Partner subscriptions on both close paths. All events carry `classification: test` and are only delivered to webhook subscriptions registered with `classification: test`. **How to Test:** 1. **Create a test case** — `POST /managed-cases` with `isTest: true` (optionally a `tag` for scoped cleanup). Note the returned `id`. 2. **Register a test webhook subscription** — subscribe to the events you want to assert on (`case.updated`, `payment.created`, `case.closed`) with `classification: test` and point the URL to your test receiver (e.g. a local ngrok tunnel or a CI webhook sink). 3. **Advance to Active** — `POST /test/cases/{id}/advance` with `{ "to": "Active" }`. Expect `{ advanced: true, state: "Active" }` and a `case.updated` event on your Collection Partner subscription. 4. **Advance to Closed:Paid** — `POST /test/cases/{id}/advance` with `{ "to": "Closed:Paid", "amount": 1000.00 }`. Expect `{ advanced: true, state: "Closed:Paid" }` and a `payment.created` event on your Collection Partner subscription. - Alternatively: advance straight from any pre-active state — the endpoint activates the case first automatically. 5. **Verify final state** — `GET /cases/{id}` and assert `lifecycle: Closed` and `closeCode: Paid`. 6. **Test no-op** — Call the same advance again. Expect `{ advanced: false, state: "Closed:Paid" }` and no new webhook events. 7. **Test mismatch error** — With the case closed as Paid, call `{ "to": "Closed:NoPayment" }`. Expect 400 with a message describing the conflict. 8. **Test Closed:NoPayment path** — Create a fresh test case, call `{ "to": "Closed:NoPayment" }` directly (no amount needed). Expect `case.updated` and `case.closed` on your Collection Partner subscription. 9. **Reset between runs** — Delete the test case via `DELETE /test/cases/{id}` (or `DELETE /test/cases?tag={tag}` to clear a whole tag) or create a new one per test run to keep assertions clean.' parameters: - name: id in: path required: true schema: type: string format: uuid requestBody: content: application/json-patch+json: schema: $ref: '#/components/schemas/Debitura.Web.ExternalApi.Contracts.V1.Cases.Requests.AdvanceTestCaseRequest' application/json: schema: $ref: '#/components/schemas/Debitura.Web.ExternalApi.Contracts.V1.Cases.Requests.AdvanceTestCaseRequest' text/json: schema: $ref: '#/components/schemas/Debitura.Web.ExternalApi.Contracts.V1.Cases.Requests.AdvanceTestCaseRequest' application/*+json: schema: $ref: '#/components/schemas/Debitura.Web.ExternalApi.Contracts.V1.Cases.Requests.AdvanceTestCaseRequest' responses: '200': description: Advance result (advanced=true if state changed, advanced=false if already there) content: text/plain: schema: $ref: '#/components/schemas/Debitura.Web.ExternalApi.Contracts.V1.Cases.AdvanceTestCaseResponse' application/json: schema: $ref: '#/components/schemas/Debitura.Web.ExternalApi.Contracts.V1.Cases.AdvanceTestCaseResponse' text/json: schema: $ref: '#/components/schemas/Debitura.Web.ExternalApi.Contracts.V1.Cases.AdvanceTestCaseResponse' '400': description: Validation error — non-test case, missing amount, or unknown target state content: text/plain: schema: $ref: '#/components/schemas/Debitura.Web.ExternalApi.Contracts.V1.Errors.ApiErrorResponseDto' application/json: schema: $ref: '#/components/schemas/Debitura.Web.ExternalApi.Contracts.V1.Errors.ApiErrorResponseDto' text/json: schema: $ref: '#/components/schemas/Debitura.Web.ExternalApi.Contracts.V1.Errors.ApiErrorResponseDto' '404': description: Case not found or not owned by this collection partner content: text/plain: schema: $ref: '#/components/schemas/Debitura.Web.ExternalApi.Contracts.V1.Errors.ApiErrorResponseDto' application/json: schema: $ref: '#/components/schemas/Debitura.Web.ExternalApi.Contracts.V1.Errors.ApiErrorResponseDto' text/json: schema: $ref: '#/components/schemas/Debitura.Web.ExternalApi.Contracts.V1.Errors.ApiErrorResponseDto' '500': description: Internal server error content: text/plain: schema: $ref: '#/components/schemas/Debitura.Web.ExternalApi.Contracts.V1.Errors.ApiErrorResponseDto' application/json: schema: $ref: '#/components/schemas/Debitura.Web.ExternalApi.Contracts.V1.Errors.ApiErrorResponseDto' text/json: schema: $ref: '#/components/schemas/Debitura.Web.ExternalApi.Contracts.V1.Errors.ApiErrorResponseDto' security: - ApiKey: [] operationId: postTestCasesByIdAdvance x-operation-id-source: derived servers: - url: https://collectionpartner-api.debitura.com description: Production /test/cases/{id}: delete: tags: - Test Cases summary: Hard-delete a single test case by ID description: 'Permanently removes a test case and all its child entities (payments, chats, files, timeline entries, webhook delivery records) from the database. **This is a hard-delete.** The row is removed entirely — there is no soft-delete or recovery path. This is intentional for CI environments that create many test cases per day. **Guards:** - Returns 400 if the case is classified as Production. Only Test cases may be deleted via this endpoint. - Returns 404 if the case is not found or not owned by the calling partner. **Idempotent:** Returns 204 even if the case is already deleted (no-op).' parameters: - name: id in: path required: true schema: type: string format: uuid responses: '204': description: Case deleted (or already absent — idempotent) '400': description: Case is a Production case — deletion rejected content: text/plain: schema: $ref: '#/components/schemas/Debitura.Web.ExternalApi.Contracts.V1.Errors.ApiErrorResponseDto' application/json: schema: $ref: '#/components/schemas/Debitura.Web.ExternalApi.Contracts.V1.Errors.ApiErrorResponseDto' text/json: schema: $ref: '#/components/schemas/Debitura.Web.ExternalApi.Contracts.V1.Errors.ApiErrorResponseDto' '404': description: Case not found or not owned by this collection partner content: text/plain: schema: $ref: '#/components/schemas/Debitura.Web.ExternalApi.Contracts.V1.Errors.ApiErrorResponseDto' application/json: schema: $ref: '#/components/schemas/Debitura.Web.ExternalApi.Contracts.V1.Errors.ApiErrorResponseDto' text/json: schema: $ref: '#/components/schemas/Debitura.Web.ExternalApi.Contracts.V1.Errors.ApiErrorResponseDto' '500': description: Internal server error content: text/plain: schema: $ref: '#/components/schemas/Debitura.Web.ExternalApi.Contracts.V1.Errors.ApiErrorResponseDto' application/json: schema: $ref: '#/components/schemas/Debitura.Web.ExternalApi.Contracts.V1.Errors.ApiErrorResponseDto' text/json: schema: $ref: '#/components/schemas/Debitura.Web.ExternalApi.Contracts.V1.Errors.ApiErrorResponseDto' security: - ApiKey: [] operationId: deleteTestCasesById x-operation-id-source: derived servers: - url: https://collectionpartner-api.debitura.com description: Production /test/cases: delete: tags: - Test Cases summary: Hard-delete all test cases with the given tag description: 'Permanently removes all test cases created with the specified tag (and all their child entities) from the database. **Scoped cleanup:** Each CI pipeline run should create cases with a unique tag (e.g. `suite-run-{uuid}`) and use this endpoint at the end of the run to clean up, without affecting cases from parallel pipeline runs. **This is a hard-delete.** Rows are removed entirely with no recovery path. This is intentional for CI environments. **Guards:** - Returns 400 if any matched case is classified as Production. The entire operation is rejected and nothing is deleted. - Returns 400 if the tag query parameter is missing or blank. **Idempotent:** Returns 204 even if no cases match the tag — no error is raised.' parameters: - name: tag in: query schema: type: string responses: '204': description: Cases deleted (or none matched — idempotent, no response body) '400': description: Tag is missing/blank, or matched cases include a Production case content: text/plain: schema: $ref: '#/components/schemas/Debitura.Web.ExternalApi.Contracts.V1.Errors.ApiErrorResponseDto' application/json: schema: $ref: '#/components/schemas/Debitura.Web.ExternalApi.Contracts.V1.Errors.ApiErrorResponseDto' text/json: schema: $ref: '#/components/schemas/Debitura.Web.ExternalApi.Contracts.V1.Errors.ApiErrorResponseDto' '500': description: Internal server error content: text/plain: schema: $ref: '#/components/schemas/Debitura.Web.ExternalApi.Contracts.V1.Errors.ApiErrorResponseDto' application/json: schema: $ref: '#/components/schemas/Debitura.Web.ExternalApi.Contracts.V1.Errors.ApiErrorResponseDto' text/json: schema: $ref: '#/components/schemas/Debitura.Web.ExternalApi.Contracts.V1.Errors.ApiErrorResponseDto' security: - ApiKey: [] operationId: deleteTestCases x-operation-id-source: derived servers: - url: https://collectionpartner-api.debitura.com description: Production /test/cases/{id}/replay-last-event: post: tags: - Test Cases summary: Replay the most recent webhook event for a test case description: 'Collection Partner API only — this endpoint is a convenience stopgap that existed before `GET /webhooks/events` was available. It remains here for backward compatibility and because it is a thin wrapper over `POST /webhooks/events/{eventId}/replay`. Design note: This endpoint was added when the Collection Partner API had no delivery-history endpoint (`GET /webhooks/events`), making it impossible to look up event IDs for replay. It is a stopgap: "give me the last event without having to know its ID." Now that `GET /webhooks/events` exists, integrators can use the full history + replay-by-id pattern instead. This endpoint is intentionally not ported to the Customer API — the stopgap only exists because it was needed before history existed.' parameters: - name: id in: path required: true schema: type: string format: uuid responses: '200': description: Event re-enqueued for delivery content: text/plain: schema: $ref: '#/components/schemas/Debitura.Web.ExternalApi.Contracts.V1.Cases.ReplayLastEventResponse' application/json: schema: $ref: '#/components/schemas/Debitura.Web.ExternalApi.Contracts.V1.Cases.ReplayLastEventResponse' text/json: schema: $ref: '#/components/schemas/Debitura.Web.ExternalApi.Contracts.V1.Cases.ReplayLastEventResponse' '400': description: Validation error — non-test case or not owned by this partner '404': description: Case not found, or no webhook events recorded yet for this case security: - ApiKey: [] operationId: postTestCasesByIdReplayLastEvent x-operation-id-source: derived servers: - url: https://collectionpartner-api.debitura.com description: Production components: schemas: Debitura.Web.ExternalApi.Contracts.V1.Cases.BusinessErrorApiDTO: type: object properties: type: type: - string - 'null' description: The type of business error (e.g., "MissingDebtCollectionContract", "MissingPowerOfAttorney", "NoPartnerAvailable"). message: type: - string - 'null' description: A human-readable description of the error. solutionUrl: type: - string - 'null' description: "A URL where the user can resolve this specific error (e.g., sign a contract).\n \n\nPartners who want a single URL that walks the user through every pending\nsigning in one chain — instead of forwarding one URL per\n`BusinessErrorApiDTO` — should use\nDebitura.Web.ExternalApi.Contracts.V1.Cases.BusinessErrorResponseApiDTO.SigningHandoff's `CombinedSigningUrl`.\nThe two are complementary: `SolutionUrl` is granular per-error;\n`CombinedSigningUrl` is the chain-walking alternative for signing-related errors." additionalProperties: false description: Represents a business error in the API response. Debitura.Web.ExternalApi.Contracts.V1.Cases.ReplayLastEventResponse: type: object properties: eventId: type: string description: The ID of the webhook event that was re-enqueued. format: uuid subscriptionId: type: string description: The subscription the event was re-enqueued to. format: uuid eventType: type: - string - 'null' description: The event type that was replayed (e.g. "case.updated"). additionalProperties: false description: Result of the `POST /test/cases/{id}/replay-last-event` convenience endpoint. Debitura.Web.ExternalApi.Contracts.V1.Errors.ApiErrorResponseDto: type: object properties: error: type: - string - 'null' description: Short human-readable error label (legacy field — preserved for backward compatibility). message: type: - string - 'null' description: Human-readable error description (legacy field — preserved for backward compatibility). businessErrors: type: - array - 'null' items: $ref: '#/components/schemas/Debitura.Web.ExternalApi.Contracts.V1.Cases.BusinessErrorApiDTO' description: Canonical structured error array. Consumers should migrate to reading this field. additionalProperties: false description: "Generic structured error response for API endpoints.\n \nDual-write design: existing fields (Debitura.Web.ExternalApi.Contracts.V1.Errors.ApiErrorResponseDto.Error / Debitura.Web.ExternalApi.Contracts.V1.Errors.ApiErrorResponseDto.Message etc.)\nare preserved for backward compatibility alongside the canonical\nDebitura.Web.ExternalApi.Contracts.V1.Errors.ApiErrorResponseDto.BusinessErrors array, allowing consumers to migrate at their own pace." Debitura.Web.ExternalApi.Contracts.V1.Cases.Requests.AdvanceTestCaseRequest: required: - to type: object properties: to: type: - string - 'null' description: 'The target state to advance the case to. Valid values: "Active", "Closed:Paid", "Closed:NoPayment"' example: Active amount: type: - number - 'null' description: 'Payment amount (required when To is "Closed:Paid"). Must be a positive value in the case currency.' format: double example: 1500.0 additionalProperties: false description: Request to advance a test case to a known lifecycle state. Debitura.Web.ExternalApi.Contracts.V1.Cases.AdvanceTestCaseResponse: type: object properties: advanced: type: boolean description: True if the case was advanced in this call; false if it was already at or past the target state (no-op). state: type: - string - 'null' description: 'The current state of the case after this call. One of: "Active", "Closed:Paid", "Closed:NoPayment", "Closed" (for other close codes).' additionalProperties: false description: Response from POST /test/cases/{id}/advance. securitySchemes: ApiKey: type: apiKey description: Collection Partner API Key (use XApiKey header) name: XApiKey in: header Bearer: type: http description: This is for referral partners, who are sending in clients to the platform. They get the bearer token from the referral partner Api and can be inserted here. scheme: bearer bearerFormat: JWT externalDocs: description: Debitura developer documentation url: https://docs.debitura.com x-refined-from: - debitura-collection-partner-api.json - debitura-customer-api.json