This guide covers the full lifecycle of a debt collection integration with the Open API: from receiving a run, through reporting payments and write-offs, to closing cases.
- GET Debtors
- GET Transfer Details
- POST Confirm transfer retrieval
- POST Update debt collection
- GET debt collection cases
- GET Blocked Debtors
- GET Blocked Debts
A debt collection run is a batch of overdue customer claims that a studio hands over to a debt collection partner. The studio creates and verifies the run in , which triggers a notification to the partner via webhook.
The integration follows four steps:
- Receive — Studio verifies the run. Partner receives a webhook and fetches the debtor list.
- Confirm — Partner confirms receipt of the cases.
- Update — Partner sends case updates as collection progresses: payments, write-offs, partial reductions, blocks.
- Close — Partner closes the case when collection is complete or unsuccessful.
To get started with registration and activation, refer to Managing Activations.
Scopes define what the partner is allowed to do. The following scopes are required for all debt collection integrations:
| Scope | Endpoints |
|---|---|
DEBT_COLLECTION_READ | GET Debtors, GET Transfer Details, GET debt collection cases, GET Blocked Debtors, GET Blocked Debts |
DEBT_COLLECTION_WRITE | POST Update debt collection, POST Confirm transfer retrieval |
Scopes are configured per partner application in the Developer Portal. If a required scope is missing, the request is rejected before any business logic runs.
Partners can optionally be granted read access to individual customer records for the debtors in an active run.
| Scope | Access |
|---|---|
CUSTOMER_READ | Customer profile |
CUSTOMER_DOCUMENT_READ | Customer documents |
CUSTOMER_CONTRACT_READ | Customer contracts |
CUSTOMER_PAYMENT_SEARCH | Payment history |
Even with the correct scopes, a partner cannot access any customer's data by default. Access is granted per customer, automatically, when a debt collection run is transferred.
- Grant: When the studio confirms a run, writes a data privacy grant for each customer in the run, scoped to the partner's API key. This allows the partner to call customer-scoped endpoints (e.g.
GET /v1/customers/{id}/documents) for those specific customers only. - Revocation: When a case is closed (
POSITIVE,NEGATIVE, orREVERSAL) and the customer has no other open debt collection cases with the same partner, the grant is automatically removed. - Multiple concurrent runs: If a customer appears in multiple active runs for the same partner, the grant stays active until the last open case is closed.
The partner never has blanket access to the studio's full customer base — only to the specific debtors actively in collection with them.
Every debt collection case involves three IDs. Keep all three — you need them for every update.
| ID | Scope | What it is |
|---|---|---|
debtorId | Per studio (not globally unique) | Identifies the member within a specific studio's debt collection context |
collectionCaseId | Per transfer | Identifies a debtor's case within a single debt collection run. One debtor can have multiple IDs if they appeared in multiple runs |
debtId | Per debt | Identifies a single overdue charge. Required for amount-level reporting |
Every call to POST Update debt collection sends the complete current state of the case. reads the new values and reconciles them with the previous state automatically.
This means:
- Always include all
collectionCaseIdsfor the partner's case, even if only one changed - Always include all
debts, even those with no changes in this update - Never send only the fields that changed — send the full case every time
The agencyCollectionCaseId is the partner's own case identifier. It is technically optional in the API, but must be set on every update for two critical reasons.
Grouping across multiple runs
A debtor can appear in more than one debt collection run (for example, the studio submits a second run for the same member after the first one was already transferred). The agencyCollectionCaseId is what ties all of those collectionCaseIds together under a single partner case. and the studio see all matching case IDs as one unit of work. Without it, has no way to associate the cases, and the studio cannot tell which collectionCaseIds belong together.
Invoicing
The partner invoices the studio for collection work per case. Only the first collectionCaseId under a given agencyCollectionCaseId is billed. Any additional collectionCaseIds grouped under the same ID are treated as additions to the same case and are not billed separately.
If agencyCollectionCaseId is not set, each collectionCaseId is treated as a separate case — the studio will be over-billed for what is in reality one collection engagement.
Set
agencyCollectionCaseIdon the very first update for a debtor and keep it consistent across all subsequent updates for that case.
Each debt has four amount fields that together define its current state:
| Field | What it means |
|---|---|
originalAmount | The total amount transferred to the partner for collection |
paidAmount | Running total received from the debtor |
reducedAmount | Running total of negotiated write-downs or partial waivers |
writeOffAmount | Running total formally derecognized for tax/accounting purposes |
The open amount in the studio's books is calculated as:
openAmount = originalAmount - paidAmount - reducedAmount - writeOffAmountAll four fields are cumulative totals, not deltas. Always send the running total. To increase a write-off from 20 to 50, send writeOffAmount: 50. To fully reverse it, send writeOffAmount: 0.
When a studio verifies a debt collection run, fires a FINANCE_DEBT_COLLECTION_RUN_CREATED webhook (see Event types). The event body contains an entityId field holding the debtCollectionRunId.
Use this ID to fetch the case details:
- GET Debtors — Returns all debtors for the run in pages. Use this to get
debtorId,collectionCaseId, anddebtIdvalues for each debtor. - GET Transfer Details — Returns metadata about the transfer: status, date, client information.
Note:
debtorIdis scoped per studio, not globally unique. The same physical person at two different studios will have differentdebtorIdvalues.
After fetching the debtor list, the partner must confirm receipt using POST Confirm transfer retrieval. This tells the studio that the data was successfully transferred to the partner.
Confirm immediately after fetching the cases. Do not wait until collection begins.
Use POST Update debt collection to report any state change as collection progresses. Increase paidAmount as payments come in. See the examples section for full payloads.
Set agencyCollectionCaseId on every update — see The agencyCollectionCaseId field for why this is critical for grouping and invoicing. will include it in webhook events sent back to you, so you can correlate incoming events with your own records. The optional publicCollectionCaseId is a separate ID used in debtor-facing communication.
Partners can reduce the open amount on individual debts without closing the case. This is useful for tax write-offs, negotiated partial waivers, or temporary derecognition during insolvency proceedings.
Use the writeOffAmount and reducedAmount fields on each debt object:
| Field | Booking entry in | Use for |
|---|---|---|
writeOffAmount | DebtClaimAbandonment | Formal tax derecognition requested by the studio |
reducedAmount | DebtClaimReduction | Negotiated write-down or partial waiver |
Prerequisites — both must be enabled:
Partner-level flag (Developer Portal): The debt modification capability must be enabled for the integration in the Developer Portal. Once enabled at the partner level, it is automatically forwarded to the studio tenants connected to that partner. Contact Sport Alliance to enable this for your integration.
Studio-level setting: Each studio must enable immediate debt modification under Settings -> Finance -> Collection. On the Collection Partner configuration, the "Debt modification on update" option must be set to Immediately. If set to
IGNORED(the default), values are stored on the case but no booking entries are created in the studio's member account.
Verify the current mode via the configuration endpoint — look for debtModificationOnUpdateMode: IMMEDIATELY.
Key behaviors:
- The member's access restriction and debt collection status are not removed when the open amount reaches 0 via write-off. Only a
closurelifts the restriction. - A debt written off to 0 will not appear in future debt collection runs.
- Write-offs and reductions are fully reversible while the case is open — see the write-off examples.
Note on
canceledAmount: This field is deprecated. UsewriteOffAmountinstead. ExistingcanceledAmountvalues are automatically migrated towriteOffAmountwhen the studio enablesIMMEDIATELYmode.
A debtor or an individual debt can be excluded from future debt collection runs by adding a block property to the update. Removing the block property in a follow-up update removes the block.
See the blocking examples for full payloads.
Send a closure object inside the case update to close a case. The closure type determines what happens in :
| Type | Effect on the member |
|---|---|
POSITIVE | Debt collection status removed. Member access restored (if no other open cases). |
NEGATIVE | Debt collection status removed. Same as POSITIVE from the member's perspective. |
REVERSAL | Debt collection status removed. Use when the partner terminates the contract. |
REJECTION | Debt collection status stays. All restrictions remain. rejectionReason is required. |
The WRITE_OFF_REMAINING_DEBTS option can be added to POSITIVE or NEGATIVE closures to write off any remaining open amount at closure time. Alternatively, use per-debt writeOffAmount before closing — both approaches can be combined.
Closure is final. Once a case is closed, it cannot be reopened via a subsequent update. A follow-up update without a
closureelement does not reopen the case. The same applies toWRITE_OFF_REMAINING_DEBTS: if the first closure update does not include it, a follow-up update adding it will be ignored.
After closure, the partner can still report payments by increasing paidAmount. will create the corresponding payment booking in the member account.
Studios can update case status directly in the UI (for example, closing or reverting a case) without contacting the partner first. When this happens, fires a webhook so the partner's records stay in sync.
This feature must be explicitly enabled for the partner in the Developer Portal before studio-side updates trigger webhook events.
The event payload contains the caseAdjustmentRequestId as entityId, plus either:
agencyCollectionCaseId(if the partner has assigned one), orcollectionCaseId
On receiving the webhook, fetch the updated case state via GET debt collection cases using the ID from the event, then update your records accordingly.
When a studio moves a customer to a different home studio, partners must listen to the CUSTOMER_HOME_STUDIO_UPDATED webhook event (sometimes shown as customer_home_studio_updated). The event includes content.sourceStudioId and content.targetStudioId.
After a home studio change, use the Open API Token for content.targetStudioId for all future retrievals and updates for that customer. Using the old studio's token will result in errors.
Studio switches to a new partner: The old partner continues handling existing cases as normal but receives no new runs.
Full termination (either side initiates):
- If initiated by the studio: close all cases with
type: REJECTIONandrejectionReason: WITHDRAWN_BY_STUDIO. - If initiated by the partner: close all cases with
type: REVERSAL.
Automatic payment forwarding from studio to partner is not supported. If a customer pays the studio directly:
- The studio informs the partner out of band.
- The partner reports the payment via Open API as if received directly (increase
paidAmount). - The studio does not book the payment in .
Money transfer arrangements between studio and partner are at their own discretion.
There are two paths, depending on what needs to change:
Partner reports a write-off or reduction (preferred): If the studio has agreed to a write-off or partial reduction, apply it directly via writeOffAmount or reducedAmount in the next case update. No closure required. See Reporting write-offs and reductions for prerequisites.
Studio needs to change the debt structure (wrong amount, missing debt): If the original amount was incorrect or a debt needs to be split, writeOffAmount and reducedAmount are not sufficient. The only path is:
- Reject the case with
rejectionReason: WITHDRAWN_BY_STUDIO(see Reversal of Case) - The studio adjusts the debt in
- The studio creates a new debt collection run
(A feature currently in development will streamline this: the studio will be able to adjust a debt mid-collection, and the partner will receive a notification with the option to accept or reject the change.)
The following examples show the exact payloads to send for common scenarios. All examples share the same preconditions.
Each example assumes:
- The studio has performed 2 debt collection runs, and you as partner have fetched the included member
- Both debt collection runs contained exactly one member, with debtorId
debtorid(usually a UUID or number — simplified here for readability) - The first debt collection run:
- Contains collectionCaseId
collection-caseid-1 - A single debt with debtId
debtid-1of 10 EUR
- Contains collectionCaseId
- The second debt collection run:
- Contains collectionCaseId
collection-caseid-2 - A single debt with debtId
debtid-2of 20 EUR
- Contains collectionCaseId
(See preconditions)
Lets assume you have received a single payment on the full amount and want to report the payment with a successful case closure.
You would send the following update to POST Update debt collection:
{
"debtors": [
{
"debtorId": "debtorid",
"agencyCollectionCases": [
{
"agencyCollectionCaseId": "your-case-id",
"publicCollectionCaseId": "optional-your-customer-facing-id",
"collectionCaseIds": [
"collection-caseid-1",
"collection-caseid-2"
],
"closure": {
"type": "POSITIVE",
"options": [],
"date": "2024-10-14",
"closureReason": "Optional, e.g. 'Member has fully paid'"
},
"debts": [
{
"debtId": "debtid-1",
"originalAmount": 10,
"paidAmount": 10,
"reducedAmount": 0,
"writeOffAmount": 0,
"currency": "EUR"
},
{
"debtId": "debtid-2",
"originalAmount": 20,
"paidAmount": 20,
"reducedAmount": 0,
"writeOffAmount": 0,
"currency": "EUR"
}
]
}
]
}
],
"requestId": "your-request-id"
}Consequences
- will create a payment of 30 EUR (since this is your first update and the
paidAmountis 30 EUR in total) - The debt collection case is closed in
- Assuming that the Member has no other debt collection cases or open debts, the debt collection level is removed from the member and he may enter the Gym again
(See preconditions)
There was a negotiation with the debtor, and you and the debtor agreed upon that he must only pay 50% of the total value to close the case.
You received the money and want to report to the payment of 50% (15 EUR) and close the case.
Its up to you how the partial payment is distributed among the open debts. In this example we assume that the paid amount is distributed equally among the debts.
You would send the following update to POST Update debt collection:
{
"debtors": [
{
"debtorId": "debtorid",
"agencyCollectionCases": [
{
"agencyCollectionCaseId": "your-case-id",
"publicCollectionCaseId": "optional-your-customer-facing-id",
"collectionCaseIds": [
"collection-caseid-1",
"collection-caseid-2"
],
"closure": {
"type": "POSITIVE",
"options": [],
"date": "2024-10-14",
"closureReason": "Optional, e.g. 'Negotiated case closure with partial payment'"
},
"debts": [
{
"debtId": "debtid-1",
"originalAmount": 10,
"paidAmount": 5,
"reducedAmount": 0,
"writeOffAmount": 0,
"currency": "EUR"
},
{
"debtId": "debtid-2",
"originalAmount": 20,
"paidAmount": 10,
"reducedAmount": 0,
"writeOffAmount": 0,
"currency": "EUR"
}
]
}
]
}
],
"requestId": "your-request-id"
}Consequences
- will create a payment of 15 EUR (since this is your first update and the
paidAmountis 15 EUR in total) - The debt collection case is closed in
- If the studio has a setting enabled to write off remaining debts, the 2 debts will be reduced by the still open amount, so the debtor then has no open debts anymore
- If the remaining debts are written off the debt collection level of the debtor is removed and he may enter the Gym again, if he is still a member with active contract
- Alternatively, partners can report a per-debt
writeOffAmountfor the still-open portion in the same update (or before sending the closure) instead of relying on the studio-side setting. See Reporting write-offs and reductions for prerequisites.
(See preconditions)
You received a partial payment of 5 EUR by the debtor. You assign this payment to one debt (debtid) and want to report this payment to , without closing the case.
You would send the following update to POST Update debt collection:
{
"debtors": [
{
"debtorId": "debtorid",
"agencyCollectionCases": [
{
"agencyCollectionCaseId": "your-case-id",
"publicCollectionCaseId": "optional-your-customer-facing-id",
"collectionCaseIds": [
"collection-caseid-1",
"collection-caseid-2"
],
"debts": [
{
"debtId": "debtid-1",
"originalAmount": 10,
"paidAmount": 5,
"reducedAmount": 0,
"writeOffAmount": 0,
"currency": "EUR"
},
{
"debtId": "debtid-2",
"originalAmount": 20,
"paidAmount": 0,
"reducedAmount": 0,
"writeOffAmount": 0,
"currency": "EUR"
}
]
}
]
}
],
"requestId": "your-request-id"
}Consequences
- will create a payment of 5 EUR
Note that as in every update, both collectionCaseIds are added, and also both debts, also the one that didn't change. The update always must contain the whole case, including all case ids and debts.
(See preconditions)
Lets assume you first reported by mistake a payment of 5 EUR on debt debtid-1. Actually you only received 2 EUR by the debtor and want to correct this.
You would send the following update to POST Update debt collection:
{
"debtors": [
{
"debtorId": "debtorid",
"agencyCollectionCases": [
{
"agencyCollectionCaseId": "your-case-id",
"publicCollectionCaseId": "optional-your-customer-facing-id",
"collectionCaseIds": [
"collection-caseid-1",
"collection-caseid-2"
],
"debts": [
{
"debtId": "debtid-1",
"originalAmount": 10,
"paidAmount": 2,
"reducedAmount": 0,
"writeOffAmount": 0,
"currency": "EUR"
},
{
"debtId": "debtid-2",
"originalAmount": 20,
"paidAmount": 0,
"reducedAmount": 0,
"writeOffAmount": 0,
"currency": "EUR"
}
]
}
]
}
],
"requestId": "your-request-id"
}Consequences
- will reduce the previously created payment of 5 EUR down to 2 EUR
(See preconditions)
Lets assume the studio you are working with has submitted a member to debt collection by mistake. The studio sends and email to you, asking to close the case without any processing.
You would send the following update to POST Update debt collection:
{
"debtors": [
{
"debtorId": "debtorid",
"agencyCollectionCases": [
{
"agencyCollectionCaseId": "your-case-id",
"publicCollectionCaseId": "optional-your-customer-facing-id",
"collectionCaseIds": [
"collection-caseid-1",
"collection-caseid-2"
],
"closure": {
"type": "REJECTION",
"rejectionReason": "WITHDRAWN_BY_STUDIO",
"options": [],
"date": "2024-10-14",
"closureReason": "Optional, e.g. 'Studio asked for case closure on 2024-10-14 as transfer made by mistake'"
},
"debts": [
{
"debtId": "debtid-1",
"originalAmount": 10,
"paidAmount": 0,
"reducedAmount": 0,
"writeOffAmount": 0,
"currency": "EUR"
},
{
"debtId": "debtid-2",
"originalAmount": 20,
"paidAmount": 0,
"reducedAmount": 0,
"writeOffAmount": 0,
"currency": "EUR"
}
]
}
]
}
],
"requestId": "your-request-id"
}(See preconditions)
Lets assume your received a debtor but with the given address information it is not possible to contact the debtor, and any address search was also unsuccessuful.
You would send the following update to POST Update debt collection:
{
"debtors": [
{
"debtorId": "debtorid",
"agencyCollectionCases": [
{
"agencyCollectionCaseId": "your-case-id",
"publicCollectionCaseId": "optional-your-customer-facing-id",
"collectionCaseIds": [
"collection-caseid-1",
"collection-caseid-2"
],
"closure": {
"type": "REJECTION",
"rejectionReason": "POSTAL_DELIVERY_NOT_POSSIBLE",
"options": [],
"date": "2024-10-14",
"closureReason": "Optional: "
},
"debts": [
{
"debtId": "debtid-1",
"originalAmount": 10,
"paidAmount": 0,
"reducedAmount": 0,
"writeOffAmount": 0,
"currency": "EUR"
},
{
"debtId": "debtid-2",
"originalAmount": 20,
"paidAmount": 0,
"reducedAmount": 0,
"writeOffAmount": 0,
"currency": "EUR"
}
]
}
]
}
],
"requestId": "your-request-id"
}(See preconditions)
Let's assume you want to block the debtor from future debt collection runs, you have sent an update with a block for the debtor.
You would send the following update to POST Update debt collection:
{
"debtors": [
{
"debtorId": "debtorid",
"agencyCollectionCases": [
{
"agencyCollectionCaseId": "your-case-id",
"publicCollectionCaseId": "optional-your-customer-facing-id",
"collectionCaseIds": [
"collection-caseid-1",
"collection-caseid-2"
],
"block": {
"limitType": "LIMITED",
"endDate": "2024-09-20"
},
"debts": [
{
"debtId": "debtid-1",
"originalAmount": 10,
"paidAmount": 0,
"reducedAmount": 0,
"writeOffAmount": 0,
"currency": "EUR"
},
{
"debtId": "debtid-2",
"originalAmount": 20,
"paidAmount": 0,
"reducedAmount": 0,
"writeOffAmount": 0,
"currency": "EUR"
}
]
}
]
}
],
"requestId": "your-request-id"
}Because there are no blocks in the json at the specific debts, eventually existing blocks will be removed.
If you later on want to enable the debtor but block debtid-2 forever, you would send the following update to POST Update debt collection:
{
"debtors": [
{
"debtorId": "debtorid",
"agencyCollectionCases": [
{
"agencyCollectionCaseId": "your-case-id",
"publicCollectionCaseId": "optional-your-customer-facing-id",
"collectionCaseIds": [
"collection-caseid-1",
"collection-caseid-2"
],
"debts": [
{
"debtId": "debtid-1",
"originalAmount": 10,
"paidAmount": 0,
"reducedAmount": 0,
"writeOffAmount": 0,
"currency": "EUR"
},
{
"debtId": "debtid-2",
"originalAmount": 20,
"paidAmount": 0,
"reducedAmount": 0,
"writeOffAmount": 0,
"currency": "EUR",
"block": {
"limitType": "UNLIMITED"
}
}
]
}
]
}
],
"requestId": "your-request-id"
}(See preconditions)
The studio requests that the full outstanding amount on debtid-1 (10 EUR) be written off for tax reasons. The case should remain open for continued collection on debtid-2.
You would send the following update to POST Update debt collection:
{
"debtors": [
{
"debtorId": "debtorid",
"agencyCollectionCases": [
{
"agencyCollectionCaseId": "your-case-id",
"collectionCaseIds": [
"collection-caseid-1",
"collection-caseid-2"
],
"debts": [
{
"debtId": "debtid-1",
"originalAmount": 10,
"paidAmount": 0,
"reducedAmount": 0,
"writeOffAmount": 10,
"currency": "EUR"
},
{
"debtId": "debtid-2",
"originalAmount": 20,
"paidAmount": 0,
"reducedAmount": 0,
"writeOffAmount": 0,
"currency": "EUR"
}
]
}
]
}
],
"requestId": "your-request-id"
}Consequences:
- creates a
DebtClaimAbandonmentbooking of -10 EUR ondebtid-1 - Open amount of
debtid-1: 10 - 0 (paid) - 0 (reduced) - 10 (written off) = 0 EUR debtid-1will not appear in future debt collection runs- The debt collection case remains open;
debtid-2is unaffected - The member's debt collection status and entrance restriction are unchanged
(See preconditions)
Following the previous example (writeOffAmount: 10 on debtid-1), a payment of 5 EUR is received. Reduce the write-off by the same amount — both fields are cumulative totals, so send the new running totals.
{
"debtors": [
{
"debtorId": "debtorid",
"agencyCollectionCases": [
{
"agencyCollectionCaseId": "your-case-id",
"collectionCaseIds": [
"collection-caseid-1",
"collection-caseid-2"
],
"debts": [
{
"debtId": "debtid-1",
"originalAmount": 10,
"paidAmount": 5,
"reducedAmount": 0,
"writeOffAmount": 5,
"currency": "EUR"
},
{
"debtId": "debtid-2",
"originalAmount": 20,
"paidAmount": 0,
"reducedAmount": 0,
"writeOffAmount": 0,
"currency": "EUR"
}
]
}
]
}
],
"requestId": "your-request-id"
}Consequences:
- The previous
DebtClaimAbandonmentof -10 EUR is partially reversed; a new abandonment of -5 EUR replaces it - A
DebtCollectionPaymentof 5 EUR is created - Open amount on
debtid-1: 10 - 5 (paid) - 0 (reduced) - 5 (written off) = 0 EUR
This is a planned feature and currently not supported.
This is a planned feature and currently not supported.
This is a planned feature and currently not supported.