Clients

Create and onboard a new client

Creates a new client (creditor) in the Debitura platform and links them to the authenticated referral partner.

Request Structure

  • ExternalTenantId (required) - Your unique identifier for this client (for correlation and future lookups)
  • Client (required) - Company information (name, registration number, country, address, contact details)
  • Users (required, 1-N) - At least one user with email and name. For multiple users, exactly one must have IsOnboardingUser=true. Single-user requests auto-infer the onboarding user.
  • Cases (optional) - Optionally create multiple collection cases during client creation (validated upfront to prevent orphaned clients)

Onboarding Flow

  1. New Client (201/202)

    • Client and users are created in Debitura
    • ReferralPartnerClientLink is established with revenue sharing percentage
    • If onboarding is complete (terms signed), returns 201
    • If onboarding pending (terms not signed), returns 202 with OnboardingLinks.Url for completing the process

    202 Response - Two URL Scenarios:

    1a. New Client - Full Onboarding URL

    • Scenario: Client has never signed the SDCA (Standard Debt Collection Agreement)
    • URL Pattern: {onboardingBase}/companydetails/{referralPartnerClientLinkId}
    • User Journey: Multi-step onboarding (company details → users → sign SDCA)
    • Use Case: First-time client registration

    1b. Existing Client - Contract Upgrade URL

    • Scenario: Client previously signed SDCA but terms have been updated (NeedsRefresh=true)
    • URL Pattern: {onboardingBase}/ContractUpgrade/Sign/{referralPartnerClientLinkId}
    • User Journey: Streamlined contract re-signing only (no company details re-entry)
    • Use Case: SDCA version upgrade (e.g., new pricing tiers, updated legal terms)
    • Note: This can happen to previously-ready clients (201 → 202 state transition)

    Important: Without Idempotency-Key, externalTenantId only prevents a duplicate client/link; it does not make case-bearing retries safe or replay the original response. Exact response replay and case-safe retries require Idempotency-Key. Client/link lookups still re-check SDCA status, so an unkeyed call for an existing client that previously returned 201 Created may flip to 202 Accepted if Debitura releases a new SDCA version requiring re-signature.

  2. Existing Client - Needs Linking (409 Conflict) Business conflicts use ConflictResponse.Type; keyed requests can additionally return IdempotencyKeyInProgress while the winning request is still running:

    2a. ClientExistsNeedsLinking (Type: "ClientExistsNeedsLinking")

    • Client already exists in Debitura (matched by user email or support email)
    • A link request is created with an expiration window configured per-partner (ApprovalTtlDays, default 7 days, clamped 1–30)
    • Existing client must approve the referral partner link via OnboardingLinks.Url
    • If approved, IsAttributedClient will be FALSE (revenue only on partner-referred cases)
    • Cases submitted in the original request are persisted and replayed against the linked creditor automatically when the user approves. AllowPendingContracts=true is forced on replay so cases land in PendingContractSigning rather than 422-failing on missing SDCA/PoA/KYC.
    • Returns 409 with OnboardingLinks.Url pointing to approval page

    2b. ClientAlreadyLinkedToAnotherPartner (Type: "AlreadyLinkedToAnotherPartner")

    • Client is already linked to a DIFFERENT referral partner
    • Only one referral partner link per client is supported
    • Cannot proceed - contact Debitura support for resolution
    • Returns 409 with error message and support contact
  3. Idempotency

    • Without an Idempotency-Key, repeated calls with the same ExternalTenantId keep the existing business-key behaviour and return the current client status without duplicating the client or link
    • Optionally send an Idempotency-Key of up to 255 characters. The key is scoped to the authenticated referral partner and retained for 24 hours
    • The same key with the same effective request body replays the original terminal status and body, including approval-required 409 responses
    • Effective comparison follows this endpoint's execution rules. For one user, IsOnboardingUser=false and true are equivalent because the user is always selected for onboarding. Omitted and empty Cases/ClaimLines are equivalent. Within Cases, SkipCreationEmails, AllowPendingContracts, and AssignedUserEmail do not change referral-onboarding execution; unsafe ReturnUrl values are dropped; valid debtor Type casing is normalized; ContactPerson is ignored for Private debtors; and State is ignored when StateAlpha2 is supplied.
    • The same key with a different effective body returns 422 with businessErrors[0].type = IdempotencyConflict; a retry while the first request is still running returns 409 with businessErrors[0].type = IdempotencyKeyInProgress and Retry-After: 2
    • Field-validation 400 responses and failures before any durable effect do not consume the key. A failure after client, link, approval-request, or case work starts is sealed and replayed to prevent duplicates
  4. Previously Withdrawn Client - Re-linked (201/202)

    • Applies when you re-submit an ExternalTenantId you previously withdrew via POST /clients/{externalTenantId}/withdraw, AND the re-submission still identifies that same client — that is, it carries a user email or supportEmail already registered on it
    • The ORIGINAL client is re-linked rather than duplicated: a new active link is created on the same Debitura client, IsAttributedClient stays true, and the archived link is retained for audit
    • Cases in the re-submission are created against that client. A CreditorReference used by a case that the withdrawal closed can be reused — such a case no longer reserves it
    • Any approval request still pending for this ExternalTenantId is resolved as declined, and a client.link_declined webhook is sent for it
    • Falls back to the 409 handling in scenario 2 if the ExternalTenantId now resolves to a DIFFERENT client, if that client has since linked to another partner, or if the withdrawn link was not attributed to you
    • Creates a new client as in scenario 1 — no re-link, no 409 — if the re-submission carries no contact email we recognise. A matching email is our only independent confirmation that this is the same client; without one we cannot tell a genuine re-submission apart from the same ExternalTenantId being reused for somebody else
    • The re-link does not update the client or add users. Client details and Users in the re-submission are ignored, and the new link keeps the original onboarding user — send a new contact through the normal user-invitation flow instead

