Source: https://docs.debitura.com/api/collection-partner-api/test-cases

# Test cases — Collection Partner API

Drive test cases through lifecycle states without manual intervention. POST /test/cases/{id}/advance activates or closes a test case in a single call, triggering the real webhook pipeline so your handlers can be verified end-to-end. Hard-delete by ID or tag to keep CI environments clean between runs.

Base URL: `https://collectionpartner-api.debitura.com`

Authentication: `XApiKey` API key; see [authentication](https://docs.debitura.com/developer-concepts/authentication.md).

Generated from [OpenAPI](https://docs.debitura.com/openapi/collection-partner-api.json). Examples are schema illustrations, not observed responses. Complete schemas, including recursive types, are in OpenAPI.

<a id="advance-a-test-case-to-a-known-lifecycle-state"></a>

## Advance a test case to a known lifecycle state.

`POST /test/cases/{id}/advance`

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.

### Path parameters

- `id` (uuid, required): 

### Request body (application/json)

- `to` (string, required, nullable): The target state to advance the case to.
Valid values: "Active", "Closed:Paid", "Closed:NoPayment"
- `amount` (number, nullable): Payment amount (required when To is "Closed:Paid").
Must be a positive value in the case currency.

```json
{
  "to": "Active",
  "amount": 1500
}
```

### Response 200

Advance result (advanced=true if state changed, advanced=false if already there)

- `advanced` (boolean): True if the case was advanced in this call; false if it was already at or past the target state (no-op).
- `state` (string, nullable): The current state of the case after this call.
One of: "Active", "Closed:Paid", "Closed:NoPayment", "Closed" (for other close codes).

```json
{
  "advanced": true,
  "state": "string"
}
```

### Response 400

Validation error — non-test case, missing amount, or unknown target state

- `error` (string, nullable): Short human-readable error label (legacy field — preserved for backward compatibility).
- `message` (string, nullable): Human-readable error description (legacy field — preserved for backward compatibility).
- `businessErrors` (array of objects, nullable): Canonical structured error array. Consumers should migrate to reading this field.
- `businessErrors[].type` (string, nullable): The type of business error (e.g., "MissingDebtCollectionContract", "MissingPowerOfAttorney", "NoPartnerAvailable").
- `businessErrors[].message` (string, nullable): A human-readable description of the error.
- `businessErrors[].solutionUrl` (string, nullable): A URL where the user can resolve this <i>specific</i> error (e.g., sign a contract).
            

Partners who want a <b>single URL</b> that walks the user through every pending
signing in one chain — instead of forwarding one URL per
`BusinessErrorApiDTO` — should use
SigningHandoff's `CombinedSigningUrl`.
The two are complementary: `SolutionUrl` is granular per-error;
`CombinedSigningUrl` is the chain-walking alternative for signing-related errors.

```json
{
  "error": null,
  "message": "Payment plan agreed with the debtor.",
  "businessErrors": [
    {
      "type": "string",
      "message": "Payment plan agreed with the debtor.",
      "solutionUrl": "https://app.debitura.com/…"
    }
  ]
}
```

### Response 404

Case not found or not owned by this collection partner

- `error` (string, nullable): Short human-readable error label (legacy field — preserved for backward compatibility).
- `message` (string, nullable): Human-readable error description (legacy field — preserved for backward compatibility).
- `businessErrors` (array of objects, nullable): Canonical structured error array. Consumers should migrate to reading this field.
- `businessErrors[].type` (string, nullable): The type of business error (e.g., "MissingDebtCollectionContract", "MissingPowerOfAttorney", "NoPartnerAvailable").
- `businessErrors[].message` (string, nullable): A human-readable description of the error.
- `businessErrors[].solutionUrl` (string, nullable): A URL where the user can resolve this <i>specific</i> error (e.g., sign a contract).
            

Partners who want a <b>single URL</b> that walks the user through every pending
signing in one chain — instead of forwarding one URL per
`BusinessErrorApiDTO` — should use
SigningHandoff's `CombinedSigningUrl`.
The two are complementary: `SolutionUrl` is granular per-error;
`CombinedSigningUrl` is the chain-walking alternative for signing-related errors.

```json
{
  "error": null,
  "message": "Payment plan agreed with the debtor.",
  "businessErrors": [
    {
      "type": "string",
      "message": "Payment plan agreed with the debtor.",
      "solutionUrl": "https://app.debitura.com/…"
    }
  ]
}
```

### Response 500

Internal server error

- `error` (string, nullable): Short human-readable error label (legacy field — preserved for backward compatibility).
- `message` (string, nullable): Human-readable error description (legacy field — preserved for backward compatibility).
- `businessErrors` (array of objects, nullable): Canonical structured error array. Consumers should migrate to reading this field.
- `businessErrors[].type` (string, nullable): The type of business error (e.g., "MissingDebtCollectionContract", "MissingPowerOfAttorney", "NoPartnerAvailable").
- `businessErrors[].message` (string, nullable): A human-readable description of the error.
- `businessErrors[].solutionUrl` (string, nullable): A URL where the user can resolve this <i>specific</i> error (e.g., sign a contract).
            

Partners who want a <b>single URL</b> that walks the user through every pending
signing in one chain — instead of forwarding one URL per
`BusinessErrorApiDTO` — should use
SigningHandoff's `CombinedSigningUrl`.
The two are complementary: `SolutionUrl` is granular per-error;
`CombinedSigningUrl` is the chain-walking alternative for signing-related errors.

```json
{
  "error": null,
  "message": "Payment plan agreed with the debtor.",
  "businessErrors": [
    {
      "type": "string",
      "message": "Payment plan agreed with the debtor.",
      "solutionUrl": "https://app.debitura.com/…"
    }
  ]
}
```

<a id="hard-delete-a-single-test-case-by-id"></a>

## Hard-delete a single test case by ID.

`DELETE /test/cases/{id}`

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).

### Path parameters

- `id` (uuid, required): 

### Response 204

Case deleted (or already absent — idempotent)

### Response 400

Case is a Production case — deletion rejected

- `error` (string, nullable): Short human-readable error label (legacy field — preserved for backward compatibility).
- `message` (string, nullable): Human-readable error description (legacy field — preserved for backward compatibility).
- `businessErrors` (array of objects, nullable): Canonical structured error array. Consumers should migrate to reading this field.
- `businessErrors[].type` (string, nullable): The type of business error (e.g., "MissingDebtCollectionContract", "MissingPowerOfAttorney", "NoPartnerAvailable").
- `businessErrors[].message` (string, nullable): A human-readable description of the error.
- `businessErrors[].solutionUrl` (string, nullable): A URL where the user can resolve this <i>specific</i> error (e.g., sign a contract).
            

Partners who want a <b>single URL</b> that walks the user through every pending
signing in one chain — instead of forwarding one URL per
`BusinessErrorApiDTO` — should use
SigningHandoff's `CombinedSigningUrl`.
The two are complementary: `SolutionUrl` is granular per-error;
`CombinedSigningUrl` is the chain-walking alternative for signing-related errors.

```json
{
  "error": null,
  "message": "Payment plan agreed with the debtor.",
  "businessErrors": [
    {
      "type": "string",
      "message": "Payment plan agreed with the debtor.",
      "solutionUrl": "https://app.debitura.com/…"
    }
  ]
}
```

### Response 404

Case not found or not owned by this collection partner

- `error` (string, nullable): Short human-readable error label (legacy field — preserved for backward compatibility).
- `message` (string, nullable): Human-readable error description (legacy field — preserved for backward compatibility).
- `businessErrors` (array of objects, nullable): Canonical structured error array. Consumers should migrate to reading this field.
- `businessErrors[].type` (string, nullable): The type of business error (e.g., "MissingDebtCollectionContract", "MissingPowerOfAttorney", "NoPartnerAvailable").
- `businessErrors[].message` (string, nullable): A human-readable description of the error.
- `businessErrors[].solutionUrl` (string, nullable): A URL where the user can resolve this <i>specific</i> error (e.g., sign a contract).
            

Partners who want a <b>single URL</b> that walks the user through every pending
signing in one chain — instead of forwarding one URL per
`BusinessErrorApiDTO` — should use
SigningHandoff's `CombinedSigningUrl`.
The two are complementary: `SolutionUrl` is granular per-error;
`CombinedSigningUrl` is the chain-walking alternative for signing-related errors.

```json
{
  "error": null,
  "message": "Payment plan agreed with the debtor.",
  "businessErrors": [
    {
      "type": "string",
      "message": "Payment plan agreed with the debtor.",
      "solutionUrl": "https://app.debitura.com/…"
    }
  ]
}
```

### Response 500

Internal server error

- `error` (string, nullable): Short human-readable error label (legacy field — preserved for backward compatibility).
- `message` (string, nullable): Human-readable error description (legacy field — preserved for backward compatibility).
- `businessErrors` (array of objects, nullable): Canonical structured error array. Consumers should migrate to reading this field.
- `businessErrors[].type` (string, nullable): The type of business error (e.g., "MissingDebtCollectionContract", "MissingPowerOfAttorney", "NoPartnerAvailable").
- `businessErrors[].message` (string, nullable): A human-readable description of the error.
- `businessErrors[].solutionUrl` (string, nullable): A URL where the user can resolve this <i>specific</i> error (e.g., sign a contract).
            

Partners who want a <b>single URL</b> that walks the user through every pending
signing in one chain — instead of forwarding one URL per
`BusinessErrorApiDTO` — should use
SigningHandoff's `CombinedSigningUrl`.
The two are complementary: `SolutionUrl` is granular per-error;
`CombinedSigningUrl` is the chain-walking alternative for signing-related errors.

```json
{
  "error": null,
  "message": "Payment plan agreed with the debtor.",
  "businessErrors": [
    {
      "type": "string",
      "message": "Payment plan agreed with the debtor.",
      "solutionUrl": "https://app.debitura.com/…"
    }
  ]
}
```

<a id="hard-delete-all-test-cases-with-the-given-tag"></a>

## Hard-delete all test cases with the given tag.

`DELETE /test/cases`

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.

### Query parameters

- `tag` (string): 

### Response 204

Cases deleted (or none matched — idempotent, no response body)

### Response 400

Tag is missing/blank, or matched cases include a Production case

- `error` (string, nullable): Short human-readable error label (legacy field — preserved for backward compatibility).
- `message` (string, nullable): Human-readable error description (legacy field — preserved for backward compatibility).
- `businessErrors` (array of objects, nullable): Canonical structured error array. Consumers should migrate to reading this field.
- `businessErrors[].type` (string, nullable): The type of business error (e.g., "MissingDebtCollectionContract", "MissingPowerOfAttorney", "NoPartnerAvailable").
- `businessErrors[].message` (string, nullable): A human-readable description of the error.
- `businessErrors[].solutionUrl` (string, nullable): A URL where the user can resolve this <i>specific</i> error (e.g., sign a contract).
            

Partners who want a <b>single URL</b> that walks the user through every pending
signing in one chain — instead of forwarding one URL per
`BusinessErrorApiDTO` — should use
SigningHandoff's `CombinedSigningUrl`.
The two are complementary: `SolutionUrl` is granular per-error;
`CombinedSigningUrl` is the chain-walking alternative for signing-related errors.

```json
{
  "error": null,
  "message": "Payment plan agreed with the debtor.",
  "businessErrors": [
    {
      "type": "string",
      "message": "Payment plan agreed with the debtor.",
      "solutionUrl": "https://app.debitura.com/…"
    }
  ]
}
```

### Response 500

Internal server error

- `error` (string, nullable): Short human-readable error label (legacy field — preserved for backward compatibility).
- `message` (string, nullable): Human-readable error description (legacy field — preserved for backward compatibility).
- `businessErrors` (array of objects, nullable): Canonical structured error array. Consumers should migrate to reading this field.
- `businessErrors[].type` (string, nullable): The type of business error (e.g., "MissingDebtCollectionContract", "MissingPowerOfAttorney", "NoPartnerAvailable").
- `businessErrors[].message` (string, nullable): A human-readable description of the error.
- `businessErrors[].solutionUrl` (string, nullable): A URL where the user can resolve this <i>specific</i> error (e.g., sign a contract).
            

Partners who want a <b>single URL</b> that walks the user through every pending
signing in one chain — instead of forwarding one URL per
`BusinessErrorApiDTO` — should use
SigningHandoff's `CombinedSigningUrl`.
The two are complementary: `SolutionUrl` is granular per-error;
`CombinedSigningUrl` is the chain-walking alternative for signing-related errors.

```json
{
  "error": null,
  "message": "Payment plan agreed with the debtor.",
  "businessErrors": [
    {
      "type": "string",
      "message": "Payment plan agreed with the debtor.",
      "solutionUrl": "https://app.debitura.com/…"
    }
  ]
}
```

<a id="replay-the-most-recent-webhook-event-for-a-test-case"></a>

## Replay the most recent webhook event for a test case.

`POST /test/cases/{id}/replay-last-event`

<b>Collection Partner API only</b> — 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`.
            
             <b>Design note:</b> 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 <b>not</b> ported
             to the Customer API — the stopgap only exists because it was needed before history existed.

### Path parameters

- `id` (uuid, required): 

### Response 200

Event re-enqueued for delivery

- `eventId` (uuid): The ID of the webhook event that was re-enqueued.
- `subscriptionId` (uuid): The subscription the event was re-enqueued to.
- `eventType` (string, nullable): The event type that was replayed (e.g. "case.updated").

```json
{
  "eventId": "3f2b6c1e-8a4d-4b7f-9c2e-5d1a0b9e7f31",
  "subscriptionId": "3f2b6c1e-8a4d-4b7f-9c2e-5d1a0b9e7f31",
  "eventType": "string"
}
```

### Response 400

Validation error — non-test case or not owned by this partner

### Response 404

Case not found, or no webhook events recorded yet for this case
