Error Reference
Every error a referral partner integration can encounter, organized by HTTP status code.
For the standard error response format and retry strategy, see Error Handling. For symptom-based debugging, see Troubleshooting.
400 Bad Request
Validation failures. The request body or query parameters are invalid. Fix the input and retry.
Response format: Varies by endpoint. Field-validation errors are typically a JSON object keyed by field name with arrays of error strings. Value-level business validations return the structured ApiErrorResponseDto shape ({ error, message, businessErrors: [] }). Malformed JSON or type-mismatch errors from model binding return ASP.NET ProblemDetails.
{
"type": "https://tools.ietf.org/html/rfc9110#section-15.5.1",
"title": "One or more validation errors occurred.",
"status": 400,
"errors": {
"Users": ["At least one user is required"]
},
"traceId": "..."
}
Client Onboarding (POST /clients)
| Error | Cause |
|---|---|
| RFC 7807 validation response | Missing, empty, malformed, or type-invalid request body; [ApiController] handles this before the action runs |
At least one user is required | users array is empty (a null users array returns The Users field is required.) |
When multiple users provided, exactly one must have IsOnboardingUser=true | Multiple users, none marked as onboarding user |
Only one user can be marked as IsOnboardingUser | Multiple users have isOnboardingUser: true |
Unknown country: XX | client.country is not a valid ISO 3166-1 alpha-2 code or country name |
Field-keyed object with Cases[n].Field keys (e.g. {"Cases[0].DueDate": ["..."]}) | Cases included in the request have invalid fields; client creation is aborted. Annotation-level case errors arrive inside the Validation Problem Details errors object instead. |
Case Submission (POST /cases)
| Error | Cause |
|---|---|
Invalid currency code: XYZ | currencyCode not recognized |
Case date cannot be in the future | date is a future date |
Due date cannot be earlier than the case date | dueDate is before date |
Due date must be in the past | dueDate is strictly in the future (tomorrow or later) — today is accepted |
Case total must be a positive amount | amountToRecover is zero or negative |
Validation Problem Details errors.Debtor: The Debtor field is required. | debtor is explicitly null |
Invalid debtor type – allowed values are 'Company' or 'Private' (case-insensitive) | debtor.type is not "Company" or "Private" (case-insensitive) |
Invalid country code: ZZ | debtor.countryAlpha2 is not a valid ISO alpha-2 code. If using debtor.country (name), the error message will suggest using countryAlpha2 instead. |
A state code ... is required for US debtors | US debtor without debtor.stateAlpha2 or debtor.state |
Webhooks
| Error | Cause |
|---|---|
| Model state errors | Missing url or events on POST /v1/webhooks |
| URL validation error | Webhook URL is HTTP, localhost, private IP, or uses a non-standard port (only 443/8443 allowed) |
'<type>' is not a valid referral partner event type. Valid types: ... | Unknown event type on POST /v1/webhooks/test. Valid types: case.created, case.updated, case.closed, client.onboarding.poa_signed, client.onboarding.contract_signed, client.linked, client.link_declined, client.link_requested, client.link_expired, cases.replay_failed |
caseId is required for <type> events. | Case event test trigger without caseId |
externalTenantId is required for <type> events. | Client event test trigger without externalTenantId |
The controller-level POST /v1/webhooks/test validations above return the standard ApiErrorResponseDto / businessErrors shape. Data-annotation and model-binding failures are intercepted earlier by [ApiController] and return Validation Problem Details instead.
401 Unauthorized
Authentication failed. Check your credentials and environment.
Response format: RFC 7807 Problem Details.
{
"title": "Unauthorized",
"detail": "API Key was not provided",
"status": 401
}
| Detail message | Cause | Fix |
|---|---|---|
API Key was not provided | Missing XApiKey header | Add XApiKey: YOUR_KEY to every request |
Invalid API Key | Key not found in Debitura | Verify key is correct and matches the environment (test vs production) |
Token has expired, Token has been revoked, or a token-validation detail | Token expired (30-min lifetime), revoked, or malformed | Mint a fresh token via POST /oauth/token. See the retry-on-401 pattern. |
403 Forbidden
Test-only endpoints called against production.
{
"error": "This endpoint is only available in the test environment. Use testreferral-api.debitura.com for testing.",
"message": "This endpoint is only available in the test environment. Use testreferral-api.debitura.com for testing.",
"businessErrors": [
{
"type": "ValidationError",
"message": "This endpoint is only available in the test environment. Use testreferral-api.debitura.com for testing.",
"solutionUrl": ""
}
]
}
These endpoints return ApiErrorResponseDto, not Problem Details. The reset-client and webhook-test messages include the test host shown above; the deletability check uses the shorter sentence without the host suffix.
Affected endpoints:
DELETE /clients/{externalTenantId}— reset test clientsGET /clients/{externalTenantId}/is-deletablePOST /v1/webhooks/test— webhook testing
404 Not Found
The requested resource does not exist or does not belong to your partner account.
| Endpoint | ApiErrorResponseDto type/message | Common cause |
|---|---|---|
GET/DELETE /clients/{externalTenantId} | NotFound with an endpoint-specific message | Wrong externalTenantId, client archived, or belongs to another partner |
POST /oauth/token | NotFound: No linked client was found for the supplied tenant. | No active link for this externalTenantId — the client was never created, or the link was archived/withdrawn. A token is minted as soon as a non-deleted, non-archived link exists; SDCA does not need to be signed first. |
GET /cases/{id} | CaseNotFound with a case-scoped message | Case does not exist or belongs to a client not linked to your partner account |
GET/PUT/DELETE /v1/webhooks/{id} | WebhookNotFound: Webhook not found | Webhook does not exist or belongs to a different partner — list webhooks via GET /v1/webhooks to find valid IDs |
These responses use error, message, and businessErrors; they do not include a Problem Details title field.
409 Conflict
A conflict with existing state. Each conflict type requires different handling.
Response format: type, message, and optional data payload.
ClientExistsNeedsLinking
The email matches an existing Debitura account. Present data.onboardingLinks.url to the user for approval. Approval URLs expire after a configurable per-partner TTL (ApprovalTtlDays, default 7 days, clamped 1–30 days). Subscribe to client.link_expired to detect expiry; resubmit POST /clients with the same externalTenantId to generate a fresh approval URL.
Cases submitted in the original request are persisted server-side and replayed against the matched creditor automatically once the user approves the link. AllowPendingContracts=true is forced on replay regardless of what you sent on the original request, so missing SDCA or PoA can place a case in PendingContractSigning rather than returning a contract-related 422. KYC is evaluated separately and does not itself set that lifecycle on this replay path. There is no case.failed event. A replayed case that fails is never created, so it has no lifecycle; each failure is reported by a cases.replay_failed webhook (creditorReference, failureReason). Cases whose creditorReference already exists for the link are skipped silently. See Handling 409 Conflicts for the full flow.
{
"type": "ClientExistsNeedsLinking",
"message": "Client already exists in Debitura. Approval required to link to referral partner.",
"data": {
"externalTenantId": "your-tenant-id",
"onboardingLinks": {
"url": "https://app.debitura.com/ReferralPartner/Approve?token=..."
}
}
}
For privacy, the 409 response does not reveal any details about the user's matched Debitura account — externalTenantId and onboardingLinks.url are the only fields you need to act on. The response also includes client, users, and isAttributedClient for JSON-shape stability with the success response — do not rely on them being populated on this conflict path.
ClientAlreadyLinkedToAnotherPartner
The client has an active link to a different referral partner. Only one partner per creditor. Contact partnerships@debitura.com.
InvalidClientType
InvalidClientType is defined in the conflict-type enum but is not currently returned. A non-creditor account (e.g. a collection-partner user) is never matched by client detection. If its email is submitted as a user email, the request fails with 409 UserCreationFailed; if only the supportEmail matches, a new client is created. See Handling 409 Conflicts.
Approve / decline race (link request already processed)
Concurrent attempts to approve or decline the same link request return 409 Conflict (atomic claim). Only the first caller succeeds; subsequent callers should treat the conflict as "already processed." This typically appears as a user double-clicking the approval button or two browser tabs racing the same approval URL — your integration does not need to retry.
Withdrawal Not Allowed
POST /clients/{externalTenantId}/withdraw returns HTTP 409 with ApiErrorResponseDto when withdrawal is blocked — either because isAttributedClient is false, or cases have progressed beyond PendingContractSigning.
{
"error": "The client cannot be withdrawn. Withdrawal is only allowed for clients your organisation brought to Debitura, and only while all of their cases are still in onboarding.",
"message": "The client cannot be withdrawn. Withdrawal is only allowed for clients your organisation brought to Debitura, and only while all of their cases are still in onboarding.",
"businessErrors": [
{
"type": "Conflict",
"message": "The client cannot be withdrawn. Withdrawal is only allowed for clients your organisation brought to Debitura, and only while all of their cases are still in onboarding.",
"solutionUrl": ""
}
]
}
422 Unprocessable Entity
Business rule violations. The request is valid but cannot be processed due to domain constraints.
Response format: Two shapes are currently reachable. Orchestrator errors use a typed businessErrors array with type, message, and solutionUrl. Earlier business-rule checks can instead return a field-keyed dictionary with no businessErrors array. Detect the shape before reading it.
{
"businessErrors": [
{
"type": "MissingPowerOfAttorney",
"message": "Power of attorney not signed with partner X.",
"solutionUrl": "https://referral.debitura.com/PowerOfAttorney/Sign/..."
}
]
}
For example, the no-partner pre-check uses the legacy shape:
{
"BusinessRule": ["We don't have an exclusive pre‑legal partner in the provided jurisdiction"]
}
Typed businessErrors[].type | Cause | solutionUrl | Fix |
|---|---|---|---|
MissingDebtCollectionContract | SDCA not signed or needs re-signing | Yes | Present URL to client, retry after signing |
MissingPowerOfAttorney | PoA (or JPA for a jurisdiction pricing zone) not signed for this jurisdiction | Yes | Present URL to client, retry after signing |
MissingKycVerification | Collection partner requires KYC and creditor hasn't completed it | Yes | Present URL to client, retry after completion. Not bypassed by allowPendingContracts. |
CreditorBlocked | Creditor account is blocked | Present as an empty string | Contact Debitura support. Error message includes the reason. |
CollectionPartnerNotFound | collectionPartnerId override is unknown or deleted. Message: Collection partner {GUID} not found; the legacy CollectionPartnerId key carries the same message alongside | No | Omit the override or use a valid collection partner ID |
NoPartnerAvailable remains an internal enum value, but POST /cases does not emit it in businessErrors[].type: the reachable no-match path is the field-keyed BusinessRule response shown above.
Unlike contract-related errors, allowPendingContracts: true does not bypass MissingKycVerification. KYC is always enforced when submitting via bearer token. See KYC Verification.
429 Too Many Requests
Rate limit exceeded. Read retryAfter from the response body and wait.
{
"error": "rate_limit_exceeded",
"message": "Too many requests. Please try again later or contact support if you need higher limits.",
"retryAfter": 42.0
}
Limits per API key: 2,000 requests/minute, 20,000/hour, 100,000/day. See Rate Limiting.
500 Internal Server Error
Transient server error. Retry with exponential backoff (max 3 retries). If persistent, contact partnerships@debitura.com with the request details and timestamp.