Summary
We are tightening the required-field contract onPOST /customers and POST /customers/batch
so that every customer we board arrives with the data needed to contact them lawfully and verify their
identity. Two redundant fields are being removed at the same time.
isTest is now accepted on customer-write requests. test remains accepted until
25 October 2026, when it is removed. Sending conflicting values is rejected; enrollment
flags cannot be null. In schedule-sync replace mode, omitting both flags now preserves an
existing customer’s test setting. See the migration windows below for endpoint-specific behavior.
The proposed required-field and loanId changes remain unenforced. We will notify partners
before introducing those changes.
Endpoints affected by the Create Customer required-field changes:
POST /customersPOST /customers/batchcustomer-create rows (same per-customer payload);scheduleMode: "enroll"rows use origination enrollment and are not affected by the required-field changes
test → isTest rename also affects POST /customers/schedule/sync (both modes) and
POST /customers/batch enrollment rows. All customer-write contracts remove test on
25 October 2026. See The sync and enrollment window.
PUT /customers/{id} and loan, call, text, and payment endpoints are not affected.
What is changing
id, taskType, streetAddress1, and city are unchanged. streetAddress2, doNotCall,
externalMetadata, and loanIds remain optional.
Why each change
At least one ofemail or phoneNumber. Every customer needs at least one channel we can reach
them on — a record with neither cannot be worked. Today phoneNumber is unconditionally required,
which turns away email-only accounts. Relaxing that while requiring one of the pair is both stricter
in effect and more flexible in practice.
At least one of timezone or zipCode. We may only place calls during hours permitted by the
borrower’s local time, so TCPA compliance depends on knowing where they are. Supplying either is
sufficient: when timezone is omitted we derive it from zipCode. If the ZIP cannot be resolved to a
timezone, the request is rejected with 400 rather than silently defaulting — a wrong timezone means
calls placed outside legal hours.
state always required. State determines which lending and collections rules apply to the
account, and several states are restricted per-portfolio. We need it regardless of how the rest of the
record was populated.
birthday and ssnLastFour required. Both are used to verify a borrower’s identity before
discussing account details on a call. Without them, an agent cannot complete verification and the
conversation cannot proceed.
test → isTest. The API already returns isTest on every customer response, but accepts test
on the way in. Aligning the write field to the read field removes a needless asymmetry. This is the one
change on this page that is already live — see the section below.
loanId removed. On POST /customers this field is accepted and then ignored — it has never had
any effect. loanIds is the field that actually controls which loans are imported, so a single loan
ID belongs there as a one-element array.
Migrating your payload
A request that is valid today:birthday and ssnLastFour added, test renamed to
isTest, loanId moved into loanIds:
loanIds is an array of integers, not strings.
Because email/phoneNumber and timezone/zipCode are at-least-one pairs, a minimal payload only
needs one of each:
What you can do now vs. what to wait for
Read this section carefully if you plan to migrate early — some of the new fields are not yet wired up. Safe to do now:- Switch
testtoisTest. This is live:isTestis accepted and honored onPOST /customersand onPOST /customers/batchcustomer-create rows. Switch whenever you are ready, up to the removal date below. - Start sending
birthday,ssnLastFour, andstateon every request. All three are already accepted and stored today; they are simply not yet enforced. - Move a single
loanIdvalue intoloanIds.loanIdsis already honored;loanIdis already ignored on this endpoint, so there is no behavior to lose.
- Dropping
phoneNumber(relying onemailalone) or droppingtimezone(relying onzipCodealone). Both are unconditionally required today and omitting either returns a400until the new contract takes effect.
The test → isTest window
isTest is accepted on POST /customers and on POST /customers/batch customer-create rows as of
25 September 2026. test continues to work, unchanged, until 25 October 2026 — a one-month
window in which either field marks the customer as a test record, so you can migrate without
coordinating a cutover with us.
Sending both with different values is rejected rather than resolved in favor of one of them. The two
answers decide whether a real borrower is contacted at all, so we would rather fail the request
visibly than guess:
"test": "false" and "isTest": false
agree and are accepted.
On POST /customers/batch this is a per-row result, not a whole-batch rejection: the batch is still
queued and the offending row appears in the job’s failure list with this message, at index of the row
you submitted.
The sync and enrollment window
The same rename and 25 October 2026 removal deadline apply to:POST /customers/schedule/sync(bothreplaceandenrollmodes)POST /customers/batchrows withscheduleMode: "enroll"
isTest is accepted on all three as of 25 September 2026. test continues to work, unchanged,
until 25 October 2026, when it is removed. The field-acceptance and conflict rules match the create window above:
either field alone is honored, both together are honored while they agree, and a contradiction is
rejected without enrolling or syncing anything.
Where the rejection surfaces differs by endpoint, because each one already has its own error
contract:
Enrollment cannot fail quietly. Unlike
POST /customers, the enrollment schema rejects fields it
does not recognize. Before this window, sending isTest there was an error rather than a silent no-op, and
after 25 October 2026 sending test will be an error too — you will find out immediately, not by
discovering production customers you meant to mark as tests.replace mode on POST /customers/schedule/sync, omitting the flag now
leaves the customer’s existing value alone. Previously an omitted test was read as false and
silently un-marked an existing test customer; if you were sending test: true on every sync purely
to stop that happening, you no longer need to. Enrollment still defaults to false when both
fields are omitted, and that value must match an existing customer’s test setting.
Error behavior
Missing required fields return400 Bad Request with the offending fields named, in the same shape
used today:
zipCode that
cannot be resolved to a timezone also returns 400.