Skip to content

Debt lists ​

Generated from DialerDigital/core commit 0fc18008b3c8 — spec openapi/dialer-v1.yaml v0.134.0. Do not edit by hand; see How this reference is built. Download the OpenAPI spec.

Español

Reusable lists of debts, associated with campaigns (ADR #162).

Endpoints ​

MethodPathSummary
GET/v1/debt-listsThe tenant's reusable lists, with their size counted on read
POST/v1/debt-listsCreate a reusable list
GET/v1/debt-lists/{id}One reusable list
POST/v1/debt-lists/{id}/archiveArchive a list (terminal, never a delete)
POST/v1/debt-lists/{id}/importBulk import INTO a list (the pipeline of /v1/debts/import)
GET/v1/debt-lists/{id}/membersRead the members of a list
DELETE/v1/debt-lists/{id}/membersTake 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-lists

Every 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

NameInTypeRequiredDescription
include_archivedquerybooleannotrue also returns the archived lists. Anything else leaves them out.

Responses

200 ​

The lists.

FieldTypeRequiredDescription
debt_listsarray of DebtListyes
debt_lists[].idstring (uuid)yes
debt_lists[].tenant_idstring (uuid)no
debt_lists[].namestringyes
debt_lists[].sizeintegeryesMembers of the list now.
debt_lists[].campaign_idsarray of string (uuid)yesThe campaigns the list is associated with, oldest association first.
debt_lists[].archivedbooleanyes
debt_lists[].archived_atstring,null (date-time)no
debt_lists[].created_atstring (date-time)no
debt_lists[].updated_atstring (date-time)no
truncatedbooleanyestrue 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-lists

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

FieldTypeRequiredDescription
namestringyes
json
{
  "name": "Cartera Q3"
}

Responses

201 ​

The new list (size 0, no campaigns).

FieldTypeRequiredDescription
debt_listobject (DebtList)yesA reusable list. size and campaign_ids are counted when the list is read, never stored. archived / archived_at are terminal.
debt_list.idstring (uuid)yes
debt_list.tenant_idstring (uuid)no
debt_list.namestringyes
debt_list.sizeintegeryesMembers of the list now.
debt_list.campaign_idsarray of string (uuid)yesThe campaigns the list is associated with, oldest association first.
debt_list.archivedbooleanyes
debt_list.archived_atstring,null (date-time)no
debt_list.created_atstring (date-time)no
debt_list.updated_atstring (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

NameInTypeRequiredDescription
idpathstring (uuid)yesResource UUID. Malformed or cross-tenant ids read as 404.

Responses

200 ​

The list, with its size and campaigns counted on read.

FieldTypeRequiredDescription
debt_listobject (DebtList)yesA reusable list. size and campaign_ids are counted when the list is read, never stored. archived / archived_at are terminal.
debt_list.idstring (uuid)yes
debt_list.tenant_idstring (uuid)no
debt_list.namestringyes
debt_list.sizeintegeryesMembers of the list now.
debt_list.campaign_idsarray of string (uuid)yesThe campaigns the list is associated with, oldest association first.
debt_list.archivedbooleanyes
debt_list.archived_atstring,null (date-time)no
debt_list.created_atstring (date-time)no
debt_list.updated_atstring (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}/archive

A 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

NameInTypeRequiredDescription
idpathstring (uuid)yesResource UUID. Malformed or cross-tenant ids read as 404.

Responses

200 ​

The list, now archived.

FieldTypeRequiredDescription
debt_listobject (DebtList)yesA reusable list. size and campaign_ids are counted when the list is read, never stored. archived / archived_at are terminal.
debt_list.idstring (uuid)yes
debt_list.tenant_idstring (uuid)no
debt_list.namestringyes
debt_list.sizeintegeryesMembers of the list now.
debt_list.campaign_idsarray of string (uuid)yesThe campaigns the list is associated with, oldest association first.
debt_list.archivedbooleanyes
debt_list.archived_atstring,null (date-time)no
debt_list.created_atstring (date-time)no
debt_list.updated_atstring (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}/import

The 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, null included — FAILS that item, before anything is written for it. The campaigns of a list's debts are the list's associations; the direct campaign_id of 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

NameInTypeRequiredDescription
idpathstring (uuid)yesResource UUID. Malformed or cross-tenant ids read as 404.
mapping_idquerystring (uuid)noThe 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_versionqueryintegernoThe version of mapping_id to record. Default the current one. Needs mapping_id.
import_idquerystring (uuid)noNames 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)

FieldTypeRequiredDescription
debtsarray of DebtImportItemyes
debts[].external_refstringyesTHE Reg F counter key (upsert key).
debts[].consumer_refstringyes
debts[].debt_typeother · student_loanno
debts[].account_numberstringno
debts[].debtor_namestring | nullnoOptional 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_centsintegerno
debts[].currencystringno
debts[].stateopen · in_collection · promise_to_pay · paid · settled · disputed · closedno
debts[].campaign_idstring (uuid)no
debts[].prior_attempts_7dintegernoHow 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_atstring (date-time)noWhen 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_sincestring (date)noThe 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[].contactsarray of objectno
debts[].contacts[].phone_e164stringyesE.164 (upsert key within the debt).
debts[].contacts[].line_typemobile · landline · voip · unknownno
debts[].contacts[].timezonestringnoIANA.
debts[].contacts[].us_statestringno
debts[].contacts[].citystringno
debts[].contacts[].postal_codestringnoZIP 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_primarybooleannoAsserts 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_refstringno
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).

