Skip to main content

Summary

We are tightening the required-field contract on POST /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 /customers
  • POST /customers/batch customer-create rows (same per-customer payload); scheduleMode: "enroll" rows use origination enrollment and are not affected by the required-field changes
The 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 of email 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:
The same request under the new contract — birthday and ssnLastFour added, test renamed to isTest, loanId moved into loanIds:
Note that 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 test to isTest. This is live: isTest is accepted and honored on POST /customers and on POST /customers/batch customer-create rows. Switch whenever you are ready, up to the removal date below.
  • Start sending birthday, ssnLastFour, and state on every request. All three are already accepted and stored today; they are simply not yet enforced.
  • Move a single loanId value into loanIds. loanIds is already honored; loanId is already ignored on this endpoint, so there is no behavior to lose.
Only after the change takes effect:
  • Dropping phoneNumber (relying on email alone) or dropping timezone (relying on zipCode alone). Both are unconditionally required today and omitting either returns a 400 until 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:
Values are compared after the usual boolean coercion, so "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.
After 25 October 2026, test is removed. Unknown fields are ignored on this endpoint, so a payload still sending test after that date will not error — it will create a production customer that you expected to be a test record. Complete the switch to isTest before the deadline.

The sync and enrollment window

The same rename and 25 October 2026 removal deadline apply to:
  • POST /customers/schedule/sync (both replace and enroll modes)
  • POST /customers/batch rows with scheduleMode: "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.
One behavior fix ships alongside this. In 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 return 400 Bad Request with the offending fields named, in the same shape used today:
For the at-least-one pairs, the message names the pair rather than a single field. A zipCode that cannot be resolved to a timezone also returns 400.

Questions

If any of these fields are not available in your system, or you need more time to adjust, contact your Finosu account manager so we can plan around it.