Appearance
Debt lists
Generated from
DialerDigital/corecommit0fc18008b3c8— specopenapi/dialer-v1.yamlv0.134.0. Do not edit by hand; see How this reference is built. Download the OpenAPI spec.
Reusable lists of debts, associated with campaigns (ADR #162).
Endpoints
| Method | Path | Summary |
|---|---|---|
GET | /v1/debt-lists | The tenant's reusable lists, with their size counted on read |
POST | /v1/debt-lists | Create a reusable list |
GET | /v1/debt-lists/{id} | One reusable list |
POST | /v1/debt-lists/{id}/archive | Archive a list (terminal, never a delete) |
POST | /v1/debt-lists/{id}/import | Bulk import INTO a list (the pipeline of /v1/debts/import) |
GET | /v1/debt-lists/{id}/members | Read the members of a list |
DELETE | /v1/debt-lists/{id}/members | Take up to 500 debts out of a list in one call |
DELETE | /v1/debt-lists/{id}/members/{debt_id} | Take one debt out of a list |
PUT | /v1/debt-lists/{id}/campaigns/{campaign_id} | Associate a list with a campaign |
DELETE | /v1/debt-lists/{id}/campaigns/{campaign_id} | Dissociate a list from a campaign |
The tenant's reusable lists, with their size counted on read
http
GET /v1/debt-listsEvery list of the tenant, by name. size (members) and campaign_ids (the campaigns the list is associated with) are counted when the request is served: the platform keeps no counter for them, so the number is never older than the response. Capped at 2 000 lists. Archived lists (POST /v1/debt-lists/{id}/archive) are excluded unless include_archived=true.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
include_archived | query | boolean | no | true also returns the archived lists. Anything else leaves them out. |
Responses
200
The lists.
| Field | Type | Required | Description |
|---|---|---|---|
debt_lists | array of DebtList | yes | |
debt_lists[].id | string (uuid) | yes | |
debt_lists[].tenant_id | string (uuid) | no | |
debt_lists[].name | string | yes | |
debt_lists[].size | integer | yes | Members of the list now. |
debt_lists[].campaign_ids | array of string (uuid) | yes | The campaigns the list is associated with, oldest association first. |
debt_lists[].archived | boolean | yes | |
debt_lists[].archived_at | string,null (date-time) | no | |
debt_lists[].created_at | string (date-time) | no | |
debt_lists[].updated_at | string (date-time) | no | |
truncated | boolean | yes | true when there are more rows than the 2 000 lists the listing returns: the response is a prefix in the documented order, not the whole set. |
401 — Unauthorized
Missing, unknown or revoked API key (or, on /v1/admin/*, a bad/unset admin token; on webhooks, a bad/missing signature). A dashboard session (ddu_) that a newer login of the same person ended answers the code session_superseded instead (tenant flag single_session_enforced), so the screen can say why it was signed out; a logout, an expired or an unknown token stay unauthorized.
json
{
"error": {
"code": "unauthorized",
"message": "invalid or revoked API key"
}
}429 — ApiKeyRateLimited
This API key is over its request quota, or the tenant's keys together are over the tenant ceiling (core#2503) (code rate_limited). The request was not processed. Wait Retry-After seconds and retry; a refusal is not counted against the budget, so that wait is always enough. Bounded per API key and, above the keys, per tenant (api_tenant_requests_per_minute): a sibling key is unaffected until the tenant's keys together reach that ceiling. The quota applies to every operation reachable with a dd_ key, so the operations that name a more specific 429 (call control, dispositions, SMS, …) can ALSO answer this one with its X-RateLimit-* headers.
json
{
"error": {
"code": "rate_limited",
"message": "API key request quota exceeded; retry after the interval in Retry-After"
}
}Create a reusable list
http
POST /v1/debt-listsA list is a named set of the tenant's debts. It is worked by ASSOCIATING it with campaigns (PUT /v1/debt-lists/{id}/campaigns/{campaign_id}), never by copying a debt: one debt is one account wherever it is worked. name is trimmed, must not be blank (400), is at most 200 characters, and is unique in the tenant (409).
Request body (JSON, required)
| Field | Type | Required | Description |
|---|---|---|---|
name | string | yes |
json
{
"name": "Cartera Q3"
}Responses
201
The new list (size 0, no campaigns).
| Field | Type | Required | Description |
|---|---|---|---|
debt_list | object (DebtList) | yes | A reusable list. size and campaign_ids are counted when the list is read, never stored. archived / archived_at are terminal. |
debt_list.id | string (uuid) | yes | |
debt_list.tenant_id | string (uuid) | no | |
debt_list.name | string | yes | |
debt_list.size | integer | yes | Members of the list now. |
debt_list.campaign_ids | array of string (uuid) | yes | The campaigns the list is associated with, oldest association first. |
debt_list.archived | boolean | yes | |
debt_list.archived_at | string,null (date-time) | no | |
debt_list.created_at | string (date-time) | no | |
debt_list.updated_at | string (date-time) | no |
400 — BadRequest
Malformed request (shape/type errors, invalid filters, bad timestamps).
json
{
"error": {
"code": "bad_request",
"message": "from must be RFC-3339"
}
}401 — Unauthorized
Missing, unknown or revoked API key (or, on /v1/admin/*, a bad/unset admin token; on webhooks, a bad/missing signature). A dashboard session (ddu_) that a newer login of the same person ended answers the code session_superseded instead (tenant flag single_session_enforced), so the screen can say why it was signed out; a logout, an expired or an unknown token stay unauthorized.
json
{
"error": {
"code": "unauthorized",
"message": "invalid or revoked API key"
}
}409 — Conflict
Invalid state transition, uniqueness conflict, or a seat that is busy/reserved.
json
{
"error": {
"code": "conflict",
"message": "invalid state transition"
}
}429 — ApiKeyRateLimited
This API key is over its request quota, or the tenant's keys together are over the tenant ceiling (core#2503) (code rate_limited). The request was not processed. Wait Retry-After seconds and retry; a refusal is not counted against the budget, so that wait is always enough. Bounded per API key and, above the keys, per tenant (api_tenant_requests_per_minute): a sibling key is unaffected until the tenant's keys together reach that ceiling. The quota applies to every operation reachable with a dd_ key, so the operations that name a more specific 429 (call control, dispositions, SMS, …) can ALSO answer this one with its X-RateLimit-* headers.
json
{
"error": {
"code": "rate_limited",
"message": "API key request quota exceeded; retry after the interval in Retry-After"
}
}One reusable list
http
GET /v1/debt-lists/{id}Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string (uuid) | yes | Resource UUID. Malformed or cross-tenant ids read as 404. |
Responses
200
The list, with its size and campaigns counted on read.
| Field | Type | Required | Description |
|---|---|---|---|
debt_list | object (DebtList) | yes | A reusable list. size and campaign_ids are counted when the list is read, never stored. archived / archived_at are terminal. |
debt_list.id | string (uuid) | yes | |
debt_list.tenant_id | string (uuid) | no | |
debt_list.name | string | yes | |
debt_list.size | integer | yes | Members of the list now. |
debt_list.campaign_ids | array of string (uuid) | yes | The campaigns the list is associated with, oldest association first. |
debt_list.archived | boolean | yes | |
debt_list.archived_at | string,null (date-time) | no | |
debt_list.created_at | string (date-time) | no | |
debt_list.updated_at | string (date-time) | no |
401 — Unauthorized
Missing, unknown or revoked API key (or, on /v1/admin/*, a bad/unset admin token; on webhooks, a bad/missing signature). A dashboard session (ddu_) that a newer login of the same person ended answers the code session_superseded instead (tenant flag single_session_enforced), so the screen can say why it was signed out; a logout, an expired or an unknown token stay unauthorized.
json
{
"error": {
"code": "unauthorized",
"message": "invalid or revoked API key"
}
}404 — NotFound
Unknown id, malformed (non-UUID) id OR another tenant's id — RLS returns zero rows, so all three are indistinguishable by design (no existence leak, never a 403 for foreign ids).
json
{
"error": {
"code": "not_found",
"message": "resource not found for this tenant"
}
}429 — ApiKeyRateLimited
This API key is over its request quota, or the tenant's keys together are over the tenant ceiling (core#2503) (code rate_limited). The request was not processed. Wait Retry-After seconds and retry; a refusal is not counted against the budget, so that wait is always enough. Bounded per API key and, above the keys, per tenant (api_tenant_requests_per_minute): a sibling key is unaffected until the tenant's keys together reach that ceiling. The quota applies to every operation reachable with a dd_ key, so the operations that name a more specific 429 (call control, dispositions, SMS, …) can ALSO answer this one with its X-RateLimit-* headers.
json
{
"error": {
"code": "rate_limited",
"message": "API key request quota exceeded; retry after the interval in Retry-After"
}
}Archive a list (terminal, never a delete)
http
POST /v1/debt-lists/{id}/archiveA list is evidence of whom a campaign worked, so it is never deleted: it is archived (archived_at, terminal, append-only). GET /v1/debt-lists stops returning it unless include_archived=true; GET /v1/debt-lists/{id} still reads it, with archived: true. Its members and its past associations stay. An archived list admits no new members (list import, segment snapshot) and cannot be associated with a campaign: 409. IDEMPOTENT: archiving an archived list answers it unchanged. 409 while the list is associated with a campaign that is not archived (fail-closed: dissociate it or archive the campaign first). 404 when the list is not the tenant's. No body.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string (uuid) | yes | Resource UUID. Malformed or cross-tenant ids read as 404. |
Responses
200
The list, now archived.
| Field | Type | Required | Description |
|---|---|---|---|
debt_list | object (DebtList) | yes | A reusable list. size and campaign_ids are counted when the list is read, never stored. archived / archived_at are terminal. |
debt_list.id | string (uuid) | yes | |
debt_list.tenant_id | string (uuid) | no | |
debt_list.name | string | yes | |
debt_list.size | integer | yes | Members of the list now. |
debt_list.campaign_ids | array of string (uuid) | yes | The campaigns the list is associated with, oldest association first. |
debt_list.archived | boolean | yes | |
debt_list.archived_at | string,null (date-time) | no | |
debt_list.created_at | string (date-time) | no | |
debt_list.updated_at | string (date-time) | no |
401 — Unauthorized
Missing, unknown or revoked API key (or, on /v1/admin/*, a bad/unset admin token; on webhooks, a bad/missing signature). A dashboard session (ddu_) that a newer login of the same person ended answers the code session_superseded instead (tenant flag single_session_enforced), so the screen can say why it was signed out; a logout, an expired or an unknown token stay unauthorized.
json
{
"error": {
"code": "unauthorized",
"message": "invalid or revoked API key"
}
}404 — NotFound
Unknown id, malformed (non-UUID) id OR another tenant's id — RLS returns zero rows, so all three are indistinguishable by design (no existence leak, never a 403 for foreign ids).
json
{
"error": {
"code": "not_found",
"message": "resource not found for this tenant"
}
}409 — Conflict
Invalid state transition, uniqueness conflict, or a seat that is busy/reserved.
json
{
"error": {
"code": "conflict",
"message": "invalid state transition"
}
}429 — ApiKeyRateLimited
This API key is over its request quota, or the tenant's keys together are over the tenant ceiling (core#2503) (code rate_limited). The request was not processed. Wait Retry-After seconds and retry; a refusal is not counted against the budget, so that wait is always enough. Bounded per API key and, above the keys, per tenant (api_tenant_requests_per_minute): a sibling key is unaffected until the tenant's keys together reach that ceiling. The quota applies to every operation reachable with a dd_ key, so the operations that name a more specific 429 (call control, dispositions, SMS, …) can ALSO answer this one with its X-RateLimit-* headers.
json
{
"error": {
"code": "rate_limited",
"message": "API key request quota exceeded; retry after the interval in Retry-After"
}
}Bulk import INTO a list (the pipeline of /v1/debts/import)
http
POST /v1/debt-lists/{id}/importThe SAME body, caps, per-item fail-soft answer and item rules as POST /v1/debts/import — debts upsert on external_ref, contacts on phone_e164, the contact fields are additive and tri-state, a null fails the item — plus two rules of its own:
- each debt that imports enters the list, as the LAST step of its item: an item that fails at any step is not added (its debt may already be written, exactly as on
/v1/debts/import, but it enters no campaign through this list). Adding is idempotent: replaying the payload adds nothing twice. No import ever takes a debt out of a list or a campaign;DELETE /v1/debt-lists/{id}/members/{debt_id}is the verb for that. - an item carrying
campaign_id— any value,nullincluded — FAILS that item, before anything is written for it. The campaigns of a list's debts are the list's associations; the directcampaign_idof a debt is set on/v1/debts/import.
Every member of the list — imported now or later — is in the population of every campaign the list is associated with. 404 when the list is not the tenant's: nothing is written, not even the debts.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string (uuid) | yes | Resource UUID. Malformed or cross-tenant ids read as 404. |
mapping_id | query | string (uuid) | no | The import-mapping template the file was mapped with. It is resolved BEFORE anything is imported (404 when it is not the tenant's, 409 when it is archived: nothing is written) and the version used is recorded after the import (GET /v1/import-mappings/{id}/uses). On the multipart form of POST /v1/debts/import the template replaces the mapping and separator parts. Absent, the import is unchanged. |
mapping_version | query | integer | no | The version of mapping_id to record. Default the current one. Needs mapping_id. |
import_id | query | string (uuid) | no | Names ONE import that the client sends in several requests (chunks). The uses those requests leave read back in GET /v1/import-mappings/{id}/uses as a single use — counts added up, id = this import_id — as long as they share the version and the list. Needs mapping_id (400 otherwise, and for a value that is not a UUID). Absent, each request is its own use. |
Request body (JSON, required)
| Field | Type | Required | Description |
|---|---|---|---|
debts | array of DebtImportItem | yes | |
debts[].external_ref | string | yes | THE Reg F counter key (upsert key). |
debts[].consumer_ref | string | yes | |
debts[].debt_type | other · student_loan | no | |
debts[].account_number | string | no | |
debts[].debtor_name | string | null | no | Optional full name of the debtor, free text. Whitespace is collapsed and trimmed; a blank value is stored as no name. More than 200 characters fails the ITEM (row error debtor_name should be at most 200 character(s)), not the batch. Encrypted at rest, not searchable, and never sent to analytics, webhooks or logs. Like the other debt fields it is a snapshot: a re-import that omits it clears the stored name. |
debts[].amount_cents | integer | no | |
debts[].currency | string | no | |
debts[].state | open · in_collection · promise_to_pay · paid · settled · disputed · closed | no | |
debts[].campaign_id | string (uuid) | no | |
debts[].prior_attempts_7d | integer | no | How many calls about THIS debt the client already placed from its previous system in the last 7 days. Without it the Reg F 7-in-7 counter starts BLIND on a migrated portfolio — it only ever saw the attempts this platform placed — and a book of business moved mid-cycle can burn the legal cap in its first week. Materialized as attempt rows the gate already reads, so the gate itself is unchanged. It is a TOTAL for the window, not a delta: re-sending the same payload adds nothing, and re-sending a SMALLER number never retracts history (the count only goes up, like last_conversation_at). Values above 7 are stored as 7 — 7 already saturates every frequency cap the engine has, so the verdict is identical and the cap only bounds the write. A value that is not an integer >= 0 fails THAT item, like any other bad field on this route; omitting it changes nothing. |
debts[].last_conversation_at | string (date-time) | no | When the client last spoke with the debtor, from its previous system (RFC 3339 with a UTC offset). Without it the Reg F 7-day post-contact cooldown (12 CFR 1006.14(b)(2)(i)(B)) starts BLIND on a migrated portfolio: the platform only knows the conversations it recorded, and could call again inside the window. Materialized as a conversations row with source: cms_import, which the gate already reads, so the gate itself is unchanged; the debt's last_conversation_at anchor advances to it. Re-sending the same payload adds nothing, and an OLDER date never moves the anchor back. Reports that measure platform activity (penetration, abandonment rate, the attempts export) do not count it. A malformed value, one without a UTC offset, or one in the future fails THAT item (a future date would hold the cooldown open past its window); omitting it changes nothing. |
debts[].delinquent_since | string (date) | no | The calendar date the debt went delinquent (YYYY-MM-DD), from the client's previous system. It is the debt's age: segmentation filters such as "delinquent for 31 to 90 days" are computed against it. A snapshot, like debtor_name: re-importing the debt without it clears it. A debt with no date never matches an age filter. Anything that is not a real calendar date in that exact form (null, an empty string, a number, a timestamp, 2026-02-30) or a date in the future fails THAT item; omitting it is fine. In a CSV import a blank cell is the same as omitting the field. |
debts[].contacts | array of object | no | |
debts[].contacts[].phone_e164 | string | yes | E.164 (upsert key within the debt). |
debts[].contacts[].line_type | mobile · landline · voip · unknown | no | |
debts[].contacts[].timezone | string | no | IANA. |
debts[].contacts[].us_state | string | no | |
debts[].contacts[].city | string | no | |
debts[].contacts[].postal_code | string | no | ZIP of the consumer. It is what resolves the NYC borough for the per-debt cap: without it a NY contact cannot be ruled OUT of the city and gets the strict cap. Refreshed on re-import only when the payload carries it — omitting it, or sending it as "", never clears a stored value, and sending it as null fails the item. Same rule as consumer_ref, timezone, us_state and city: the contract is one, even though this column is the only nullable one of the six. |
debts[].contacts[].is_primary | boolean | no | Asserts THE primary contact of the debt. On import, true demotes the previous primary (kept, never deleted) and promotes this one; omitted or false leaves the existing primary untouched. More than one per item fails the item. |
debts[].contacts[].consumer_ref | string | no |
json
{
"debts": [
{
"external_ref": "cms-debt-0002",
"consumer_ref": "cms-consumer-9002",
"amount_cents": 48000,
"currency": "USD",
"contacts": [
{
"phone_e164": "+13125551002",
"line_type": "landline",
"timezone": "America/Chicago",
"is_primary": true
}
]
}
]
}Responses
200
Import outcome (per-item fail-soft).
| Field | Type | Required | Description |
|---|---|---|---|
imported | integer | yes | |
failed | array of object | yes | |
failed[].index | integer | no | |
failed[].error | string | no | |
mapping | object | no | Present only when the request named mapping_id: the template version recorded as used. |
mapping.id | string (uuid) | yes | |
mapping.version | integer | yes |
json
{
"imported": 1,
"failed": []
}400 — BadRequest
Malformed request (shape/type errors, invalid filters, bad timestamps).
json
{
"error": {
"code": "bad_request",
"message": "from must be RFC-3339"
}
}401 — Unauthorized
Missing, unknown or revoked API key (or, on /v1/admin/*, a bad/unset admin token; on webhooks, a bad/missing signature). A dashboard session (ddu_) that a newer login of the same person ended answers the code session_superseded instead (tenant flag single_session_enforced), so the screen can say why it was signed out; a logout, an expired or an unknown token stay unauthorized.
json
{
"error": {
"code": "unauthorized",
"message": "invalid or revoked API key"
}
}404 — NotFound
Unknown id, malformed (non-UUID) id OR another tenant's id — RLS returns zero rows, so all three are indistinguishable by design (no existence leak, never a 403 for foreign ids).
json
{
"error": {
"code": "not_found",
"message": "resource not found for this tenant"
}
}409 — Conflict
Invalid state transition, uniqueness conflict, or a seat that is busy/reserved.
json
{
"error": {
"code": "conflict",
"message": "invalid state transition"
}
}429 — ImportThrottled
The import was refused BEFORE any row was read (core#2505). Two codes, both with Retry-After. import_in_progress: the tenant already has its maximum of imports running (2 by default, per node); retry when one finishes. rate_limited: more than 10 imports in the fixed window (by default) for the tenant, which is a bound per tenant and not per key because the connection pool an import drains is the cell's. Applies to POST /v1/debts/import and POST /v1/debt-lists/{id}/import alike. The per-key request quota can still answer rate_limited first.
in_flight
json
{
"error": {
"code": "import_in_progress",
"message": "an import of this tenant is still running; retry when it finishes"
}
}window
json
{
"error": {
"code": "rate_limited",
"message": "import rate limit exceeded; retry later"
}
}Read the members of a list
http
GET /v1/debt-lists/{id}/membersThe debts that are in the list, oldest entry first, paginated by an opaque keyset cursor: a page never repeats or loses a member. Each row carries debt_id, the client's external_ref and added_at — no phone and no debtor name. An archived list is still readable. 404 when the list is not the tenant's. Which import put a debt in is not recorded per member: an import is undone by reading its external_refs and removing them with DELETE /v1/debt-lists/{id}/members.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string (uuid) | yes | Resource UUID. Malformed or cross-tenant ids read as 404. |
limit | query | integer | no | Page size (a value over the ceiling is capped, not refused). |
cursor | query | string | no | Opaque keyset cursor — the next_cursor of the previous page. |
Responses
200
One page of members.
| Field | Type | Required | Description |
|---|---|---|---|
members | array of DebtListMember | yes | |
members[].debt_id | string (uuid) | yes | |
members[].external_ref | string | yes | |
members[].added_at | string (date-time) | yes | |
next_cursor | string | null | yes | Cursor of the next page; null on the last one. |
400 — BadRequest
Malformed request (shape/type errors, invalid filters, bad timestamps).
json
{
"error": {
"code": "bad_request",
"message": "from must be RFC-3339"
}
}401 — Unauthorized
Missing, unknown or revoked API key (or, on /v1/admin/*, a bad/unset admin token; on webhooks, a bad/missing signature). A dashboard session (ddu_) that a newer login of the same person ended answers the code session_superseded instead (tenant flag single_session_enforced), so the screen can say why it was signed out; a logout, an expired or an unknown token stay unauthorized.
json
{
"error": {
"code": "unauthorized",
"message": "invalid or revoked API key"
}
}404 — NotFound
Unknown id, malformed (non-UUID) id OR another tenant's id — RLS returns zero rows, so all three are indistinguishable by design (no existence leak, never a 403 for foreign ids).
json
{
"error": {
"code": "not_found",
"message": "resource not found for this tenant"
}
}429 — ApiKeyRateLimited
This API key is over its request quota, or the tenant's keys together are over the tenant ceiling (core#2503) (code rate_limited). The request was not processed. Wait Retry-After seconds and retry; a refusal is not counted against the budget, so that wait is always enough. Bounded per API key and, above the keys, per tenant (api_tenant_requests_per_minute): a sibling key is unaffected until the tenant's keys together reach that ceiling. The quota applies to every operation reachable with a dd_ key, so the operations that name a more specific 429 (call control, dispositions, SMS, …) can ALSO answer this one with its X-RateLimit-* headers.
json
{
"error": {
"code": "rate_limited",
"message": "API key request quota exceeded; retry after the interval in Retry-After"
}
}Take up to 500 debts out of a list in one call
http
DELETE /v1/debt-lists/{id}/membersThe batch twin of DELETE /v1/debt-lists/{id}/members/{debt_id}, with the same guarantee: the debts are untouched, they only leave the list and so the population of the campaigns that had them through it (unless they are in them another way: their own campaign_id, or another list). IDEMPOTENT: an id that is not a member removes nothing and is not an error; the answer counts the ones that LEFT. All-or-nothing on shape: 400 when debt_ids is empty, has more than 500 or carries a value that is not a UUID, and then nothing leaves. 404 only when the list is not the tenant's. The body travels on a DELETE.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string (uuid) | yes | Resource UUID. Malformed or cross-tenant ids read as 404. |
Request body (JSON, required)
| Field | Type | Required | Description |
|---|---|---|---|
debt_ids | array of string (uuid) | yes |
json
{
"debt_ids": [
"6f1d3b0e-4c1a-4a52-9a43-0a3f6c1d9e11"
]
}Responses
200
How many debts left the list.
| Field | Type | Required | Description |
|---|---|---|---|
removed | integer | yes |
400 — BadRequest
Malformed request (shape/type errors, invalid filters, bad timestamps).
json
{
"error": {
"code": "bad_request",
"message": "from must be RFC-3339"
}
}401 — Unauthorized
Missing, unknown or revoked API key (or, on /v1/admin/*, a bad/unset admin token; on webhooks, a bad/missing signature). A dashboard session (ddu_) that a newer login of the same person ended answers the code session_superseded instead (tenant flag single_session_enforced), so the screen can say why it was signed out; a logout, an expired or an unknown token stay unauthorized.
json
{
"error": {
"code": "unauthorized",
"message": "invalid or revoked API key"
}
}404 — NotFound
Unknown id, malformed (non-UUID) id OR another tenant's id — RLS returns zero rows, so all three are indistinguishable by design (no existence leak, never a 403 for foreign ids).
json
{
"error": {
"code": "not_found",
"message": "resource not found for this tenant"
}
}429 — ApiKeyRateLimited
This API key is over its request quota, or the tenant's keys together are over the tenant ceiling (core#2503) (code rate_limited). The request was not processed. Wait Retry-After seconds and retry; a refusal is not counted against the budget, so that wait is always enough. Bounded per API key and, above the keys, per tenant (api_tenant_requests_per_minute): a sibling key is unaffected until the tenant's keys together reach that ceiling. The quota applies to every operation reachable with a dd_ key, so the operations that name a more specific 429 (call control, dispositions, SMS, …) can ALSO answer this one with its X-RateLimit-* headers.
json
{
"error": {
"code": "rate_limited",
"message": "API key request quota exceeded; retry after the interval in Retry-After"
}
}Take one debt out of a list
http
DELETE /v1/debt-lists/{id}/members/{debt_id}The debt leaves the list, and with it the population of the campaigns the list is associated with (unless it is in them another way: its own campaign_id, or another list). The debt itself is untouched. 404 when it was not a member of the tenant's list.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string (uuid) | yes | Resource UUID. Malformed or cross-tenant ids read as 404. |
debt_id | path | string (uuid) | yes | The member debt's UUID. Malformed or cross-tenant ids read as 404. |
Responses
204
The debt is no longer a member.
401 — Unauthorized
Missing, unknown or revoked API key (or, on /v1/admin/*, a bad/unset admin token; on webhooks, a bad/missing signature). A dashboard session (ddu_) that a newer login of the same person ended answers the code session_superseded instead (tenant flag single_session_enforced), so the screen can say why it was signed out; a logout, an expired or an unknown token stay unauthorized.
json
{
"error": {
"code": "unauthorized",
"message": "invalid or revoked API key"
}
}404 — NotFound
Unknown id, malformed (non-UUID) id OR another tenant's id — RLS returns zero rows, so all three are indistinguishable by design (no existence leak, never a 403 for foreign ids).
json
{
"error": {
"code": "not_found",
"message": "resource not found for this tenant"
}
}429 — ApiKeyRateLimited
This API key is over its request quota, or the tenant's keys together are over the tenant ceiling (core#2503) (code rate_limited). The request was not processed. Wait Retry-After seconds and retry; a refusal is not counted against the budget, so that wait is always enough. Bounded per API key and, above the keys, per tenant (api_tenant_requests_per_minute): a sibling key is unaffected until the tenant's keys together reach that ceiling. The quota applies to every operation reachable with a dd_ key, so the operations that name a more specific 429 (call control, dispositions, SMS, …) can ALSO answer this one with its X-RateLimit-* headers.
json
{
"error": {
"code": "rate_limited",
"message": "API key request quota exceeded; retry after the interval in Retry-After"
}
}Associate a list with a campaign
http
PUT /v1/debt-lists/{id}/campaigns/{campaign_id}Every member of the list — the ones there now and the ones imported later — enters the campaign's population from its next read (the next tick of its runner). A debt can therefore be in several campaigns at once: each works it with its own marks, while the Reg F 7-in-7 counter, the park of a disposition rule and the debt's state stay per debt and cross campaigns, so a second campaign never adds calls beyond the cap. IDEMPOTENT: a second PUT changes nothing and answers the same. 404 when the list or the campaign is not the tenant's; 409 when the list is archived. No body.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string (uuid) | yes | Resource UUID. Malformed or cross-tenant ids read as 404. |
campaign_id | path | string (uuid) | yes | The campaign's UUID. Malformed or cross-tenant ids read as 404. |
Responses
200
The list, with its campaigns.
| Field | Type | Required | Description |
|---|---|---|---|
debt_list | object (DebtList) | yes | A reusable list. size and campaign_ids are counted when the list is read, never stored. archived / archived_at are terminal. |
debt_list.id | string (uuid) | yes | |
debt_list.tenant_id | string (uuid) | no | |
debt_list.name | string | yes | |
debt_list.size | integer | yes | Members of the list now. |
debt_list.campaign_ids | array of string (uuid) | yes | The campaigns the list is associated with, oldest association first. |
debt_list.archived | boolean | yes | |
debt_list.archived_at | string,null (date-time) | no | |
debt_list.created_at | string (date-time) | no | |
debt_list.updated_at | string (date-time) | no |
401 — Unauthorized
Missing, unknown or revoked API key (or, on /v1/admin/*, a bad/unset admin token; on webhooks, a bad/missing signature). A dashboard session (ddu_) that a newer login of the same person ended answers the code session_superseded instead (tenant flag single_session_enforced), so the screen can say why it was signed out; a logout, an expired or an unknown token stay unauthorized.
json
{
"error": {
"code": "unauthorized",
"message": "invalid or revoked API key"
}
}404 — NotFound
Unknown id, malformed (non-UUID) id OR another tenant's id — RLS returns zero rows, so all three are indistinguishable by design (no existence leak, never a 403 for foreign ids).
json
{
"error": {
"code": "not_found",
"message": "resource not found for this tenant"
}
}409 — Conflict
Invalid state transition, uniqueness conflict, or a seat that is busy/reserved.
json
{
"error": {
"code": "conflict",
"message": "invalid state transition"
}
}429 — ApiKeyRateLimited
This API key is over its request quota, or the tenant's keys together are over the tenant ceiling (core#2503) (code rate_limited). The request was not processed. Wait Retry-After seconds and retry; a refusal is not counted against the budget, so that wait is always enough. Bounded per API key and, above the keys, per tenant (api_tenant_requests_per_minute): a sibling key is unaffected until the tenant's keys together reach that ceiling. The quota applies to every operation reachable with a dd_ key, so the operations that name a more specific 429 (call control, dispositions, SMS, …) can ALSO answer this one with its X-RateLimit-* headers.
json
{
"error": {
"code": "rate_limited",
"message": "API key request quota exceeded; retry after the interval in Retry-After"
}
}Dissociate a list from a campaign
http
DELETE /v1/debt-lists/{id}/campaigns/{campaign_id}The list's members leave the campaign's population from its next read (unless they are in it another way). The debts, their own campaign_id and the list are untouched. 404 when there was no such association in the tenant.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
id | path | string (uuid) | yes | Resource UUID. Malformed or cross-tenant ids read as 404. |
campaign_id | path | string (uuid) | yes | The campaign's UUID. Malformed or cross-tenant ids read as 404. |
Responses
204
The association is gone.
401 — Unauthorized
Missing, unknown or revoked API key (or, on /v1/admin/*, a bad/unset admin token; on webhooks, a bad/missing signature). A dashboard session (ddu_) that a newer login of the same person ended answers the code session_superseded instead (tenant flag single_session_enforced), so the screen can say why it was signed out; a logout, an expired or an unknown token stay unauthorized.
json
{
"error": {
"code": "unauthorized",
"message": "invalid or revoked API key"
}
}404 — NotFound
Unknown id, malformed (non-UUID) id OR another tenant's id — RLS returns zero rows, so all three are indistinguishable by design (no existence leak, never a 403 for foreign ids).
json
{
"error": {
"code": "not_found",
"message": "resource not found for this tenant"
}
}429 — ApiKeyRateLimited
This API key is over its request quota, or the tenant's keys together are over the tenant ceiling (core#2503) (code rate_limited). The request was not processed. Wait Retry-After seconds and retry; a refusal is not counted against the budget, so that wait is always enough. Bounded per API key and, above the keys, per tenant (api_tenant_requests_per_minute): a sibling key is unaffected until the tenant's keys together reach that ceiling. The quota applies to every operation reachable with a dd_ key, so the operations that name a more specific 429 (call control, dispositions, SMS, …) can ALSO answer this one with its X-RateLimit-* headers.
json
{
"error": {
"code": "rate_limited",
"message": "API key request quota exceeded; retry after the interval in Retry-After"
}
}