FieldTypeRequiredDescription
importedintegeryes
failedarray of objectyes
failed[].indexintegerno
failed[].errorstringno
mappingobjectnoPresent only when the request named mapping_id: the template version recorded as used.
mapping.idstring (uuid)yes
mapping.versionintegeryes
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}/members

The 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

NameInTypeRequiredDescription
idpathstring (uuid)yesResource UUID. Malformed or cross-tenant ids read as 404.
limitqueryintegernoPage size (a value over the ceiling is capped, not refused).
cursorquerystringnoOpaque keyset cursor — the next_cursor of the previous page.

Responses

200 ​

One page of members.

FieldTypeRequiredDescription
membersarray of DebtListMemberyes
members[].debt_idstring (uuid)yes
members[].external_refstringyes
members[].added_atstring (date-time)yes
next_cursorstring | nullyesCursor 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}/members

The 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

NameInTypeRequiredDescription
idpathstring (uuid)yesResource UUID. Malformed or cross-tenant ids read as 404.

Request body (JSON, required)

FieldTypeRequiredDescription
debt_idsarray of string (uuid)yes
json
{
  "debt_ids": [
    "6f1d3b0e-4c1a-4a52-9a43-0a3f6c1d9e11"
  ]
}

Responses

200 ​

How many debts left the list.

FieldTypeRequiredDescription
removedintegeryes

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

NameInTypeRequiredDescription
idpathstring (uuid)yesResource UUID. Malformed or cross-tenant ids read as 404.
debt_idpathstring (uuid)yesThe 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

NameInTypeRequiredDescription
idpathstring (uuid)yesResource UUID. Malformed or cross-tenant ids read as 404.
campaign_idpathstring (uuid)yesThe campaign's UUID. Malformed or cross-tenant ids read as 404.

Responses

200 ​

The list, with its campaigns.

FieldTypeRequiredDescription
debt_listobject (DebtList)yesA reusable list. size and campaign_ids are counted when the list is read, never stored. archived / archived_at are terminal.
debt_list.idstring (uuid)yes
debt_list.tenant_idstring (uuid)no
debt_list.namestringyes
debt_list.sizeintegeryesMembers of the list now.
debt_list.campaign_idsarray of string (uuid)yesThe campaigns the list is associated with, oldest association first.
debt_list.archivedbooleanyes
debt_list.archived_atstring,null (date-time)no
debt_list.created_atstring (date-time)no
debt_list.updated_atstring (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

NameInTypeRequiredDescription
idpathstring (uuid)yesResource UUID. Malformed or cross-tenant ids read as 404.
campaign_idpathstring (uuid)yesThe 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"
  }
}

Nothing in these docs is legal advice — always confirm compliance posture with your own counsel.