Response Codes

  • 201 Created - Client created and fully onboarded (terms signed, ready to create cases, IsAttributedClient=true)
  • 202 Accepted - Client created but onboarding incomplete (OnboardingLinks.Url provided to complete, IsAttributedClient=true)
  • 400 Bad Request - Validation errors (missing required fields, invalid data, duplicate CreditorReferences within request). Most validation failures return a single-message ApiErrorResponseDto. Field-length violations (Client.Name, Client.SupportPhone, Client.RegistrationNumber, Client.SupportEmail, Users[].Email) are the one exception: they return a field-keyed error dictionary instead, e.g. { "Client.Name": ["Name must not exceed 80 characters (received 95)."] }, so multiple field problems can be reported together in one round-trip.
  • 409 Conflict - Business conflicts or keyed-request contention:
    • ConflictResponse.Type = "ClientExistsNeedsLinking" - Client exists, needs approval link (if approved, IsAttributedClient=false)
    • ConflictResponse.Type = "AlreadyLinkedToAnotherPartner" - Client already linked to different partner (cannot proceed)
    • businessErrors[0].type = "IdempotencyKeyInProgress" - Another request with the same partner, key, and effective body is still running; retry after the Retry-After delay
  • 422 Unprocessable Entity - Business rule violation, including businessErrors[0].type = "IdempotencyConflict" when the same key is reused with a different effective body
  • 500 Internal Server Error - Unexpected server error

Optional Multi-Case Creation If the Cases array is provided in the request:

  • ALL cases are validated BEFORE client creation (checks field validation and duplicate references)
  • Client is ALWAYS created to ensure referral partner link and future attribution
  • Cases with available collection partners are created successfully (CaseResults.SuccessfulCases)
  • Cases without available partners are NOT created and reported as failures (CaseResults.FailedCases)
  • If validation errors exist (primitives, duplicates), client creation is aborted and 400 is returned
  • Successful cases appear in CaseResults.SuccessfulCases with full case details
  • Failed cases appear in CaseResults.FailedCases with CaseIndex, CreditorReference, and error details (e.g., "No collection partner available for jurisdiction")

Claim Amount and Aging on each case Each entry in Cases accepts the claim amount in one of three ways:

  1. amountToRecover plus a single dueDate
  2. amountToRecover plus the cumulative amountToRecoverOver6/12/24Months buckets
  3. claimLines[] — send the unpaid invoices and let us do the arithmetic (recommended for multi-invoice claims)
"claimLines": [
  { "dueDate": "2024-01-15", "amount": 423.42, "reference": "INV-1001" },
  { "dueDate": "2025-05-02", "amount": 700.87, "reference": "INV-1042" }
]
  • amount is the outstanding balance on that invoice, already net of payments and credit notes.
  • We derive amountToRecover and all three age buckets from the lines, so send claimLines instead of amountToRecover and the bucket fields. Sending both is a 400.
  • If you omit the case-level dueDate, it is set to the oldest due date across that case's lines; an explicit dueDate is honoured.
  • Maximum 1000 lines per case, and a maximum of 1000 lines in total across all entries in Cases. Per-line errors are keyed by case and line index, e.g. Cases[0].ClaimLines[2].DueDate.
  • claimLines is a pricing input only — it is not stored and is not shown to the collecting partner.

