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: TestCases description: Test Cases paths: /test/cases/{id}/advance: post: tags: - TestCases 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.\n\n**Purpose:**\nEnables CI pipelines and integrators to exercise the full collection lifecycle — including real webhooks and events — without manual intervention.\n\n**Target States:**\n- `Active` — Activates the case from PendingVerification or PendingVerificationInternal. No-op if already Active or Closed.\n- `Closed:Paid` — Records a payment for `amount` and closes the case as Paid. Activates the case first if needed. `amount` is required.\n- `Closed:NoPayment` — Closes the case with PreLegalExhaustedNoPayment. Activates the case first if needed.\n\n**Guards:**\n- Returns 400 if the case is not a test case (Classification != Test).\n- Returns 400 if `amount` is missing when `to` is `Closed:Paid`.\n- Returns 400 if `to` is not one of the valid values.\n- Returns 400 if the case is already closed with a different close code (e.g. already Closed:NoPayment when requesting Closed:Paid).\n- Returns 404 if the case is not found or not owned by the calling partner.\n\n**No-op behaviour:**\nIf the case is already at the requested state, the call returns 200 with `advanced: false` and the current state — no changes are made.\n\n**Webhooks:**\nTransitions use the real internal service pipeline. Webhook delivery per target:\n\n- `→ Active`: fires `case.updated` to Creditor, ReferralPartner, and **Collection Partner** subscriptions.\n- `→ Closed:Paid`: fires `payment.created` to Creditor and **Collection Partner**; fires `case.updated` and `case.closed` to Creditor, ReferralPartner, and **Collection Partner** subscriptions.\n- `→ Closed:NoPayment`: fires `case.updated` and `case.closed` to Creditor, ReferralPartner, and **Collection Partner** subscriptions.\n\n**Note:** `case.closed` is delivered to Collection Partner subscriptions on both close paths.\n\nAll events carry `classification: test` and are only delivered to webhook subscriptions registered with `classification: test`.\n\n**How to Test:**\n\n1. **Create a test case** — `POST /managed-cases` with `isTest: true` (optionally a `tag` for scoped cleanup). Note the returned `id`.\n\n2. **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).\n\n3. **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.\n\n4. **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.\n - Alternatively: advance straight from any pre-active state — the endpoint activates the case first automatically.\n\n5. **Verify final state** — `GET /cases/{id}` and assert `lifecycle: Closed` and `closeCode: Paid`.\n\n6. **Test no-op** — Call the same advance again. Expect `{ advanced: false, state: \"Closed:Paid\" }` and no new webhook events.\n\n7. **Test mismatch error** — With the case closed as Paid, call `{ \"to\": \"Closed:NoPayment\" }`. Expect 400 with a message describing the conflict.\n\n8. **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.\n\n9. **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: [] servers: - url: https://collectionpartner-api.debitura.com description: Production /test/cases/{id}: delete: tags: - TestCases 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: [] servers: - url: https://collectionpartner-api.debitura.com description: Production /test/cases: delete: tags: - TestCases 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: [] servers: - url: https://collectionpartner-api.debitura.com description: Production /test/cases/{id}/replay-last-event: post: tags: - TestCases summary: Replay the most recent webhook event for a test case. description: "Collection Partner API only — this endpoint is a convenience stopgap that existed\n before `GET /webhooks/events` was available. It remains here for backward compatibility\n and because it is a thin wrapper over `POST /webhooks/events/{eventId}/replay`.\n \n Design note: This endpoint was added when the Collection Partner API had no\n delivery-history endpoint (`GET /webhooks/events`), making it impossible to look up\n event IDs for replay. It is a stopgap: \"give me the last event without having to know its ID.\"\n Now that `GET /webhooks/events` exists, integrators can use the full\n history + replay-by-id pattern instead. This endpoint is intentionally not ported\n 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: [] 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