User Roles

  • IsOnboardingUser=true - User receives CreditorAdmin role (full access)
  • IsOnboardingUser=false - User receives CreditorUser role (limited access)

Revenue Sharing (Fee Percentage Locked at Link Creation)

  • The referral fee percentage is snapshot from the partner's current configuration at the exact moment the client link is created
  • This percentage is IMMUTABLE and will NEVER change for this client - even if the partner's global fee percentage is updated later
  • The locked percentage is used for ALL revenue calculations for ALL cases created by this client, forever
  • This ensures predictable, stable revenue sharing and prevents disputes over retroactive fee changes
  • To see the locked percentage for a specific client, use GET /clients/{externalTenantId} (included in response)
post/clients

Headers

Idempotency-Keystring

Optional non-empty, non-whitespace idempotency key (max 255 characters) for safely retrying this request. If a previous request used the same key with the same effective request body, the original terminal response is replayed verbatim. Reusing the key for a different effective request returns 422 with businessErrors[0].type: "IdempotencyConflict". Field-level 400 validation errors are not stored, so you may fix the request and retry with the same key. An approval-required 409 remains the stored response even after the client later approves; use the client lookup or webhooks for current state.

Request body

externalTenantIdstring required

Example request

{
  "externalTenantId": "tenant-12345",
  "client": {
    "name": "Acme Corp",
    "registrationNumber": "12345678",
    "address": "123 Business Street",
    "zipCode": "1000",
    "city": "Lisbon",
    "state": "Lisbon",
    "country": "Portugal",
    "supportEmail": "billing@acme-corp.com",
    "supportPhone": "+351 123 456 789"
  },
  "users": [
    {
      "name": "John Doe",
      "email": "john.doe@acme-corp.com",
      "isOnboardingUser": true
    }
  ],
  "cases": [
    {
      "currencyCode": "EUR",
      "amountToRecover": 4000,
      "date": "2026-03-01",
      "dueDate": "2026-03-09",
      "claimDescription": "Custom mobile app development services",
      "comments": "Outstanding invoice INV 2024 00789 for custom mobile app development delivered 15 Nov 2024; payment 60 days overdue despite two reminders.",
      "creditorReference": "INV‑2024‑00789",
      "isTest": false,
      "debtor": {
        "type": "Company",
        "name": "Acme Corp",
        "contactPerson": "John Doe",
        "companyRegistrationNumber": "12345678",
        "address": "123 Business Street",
        "zipCode": "1000",
        "city": "Lisbon",
        "state": "Lisbon",
        "countryAlpha2": "PT",
        "email": "billing@acme‑corp.com",
        "phone": "+351 123 456 789"
      }
    }
  ]
}

Response

Client created and fully onboarded (terms signed)

externalTenantIdstring nullable
onboardingDoneboolean
isAttributedClientboolean
dateLinkedstring date-time
kycStatus'NotRequired' | 'Pending' | 'Verified'

KYC (Know Your Customer) verification status for a client linked to a referral partner. Captures three distinct states that a simple boolean cannot express.

Wire format is LOCKED to snake_case string values ("not_required", "pending", "verified") via Newtonsoft.Json StringEnumConverter with SnakeCaseNamingStrategy. The host serializer for this API is Newtonsoft.Json (AddNewtonsoftJson in Program.cs), so a System.Text.Json JsonStringEnumConverter attribute would be silently ignored — hence the explicit Newtonsoft converter here.

The System.Text.Json JsonConverter attribute is declared in addition so that consumers (and our own integration tests) who parse responses using System.Text.Json can deserialize the string form back into this enum. The Newtonsoft converter is what actually controls outbound wire format on this host.

Example response

{
  "caseResults": {
    "successfulCases": [
      {
        "debtor": {
          "type": "Company",
          "name": "Pacific Trading LLC",
          "contactPerson": "John Smith",
          "companyRegistrationNumber": "47-1234567",
          "address": "123 Market Street, Suite 400",
          "zipCode": "94105",
          "city": "San Francisco",
          "state": "California",
          "stateAlpha2": "CA",
          "countryAlpha2": "US",
          "country": "United States",
          "email": "accounts@pacifictrading.com",
          "phone": "+1 415 555 0123"
        }
      }
    ]
  }
}

Changes