All notable changes to the Talli API are documented here.
Entries are tagged by API version: [V2] for Campaign Manager, [V3] for Distribution Manager, [Beneficiary] for the unversioned beneficiary API, [Public] for the anonymous /public/* endpoints.
[V3]POST /v3/distribution-manager/distribution-drafts/{id}/:promotenow carries each entry'sisDisabledanddisableReasonfrom the draft'spayoutMethodsonto the promoted distribution'spayoutMethods.[V3]POST /v3/distribution-manager/payout-instructions/{id}/:try-restorenow accepts a payout instruction whose selected payout method is aCheckTransfer, while the check is funded and waiting for the print run. The instruction moves toRestoredonce the funds are back in the distribution.[V3]POST /v3/distribution-manager/payout-instructions/{id}/:try-restorefor a check transfer already sent for printing, or whose funding has failed, is rejected with400.[V3]POST /v3/distribution-manager/templatesandPUT /v3/distribution-manager/templates/{id}reject a content field holding a{{tag:Key}}placeholder whoseKeycontains a character that is not allowed in a tag key, with400.
[V3]GET /v3/distribution-manager/payout-instructions/{id}/payout— a check-transfer payout now carriesrequestedOn, the moment the beneficiary submitted the check request.[V3]GET /v3/distribution-manager/payout-instructions/{id}/activitynow returns aCheckTransferRequestedactivity when a beneficiary requests a check payout, withCheck transfer requestedindescription.[V3]GET /v3/distribution-manager/simulate/emailslists the organization's most recent emails, newest first, withrecipient,templateType,deliveryStatus,payoutInstructionId,subjectandhtmlBody.limitis between 1 and 50 and defaults to 10; there is no paging.[V3]subjectandhtmlBodyarenullfor an email whose content is not available yet; poll again for an email sent seconds ago.
[V3]Thesimulateendpoints exist in the sandbox environment only; in production they return404.[V3]POST /v3/distribution-manager/simulate/bank-accounts/{id}/:receive-achfunds the bank account with a simulated incoming ACH deposit. It acceptsamountand an optionalidempotencyKey, returns202, and the account's available balance rises within seconds.[V3]POST /v3/distribution-manager/simulate/payout-instructions/{id}/:settle-achsettles the payout instruction's ACH transfer and returns202. The payout instruction reachesFulfilledwithin seconds; a payout with no settleable ACH transfer is rejected with409.
[V3]Icon uploads —POST /v3/distribution-manager/distributions,PATCH /v3/distribution-manager/distributions/{id}/distribution-iconand the distribution-draft equivalents — now accept PNG and JPEG only; GIF and WebP are rejected with400, same as any other unsupported type. This applies to file uploads only — an icon supplied asiconUrlon create or draft patch is not re-validated against the accepted set. Icon URLs stored before this change, including GIF/WebP ones, are unaffected and keep rendering.
[V3]The payout methodcategorygains the valueCheckTransfer: a payout instruction whose beneficiary requests a check moves fromPayoutMethodSelectedtoRequested.[V3]ACheckTransfermethod can be attached to a distribution only whileGET /v3/distribution-manager/payout-methodsreturns it for the caller.
[V3]A distribution can offer at most one check payout method, countingPaperCheckandCheckTransfertogether. A create, patch, promote or clone request naming more than one of them, on a distribution or a distribution draft, is rejected with400.
[V3]Tag keys on one payout instruction are now compared exact-case, so{"BusinessName": "John Doe", "Businessname": "EMEA"}is two tags instead of a400. This applies toPOST /v3/distribution-manager/payout-instructionsand toPATCH /v3/distribution-manager/payout-instructions/{id}with/Tags.[V3]The same comparison applies to thetag:-prefixed dynamic fields onPOST /v3/distribution-manager/templates/:send-preview:tag:BusinessNamealongsidetag:Businessnameis now accepted.[V3]Two tag keys that are equal after trimming are still rejected with400.[V3]POST /v3/distribution-manager/distributions/{id}/payout-instructions/:uploadis unchanged: a file with two tag columns whose names differ only by case is still rejected with400.
[V3]{{tag:Key}}placeholders in email template content now match the payout instruction's tag key exact-case, not ignoring case.{{tag:teamid}}no longer resolves aTeamIdtag; it renders astag:teamid, the same fallback used for an instruction that does not carry the tag at all. Tag-key uniqueness rules onPOST/PATCH /v3/distribution-manager/payout-instructions, file uploads, and thetag:-prefixed dynamic fields on:send-previeware unchanged — keys are still unique per instruction regardless of case.
[V3]PATCH /v3/distribution-manager/payout-instructions/{id}— an/amountincrease on aRestoredpayout is rejected synchronously with409INSUFFICIENT_DISTRIBUTION_BALANCEwhen the distribution's unallocated balance can't cover the difference.[V3]POST /v3/distribution-manager/payout-instructions/{id}/:reinitiate— on a "Cancelled" payout, is rejected synchronously with409INSUFFICIENT_DISTRIBUTION_BALANCEwhen the distribution's unallocated balance can't cover the payout amount.[V3]A payout instruction tag value can be at most 128 characters instead of 256. The limit applies everywhere tags are set:POST /v3/distribution-manager/payout-instructions,PATCH /v3/distribution-manager/payout-instructions/{id}with/Tags, file uploads viaPOST /v3/distribution-manager/distributions/{id}/payout-instructions/:upload(per cell), and thetag:-prefixed dynamic fields onPOST /v3/distribution-manager/templates/:send-preview. A longer value is rejected with400; an upload names the tag column and the rows. Values stored before this change keep their full length and are returned unchanged on reads and in exports, but a write that carries one is now rejected. The key limit of 64 characters and all other tag rules are unchanged.[V3]Tag keys can no longer contain&. The rule applies everywhere tags are set:POST /v3/distribution-manager/payout-instructions,PATCH /v3/distribution-manager/payout-instructions/{id}with/Tags, and file uploads viaPOST /v3/distribution-manager/distributions/{id}/payout-instructions/:upload; it also covers thetag:-prefixed dynamic fields onPOST /v3/distribution-manager/templates/:send-preview. A request carrying one is rejected with400, and an upload names the offending tag column. Tag values are unaffected and may still contain&. Tag keys already stored with&have been renamed, each&replaced with_, so a key returned byGET /v3/distribution-manager/payout-instructions/{id}may differ from the one originally sent. The existing key rules — no comma or double quote, none of< > { }, no upload file column name, unique regardless of case — and the length caps are unchanged.
[V3]A beneficiary who unsubscribes from emails no longer has their payout instructions cancelled. Previously every payout instruction with the same email in the distribution moved toCancelledwithcancellationReason: UnsubscribedFromEmails; now the instructions keep their status and stay claimable, and the platform stops emailing that beneficiary instead. Payout instructions are no longer cancelled with theUnsubscribedFromEmailsreason by the platform (the value remains valid on historical rows and as a manual cancellation reason). If the beneficiary resubscribes, emailing resumes without any further action.[V3]POST /v3/distribution-manager/payout-instructions/{id}/:initiate,.../:reinitiate, and.../:send-reminderreturn400when the beneficiary has unsubscribed from emails.:initiatewithchannel: Noneis not affected, since it sends no email.[V3]POST /v3/distribution-manager/distributions/{id}/payout-instructions/:initiate— count-based selection skips payout instructions whose beneficiary has unsubscribed. WhenpayoutInstructionIdsare supplied explicitly and any of them belongs to an unsubscribed beneficiary, the whole selection is rejected with409PAYOUT_INSTRUCTIONS_NOT_INITIABLE; the response now carries anunsubscribedIdslist alongsideunknownIdsandnotReadyIds, and nothing is initiated.[V3]POST /v3/distribution-manager/distributions/{id}/payout-instructions/:send-reminders— payout instructions whose beneficiary has unsubscribed are no longer eligible and are skipped by the selection, as they are by the automatic reminder schedule.[V3]A payout instruction restored after a failed payout stays in theRestoredstatus instead of being automatically re-initiated when its beneficiary has unsubscribed from emails.[V3]GET /v3/distribution-manager/payout-instructions/{id}/activitynow returns aPayoutInstructionEmailSubscriptionStatusUpdatedactivity when the beneficiary unsubscribes from emails or resubscribes —Beneficiary unsubscribed from emailsorBeneficiary resubscribed to emails, with the new status indetails.
[V3]Tag keys and values can no longer contain<,>,{or}. The rule applies everywhere tags are set:POST /v3/distribution-manager/payout-instructions,PATCH /v3/distribution-manager/payout-instructions/{id}with/Tags, and file uploads viaPOST /v3/distribution-manager/distributions/{id}/payout-instructions/:upload; it also covers thetag:-prefixed dynamic fields onPOST /v3/distribution-manager/templates/:send-preview. A request carrying one is rejected with400; a value violation names the tag. An upload names the tag column, with the rows for a value violation. The existing key rules — no comma or double quote, no upload file column name, unique regardless of case — and the length caps are unchanged.[V3]POST /v3/distribution-manager/distributions/{id}/payout-instructions/:upload— the six known column names are matched without regard to case or surrounding whitespace, so a column calledEMAILis now read asEmailrather than rejected. A file naming the same known column twice is rejected with400, as is a file with no header row. Column names and cell values are trimmed, so two of either that differ only by surrounding whitespace are now the same: two tag columns whose names differ only by padding are rejected as duplicates, a file whoseIdcells differ only by padding is rejected for a duplicate row, and a payout instruction stored earlier under a paddedIdis no longer matched by a re-upload of the same file — it gets a new row. A cell holding only whitespace now reads as empty, exactly as a cell left blank does: a row whoseAmountholds only whitespace imports without an amount instead of the file being rejected, and re-uploading such a row against an existing payout instruction clears its amount and returns it toDraft. Header problems come back under thefilekey, where the column-format error previously usedFile.[V3]GET /v3/distribution-manager/payout-instructions/{id}/activity— the entry for a tag change now reports the tags the change removed as<old value> → (removed)indetails, beside the tags that remain, and readsTags clearedwhen the change left none. Previously the entry carried only the new tag set, so clearing every tag produced an entry with an emptydetails. Entries recorded before this change carry no removals and render as they did.
[V3]Email template content now resolves{{tag:Key}}placeholders to the payout instruction's tag values. A placeholder works in any content field of the templates sent for a payout instruction — the initiation, reminder, push, reinitiation and payout-available emails — and may be mixed freely with the existing placeholders, for exampleTeam {{tag:TeamId}} for {{FirstName}}. Key matching ignores case, so{{tag:teamid}}resolves theTeamIdtag. A placeholder naming a tag the instruction does not carry renders as the key text itself,tag:TeamId, so the surrounding wording stays intact. For emails sent gradually — bulk initiation and bulk reminders — the values are those the instruction carried when the send was accepted; a tag edited afterwards does not change an email already queued.[V3]POST /v3/distribution-manager/templates/:send-previewaccepts sample values for the{{tag:Key}}placeholders as dynamic fields keyed with thetag:prefix —{"dynamicFields": {"FirstName": "John", "tag:TeamId": "42"}}. Such keys are exempt from the dynamic-field name check and instead follow the payout instruction tag rules (at most 20, key up to 64 characters, value up to 256, no comma or double quote, no duplicate keys ignoring case, and no key matching an upload-file column:Id,Amount,FirstName,LastName,Email,PhoneNumber); a violation is rejected with400. Dynamic fields without the prefix are validated against the template type as before. A placeholder with no matching key renders its fallback text, exactly as it would for an instruction lacking that tag.[V3]Email template schemas returned byGET /v3/distribution-manager/templates/template-type-schemasgain aDistributionNamedynamic field on theInitiateEmail,ReminderEmail,FailureReinitiateEmail, andPushEmailtypes. Template content may reference{{DistributionName}}; it renders as the distribution's name. The existingCampaignNamefield stays valid as a legacy alias of the same value — stored templates using{{CampaignName}}keep rendering unchanged, anddynamicFieldsonPOST /v3/distribution-manager/templates/:send-previewaccepts either key.
[V3]The default content values in the template type schemas (SubjectandParagraph1ofInitiateEmailandReminderEmail) now use the{{DistributionName}}placeholder instead of{{CampaignName}}. Templates created from these defaults render identically.[V3]TheCampaignLogocontent field is renamed toDistributionLogoacross the template type schemas, andPOST /v3/distribution-manager/templatesandPUT /v3/distribution-manager/templates/{id}expect the new key incontentFields.CampaignLogostays accepted as a legacy alias on create, update, and:send-preview— but sending both keys in one request is rejected with400. Templates stored with the old key are unaffected and keep rendering;GETresponses echo whichever key a template was saved with.
[V3]cancellationReasonon payout instruction responses gains the valueDirectAchRequestRejected. A direct ACH request that is accepted with202but cannot then be executed — the distribution does not cover the amount, or ACH is not available for it — resolves to a cancelled payout instruction carrying this reason. Such a request may be corrected and resubmitted with the sameexternalId; it continues on the same payout instruction, so the identifier from the original response stays valid and the resubmitted amount and payee details are the ones used.
[V3]GET /v3/distribution-manager/reports/distributions/{distributionId}/basic— requesting a different page, page size, or sort order for the same distribution within 2 minutes of an earlier request no longer returns that earlier request's cached page. Each combination of page, size, and sort now caches separately.[V3]GET /v3/distribution-manager/reports/distributions/{distributionId}/basic— rows now come back in a stable order: the requestedsortBycolumn, or creation date descending when none is given, with the payout instruction id breaking ties either way. The order was previously undefined when the sort column held duplicates, so consecutive pages could repeat or skip a row.
[V3]POST /v3/distribution-manager/distributions/{distributionId}/payout-instructions/:initiateaccepts a new optionalpayoutInstructionIdsarray, initiating exactly the payout instructions listed rather than a count or an upload batch. It cannot be combined withuploadBatchIdorcount(400). The selection is all-or-nothing: every id must belong to the distribution and be in theReadystatus, otherwise the request is rejected with409PAYOUT_INSTRUCTIONS_NOT_INITIABLE— listing the offending ids inunknownIdsandnotReadyIds— and nothing is initiated. The funds, daily email limit anddryRunbehaviour are unchanged. Existing callers are unaffected.[V3]Payout instruction responses gainisDirectPayout. It istruefor a payout requested directly by the distribution manager, which has no claim journey and never receives a notification. Reminders and reinitiation are rejected with400for such a payout.[V3]cancellationReasonon payout instruction responses gains the valueDirectAchFailed, reported when the bank transfer for a direct payout fails and the payout is cancelled. Existing values are unchanged.
[V3]Payout instructions now allow up to 20 tags instead of 10. The limit applies everywhere tags are set:POST /v3/distribution-manager/payout-instructions,PATCH /v3/distribution-manager/payout-instructions/{id}with/Tags, and file uploads viaPOST /v3/distribution-manager/distributions/{id}/payout-instructions/:upload(up to 20 tag columns). All other tag rules are unchanged.[V3]POST /v3/distribution-manager/distributions/{distributionId}/payout-instructions/:direct-achnow requires thepayoutInstructions:directPayoutpermission instead ofpayoutInstructions:initiate.[V3]POST /v3/distribution-manager/distributions/{distributionId}/payout-instructions/:direct-achnow rejects a repeatedexternalIdwith400. Previously a repeat returned202with the same payout instruction identifier; the request is now accepted only once perexternalIdwithin a distribution.
[V3]POST /v3/distribution-manager/bank-accounts/{id}/:withdrawnow requires thebankAccounts:withdrawpermission instead ofadmin:manage.
[V3]NewPOST /v3/distribution-manager/distributions/{distributionId}/payout-instructions/:direct-achendpoint requests a direct ACH payout to a vendor/payee supplied entirely by the distribution manager, with no beneficiary claim journey. The body carries the payout amount, the payee identity (name, email, phone, date of birth, address) and the destination bank details (routing/account number, account type,accountOwnerTypeofIndividualorBusiness, andbusinessNamefor a business account). On acceptance returns202with the identifier of the payout instruction the request will produce; that identifier is derived from the distribution andexternalId, so repeating a request yields the same one. Returns404if the distribution does not exist,400if the request is rejected — including when ACH is not enabled for the distribution — and429when the rate limit is exceeded.
[V3]Icon uploads —POST /v3/distribution-manager/distributions,PATCH /v3/distribution-manager/distributions/{id}/distribution-iconand the distribution-draft equivalents — accept PNG, JPEG, GIF and WebP only, and reject anything else with400. The returned icon URL no longer contains the uploaded filename; icon URLs stored before this change are unaffected.[Beneficiary]POST /beneficiary/request-otpno longer accepts thecurrentDateTimeStringquery parameter — the time shown in the one-time-code message is now set by Talli, in UTC (for example21 Jul 2026, 14:32 UTC). The parameter is ignored rather than rejected, so existing clients keep working.
[V3]otpThresholdAmountis no longer part of the distribution or distribution-draft API — it is gone from the create payloads, theGETresponses, and the pathsPATCHaccepts. It is now managed internally by Talli; contact support if you need a threshold configured.
[V3]GET /v3/distribution-manager/payout-instructions— theemailfilter no longer rejects terms containing%,_or\with400. Those characters are now matched literally as part of the address instead — for exampleemail=jo_hn@example.commatches only that exact address, not any address with a different character in place of the underscore.
[V3]GET /v3/distribution-manager/payout-instructionsaccepts optionaltagKeyandtagValuequery parameters:tagKeytogether withtagValuereturns the payout instructions carrying that exact tag pair, whiletagKeyalone returns the instructions carrying the key with any value. Matching is exact, including case; both parameters are trimmed and an empty value counts as absent.tagValuewithouttagKeyis rejected with400. The tag filter combines with the existingdistributionId,status,externalId,email, andbatchIdfilters.[V3]NewGET /v3/distribution-manager/tag-keysendpoint returns the distinct tag keys across the payout instructions visible to the caller, as a plain array of strings sorted alphabetically without regard to case —[]when no payout instruction carries a tag. Use it to offer tag-key suggestions before filtering the payout-instructions list.
[V3]GET /v3/distribution-manager/reports/distributions/{distributionId}/basic— the exported CSV gains one column per distinct tag key in the distribution, appended after the existing columns in alphabetical order (ignoring case). A row's cell holds its value for that key, or stays empty when the payout instruction does not carry the tag. Every page of one distribution carries the same tag columns, so paged exports stay concatenable. The existing columns are unchanged.
[V3]GET /v3/distribution-manager/payout-instructionsgains filters:statuses(repeatable or comma-separated — matches any of the listed statuses; each must be a defined status name, and an unknown or numeric value is rejected with400),distributionIds(repeatable or comma-separated — matches any of the listed distributions; each must be a valid guid),phoneNumber(exact match on the beneficiary's number, normalized first so common US formats such as+15551234567,15551234567, or(555) 123-4567all match; a number written in full international form — a leading+followed by its country code — matches too), andfromDate/toDate(inclusive range on the payout instruction's creation date,YYYY-MM-DD). Filters of different kinds combine with AND. Supplying both a singular filter and its plural form (statuswithstatuses, ordistributionIdwithdistributionIds) returns400, as does afromDatelater thantoDate. The singularstatusis now validated the same way asstatuses— a numeric or unknown value, or a comma-separated list, is rejected with400rather than silently reinterpreted.
[V3]PATCH /v3/distribution-manager/payout-instructions/{id}—/amountand/emailoperations are now accepted only in statusesDraft,Ready,Restored, andCancelled. To change amount or email on a payout that was already sent, cancel it, edit while cancelled, then reinitiate.[V3]PATCH /v3/distribution-manager/payout-instructions/{id}— an/emailchange inCancelledno longer auto-restores the payout.[V3]POST /v3/distribution-manager/payout-instructions/{id}/:reinitiate— Now accepts payout instructions in statusCancelled. A new allocation is created for the current amount, if the campaign has insufficient unallocated funds the reinitiation fails asynchronously and the payout stays in Cancelled.[V3]GET /v3/distribution-manager/payout-instructions/{id}/activity—Payout instruction updatedrows now include adetailsobject of{ fieldName: "old → new" }for each field that changed on the update (e.g.amount: "50 → 100",email: "old@x.com → new@y.com").
[V3]GET /v3/distribution-manager/payout-instructionsreturns payout instructions in a stable order: newest first. The order was previously undefined, so consecutive pages could repeat or skip a row.
[Beneficiary]POST /beneficiary/payout-instruction/ach-transferaccepts two new optional fields for paying into a business bank account:accountOwnerType(IndividualorBusiness; omitted ornullmeansIndividual, so existing requests are unaffected) andbusinessName. ForBusiness,businessNameis required and must be 1–100 characters, anddateOfBirthis no longer required — it is ignored if sent. ForIndividual,dateOfBirthremains required andbusinessNamemust be omitted. A request whoseaccountOwnerTypediffers from the owner type the same bank account was already registered with is rejected with400.
[V3]POST /v3/distribution-manager/payout-instructionsaccepts an optionaltagsobject of free-form key/value labels, e.g.{"TeamId": "Alpha"}. At most 10 tags per payout instruction; keys up to 64 characters and values up to 256, both trimmed; keys must be unique regardless of case, must not contain a comma or a double quote, and must not be one of the known upload file columns (Id,Amount,FirstName,LastName,Email,PhoneNumber); values must not be empty. A request that breaks any of these is rejected with400.[V3]GET /v3/distribution-manager/payout-instructionsandGET /v3/distribution-manager/payout-instructions/{id}returntagsfor each payout instruction — always present,{}when it has none.[V3]PATCH /v3/distribution-manager/payout-instructions/{id}accepts/Tagsas a patchable path, and accepts it in any status, including after the payout was requested or redeemed — the not-yet-requested restriction still applies to every other patchable property, and to a patch that mixes/Tagswith one of them. Tags are replaced as a whole set:replace /Tagswith{}clears them, aremoveoperation on/Tagsis rejected with400, and paths into a single tag such as/Tags/TeamIdstay unsupported. The tag limits above are enforced here too, with a400for violations.[V3]POST /v3/distribution-manager/distributions/{id}/payout-instructions/:uploadandGET /v3/distribution-manager/distributions/{id}/payout-instructions/upload-status/{batchId}both returndetectedTagColumns: the columns of the uploaded file that were imported as tags, in file order,[]for a file without them. Compare it with the columns you sent to catch a mistyped column name.[V3]GET /v3/distribution-manager/payout-instructions/{id}/activityincludes aTags updatedentry every time an instruction's tags change, carrying the new tag set indetails.
[V3]GET /v3/distribution-manager/payout-instructions— theemailfilter now accepts a partial address and matches from the beginning of it, soemail=johnreturns every beneficiary whose address starts withjohn. It matches from the start only:email=ohndoes not matchjohn@example.com. A full address still matches that beneficiary, and now additionally any address that starts with it, soemail=bob@gmail.comalso returnsbob@gmail.com.br.%,_and\are rejected with400.[Beneficiary]POST /beneficiary/payout-instruction/prepaid-card—addressLine2is now optional.[V3]POST /v3/distribution-manager/distributions/{id}/payout-instructions/:uploadno longer rejects a file whose header carries columns beyond the known ones with400"Unexpected columns format or invalid delimiter" — those columns are now imported as tags on every row. The known six columns are still all required. At most 10 tag columns; names up to 64 characters and values up to 256; names must be unique regardless of case and must not match a known column name in any casing (a column calledEMAILis rejected rather than read asEmail); an empty cell means the row carries no such tag. A file that breaks a rule is rejected with400under theTagskey, quoting the row numbers for over-long values. A file with tag columns is the desired tag set for every row it re-uploads, so an empty cell clears that tag; a file without tag columns leaves the stored tags untouched.
[V3]POST /v3/distribution-manager/reports/tax/export— the exported CSV gains seven columns, which shifts the position of most existing ones:Business NameafterName,Other ClassificationafterTax Classification, thenExempt Payee Code,FATCA Code,Account Numbers, andForeign Partners/Owners/BeneficiariesafterZip, andTIN TypeafterTIN.TINmoves from the ninth column to the fifteenth and each address column moves by two. Every existing column keeps its header label, so read the CSV by header rather than by column index. Rows with no business data leave the new cells empty and reportSsnas the TIN type.
[V3]NewGET /v3/distribution-manager/distributions/{id}/payout-instructions/upload-status/{batchId}endpoint returns exact progress for one upload batch (thebatchIdreturned by the upload endpoint):totalRows,processedRows,pendingRows, the per-outcomereadyRows/draftRows/skippedRows/failedRowscounts, andisComplete. Poll it after an upload instead of polling the payout-instructions list. Counts reflect each row's outcome at import time and can keep settling for a short window after a processing failure — a row can flip fromFailedtoReady— so do not treat the first complete snapshot as immutable. Returns404when the distribution or the upload batch is unknown, or the batch belongs to another distribution.[V3]NewGET /v3/distribution-manager/distributions/{id}/payout-instructions/upload-status/{batchId}/rowsendpoint lists the per-row outcomes of an upload batch, ordered by each row's line number in the uploaded file. Each row carriesrowNumber,externalId(the file'sIdcolumn value),outcome(Pending|Ready|Draft|Skipped|Failed),payoutInstructionId(the payout instruction the row created or updated —nullforPending,Skipped, andFailedrows), andprocessedAt. SupportspageNumber/pageSizepaging (defaults1/20;pageSizemust be 1–100, otherwise400) and an optionaloutcomefilter (e.g.outcome=Failed); an unrecognisedoutcomevalue is rejected with400. Wait until upload-status reportsisCompletebefore sweeping rows — outcomes settle while processing runs. ASkippedrow's live payout instruction can be found by filteringGET /v3/distribution-manager/payout-instructionson itsexternalId. Returns404under the same conditions as upload-status.
[V3]POST /v3/distribution-manager/distributions/{distributionId}/payout-instructions/:initiateand.../:send-reminders— acountthat exceeds the daily email send limit is no longer rejected with a400validation error.countis now purely an upper bound on how many payout instructions to select, and the daily-limit check is applied to the selected batch rather than tocountitself. The value is still range-checked — greater than0and, when supplied, at most25000, otherwise400; only the daily-limit ceiling oncountis gone. Two caller-visible consequences: (1) a request whosecountexceeds the limit but whose actual selection fits the remaining quota now succeeds —200for a dry run,202otherwise — where it previously failed with400; (2) a request whose selection would exceed the remaining quota returns409with error codeEMAIL_SEND_QUOTA_EXCEEDED, carryinglimit,used,requested, andresetsAt, whererequestedis the size of the selected batch, not the submittedcount. Every over-quota condition now produces one consistent, machine-readable response instead of two different shapes; the409surfaces on the dry run too.
[V3]GET /v3/distribution-manager/bank-accounts/{id}/withdrawal-accounts—routingNumberandaccountNumberare now masked to the last four characters (previously the last three), consistent with how account numbers are surfaced elsewhere.
[V3]POST /v3/distribution-manager/distributions/{distributionId}/payout-instructions/:initiateaccepts a new optionaluploadBatchId: when set, only Ready payout instructions created or updated by that upload batch (thebatchIdreturned by the upload endpoint) are selected; when omitted, all Ready instructions of the distribution remain eligible as before. The batch must belong to the distribution and have finished processing: an unknownuploadBatchIdis rejected with404, and a still-processing one with409and error codeUPLOAD_STILL_PROCESSING(carryingtotalRowsandprocessedRows) — poll the upload-status endpoint until it reports complete.
[V3]:initiate—countis now optional. When omitted, all Ready instructions in scope are selected, up to the 25,000-per-initiate maximum; if more are Ready, the request is rejected with409and error codeTOO_MANY_PAYOUT_INSTRUCTIONS_TO_INITIATE(carryingreadyCountandmaxBatchSize). The rejection surfaces on the dry run too, so nothing is initiated unknowingly. An explicitcountbehaves exactly as before.
[V3]:initiatenow selects oldest-first (longest-waiting instructions first), as its documentation always stated; the selection had actually been newest-first. Only observable when initiating fewer instructions than are Ready.
[V3]POST /v3/distribution-manager/distributionsandPOST /v3/distribution-manager/distribution-drafts/{id}/:promotenow reject with400anypayoutMethodIdsentry that is not among the payout methods available for the organization's default bank (the set returned byGET /v3/distribution-manager/payout-methods). Previously any active payout method was accepted.[V3]PATCH /v3/distribution-manager/distributions/{id}now rejects with400a patch that enables a payout method not available for that distribution's bank (the set returned byGET /v3/distribution-manager/payout-methods?distributionId={id}). Payout methods already enabled on the distribution are not re-checked, so unrelated edits to a distribution that already carries such a method still succeed.
[Beneficiary]POST /beneficiary/tax/w9— thecertifiedrequest field has been removed. It is no longer required and requests that omit it now succeed; requests that still send it are accepted (the field is ignored). A request without aUser-Agentheader is no longer rejected with400.[Beneficiary]GET /beneficiary/tax/w9— thecertifiedOnfield has been removed from thesubmissionobject.[V3]POST /v3/distribution-manager/reports/tax/export— theCertified Oncolumn has been removed from the exported CSV.
[V3]POST /v3/distribution-manager/distributionsandPOST /v3/distribution-manager/distribution-draftsaccept an optionaltaxCollectionModeform field (None|Standard), so tax collection can be configured at creation instead of requiring a follow-up PATCH. For distributions, omitting the field defaults toNone.
[V3]POST /v3/distribution-manager/distributions/{id}/payout-instructions/:uploadnow returns{ batchId }in its202 Acceptedbody. ThebatchIdidentifies the upload; use it to poll upload progress and to filter the payout-instructions list by upload (see below).[V3]GET /v3/distribution-manager/payout-instructionsaccepts a new optionalbatchIdquery parameter that filters the list to the payout instructions created by that upload batch.
[V3]POST /v3/distribution-manager/distributions/{id}/payout-instructions/:uploadnow rejects a file with more than 100,000 rows with a400; files at or below the limit are unaffected. Support for larger imports was intentionally dropped from the supported envelope.
[V3]POST /v3/distribution-manager/reports/tax/export— exports certified W-9 tax records as a password-protected ZIP. Request body:scope(Distribution|DateRange|Beneficiary),distributionId(required whenscopeisDistribution),from/to(required whenscopeisDateRange; exact UTC instants, inclusive, matched against the payout's fulfillment date),payoutInstructionId(required whenscopeisBeneficiary),includeNotFulfilledPayouts(optional, defaultfalse— whenfalse, payout instructions that were never fulfilled are excluded from theDistributionandBeneficiaryscopes even if their W-9 is completed; has no effect onDateRange, which is always keyed to fulfillment date), andzipPassword(required, minimum 12 characters with at least one uppercase letter, one lowercase letter, one digit, and one special character, used to encrypt the returned file — not stored by Talli). Returnsapplication/zipcontaining a CSV of the matching records. Returns400when a scope-specific field is missing/invalid orzipPassworddoesn't meet the length/complexity requirements, and404when the givendistributionIdorpayoutInstructionIddoes not exist.
[V3]GET /v3/distribution-manager/invitationsno longer returns expired invitations (those past theirexpiresAt);totalItemsand pagination now count only pending invitations. To re-invite someone whose invitation expired, create a new invitation viaPOST /v3/distribution-manager/invitations/bulk.
[Beneficiary]POST /beneficiary/tax/w9— submits the beneficiary's W-9 tax information in a single call. Request body:name(required),taxClassification(optional; onlyIndividualis accepted),tin(required, exactly 9 digits),address(line1,city,state,ziprequired;line2optional),certified(required, must betrue), andconsent({ electronicDelivery }). Returns202on success. Returns400when the mandatory set is incomplete,tinis not 9 digits,taxClassificationis notIndividual, orcertifiedis nottrue. Returns409withcodeTAX_ALREADY_SUBMITTEDif a W-9 has already been submitted for the beneficiary.
[V3]Distribution and draft responses now include ataxCollectionobject withmode(None|Standard) and nullableenabledOn. Existing distributions default to{ mode: "None", enabledOn: null }.[V3]PATCH /v3/distribution-manager/distributions/{id}andPATCH /v3/distribution-manager/distribution-drafts/{id}accept/taxCollection(e.g.[{ "op": "replace", "path": "/taxCollection", "value": { "mode": "Standard" } }]). Requiresdistributions:edit.enabledOnis server-stamped on firstNone → Standardand retained onStandard → None; unknown mode strings return400.[V3]POST /v3/distribution-manager/distribution-drafts/{id}/:promote— aStandarddraft yields a distribution created asStandardwithenabledOnstamped at promote time.
[V3]GET /v3/distribution-manager/payout-methodsnow returns only the payout methods available for the relevant bank and organization, rather than every active payout method. It accepts a new optionaldistributionIdquery parameter: pass it to scope the list to an existing distribution's bank; omit it to scope the list to the organization's default bank (e.g. when creating a distribution or editing a draft). Returns404when thedistributionIddoes not identify an existing distribution in your organization.
[Beneficiary]POST /beneficiary/payout-instructions/{id}/prepaid-cardnow requiresdateOfBirth(formatYYYY-MM-DD, must be a date in the past). Requests that omit it or supply today's/a future date are rejected with400.
[V3]Removed the instrument-read endpointsGET /v3/distribution-manager/ach-transfers/{id},/ach-transfers/?payoutInstructionId=,/paypal-transfers/{id},/paypal-transfers/?payoutInstructionId=,/venmo-transfers/{id},/venmo-transfers/?payoutInstructionId=,/gift-cards/{id},/gift-cards/?payoutInstructionId=,/prepaid-cards/{id},/prepaid-cards/?payoutInstructionId=. These endpoints now return404.
[V3]POST /v3/distribution-manager/distributions/{id}/:reassign-subclient— reassigns a distribution to a different sub-client. Request body: nullablesubClientId; provide a sub-client id to move the distribution to that sub-client, ornullto make it parent-only. The newsubClientIdcascades to every payout instruction of the distribution. Returns202on success. Returns400ifsubClientIdis provided but is not a sub-client in the caller's organization, and404if the distribution does not exist.
[V3]GET /v3/distribution-manager/users/{id}now returns the user object directly, withrolenested on it. The previous{ "user": { ... }, "role": { ... } }wrapper and its top-levelrolefield have been removed.
[V3]POST /v3/distribution-manager/distributions/{distributionId}/payout-instructions/:send-reminders— bulk send reminders for a distribution. Request body:count(required) and optionaldryRun(defaultfalse). Selects up tocountof the distribution's payout instructions that are still awaiting beneficiary action and have not been notified within the reminder interval, oldest reminder first, and sends each a reminder; the emails are queued and delivered gradually rather than all at once. On success returns202with{ dryRun, batchId, acceptedCount }—batchIdtags every email produced by the batch andacceptedCountis how many reminders were accepted. WithdryRun: truethe same selection and validation run but nothing is sent: returns200withbatchId: null.countmust be greater than0and not exceed the organization's daily email send limit, otherwise400. Returns409witherrorCodeNO_PAYOUT_INSTRUCTIONS_TO_REMIND(no eligible recipients) orEMAIL_SEND_QUOTA_EXCEEDED(the batch would exceed the organization's daily email send limit — the response includeslimit,used,requested, andresetsAt). Returns404if the distribution does not exist and429when the per-organization rate limit is exceeded.
[V3]Distribution responses (GET /v3/distribution-manager/distributionsand.../{id}) now include a read-only, nullablesubClientIdidentifying the owning sub-client;nullfor parent-only distributions.[V3]POST /v3/distribution-manager/distributionsaccepts an optionalsubClientId(the owning sub-client); omitting it leaves the distribution parent-only. The field is not patchable viaPATCH /v3/distribution-manager/distributions/{id}.[V3]Payout instruction responses now include a read-only, nullablesubClientId, inherited from the owning distribution.[V3]Distribution drafts now carry an optionalsubClientId: settable onPOST /v3/distribution-manager/distribution-drafts, patchable viaPATCH, returned on GET, and inherited by the distribution (and its payout instructions) onPOST .../distribution-drafts/{id}/:promote. Drafts are sub-client read-scoped: a sub-client viewer sees only their own drafts; unassigned drafts are staff-only.
[V3]Sub-client management:GET /v3/distribution-manager/subclients(list, with member counts),POST /v3/distribution-manager/subclients(create,201),PATCH /v3/distribution-manager/subclients/{id}.PATCH /v3/distribution-manager/users/{id}/subclientassigns a user to a sub-client or detaches them whensubClientIdis omitted/null.[V3]CreateInvitationRequestaccepts an optionalsubClientId; when set, the invitee is provisioned as asub-client-viewerof that sub-client.UserResponseandInvitationResponsenow include a nullable nestedsubClient({ id, name }) — null for non-sub-client invitees.[V3]The user object returned byGET /v3/distribution-manager/usersandGET /v3/distribution-manager/users/{id}now includesrole— the user's role as{ id, name }. It is always present (an active user is expected to have exactly one role) and is now returned on the list as well, not only when fetching a single user by id.
[V3]GET /v3/distribution-manager/distributions/{id}/account-detailsnow resolves the distribution through the organization-scoped (filter-respecting) read path. A distribution belonging to another organization now returns404instead of being read by id regardless of tenant. The two distinct404messages ("distribution not found" vs "bank account not found") were also unified into a single message so the response cannot be used as a cross-organization GUID-existence oracle. (TALLI-2834)[V3]POST /v3/distribution-manager/distributions/{id}/:refund(deprecated) now resolves the distribution through the organization-scoped read path; a distribution belonging to another organization returns404instead of being read by id regardless of tenant. (TALLI-2834)
[V3]BankAccountResponsenow includesacceptsNewDistributions(boolean).trueonly when the bank account is on the organization's default bank partner AND has statusActive;falsefor run-off accounts on a former partner or accounts still being provisioned. Consumers should use this flag (notstatus) when deciding whether an account is eligible for new distribution creation. The listing endpointGET /v3/distribution-manager/bank-accountsstill returns every account regardless of provider, so references to run-off accounts remain resolvable.
[V3]POST /v3/distribution-manager/distributionsandPOST /v3/distribution-manager/distribution-drafts/{id}/:promotenow return400 ValidationProblem(keyed onbankAccountId) when the chosen bank account is not on the organization's default bank partner. Existing distributions referencing a former partner's bank account continue to function — only new distribution creation is gated. DraftPOSTandPATCHendpoints intentionally do not gate on the partner match; the check fires at promote.
[V3]POST /v3/distribution-manager/distributions/{distributionId}/payout-instructions/:initiate— bulk initiate payout instructions. Takes acountand initiates up to that many of the distribution'sReadypayout instructions (oldest first) as a single batch; the initial emails are sent gradually rather than all at once. The batch is atomic: the distribution's bank account must hold enough unallocated funds to cover it, and it must fit within the organization's daily email send limit. Rejections return409Problem Details witherrorCodeNO_PAYOUT_INSTRUCTIONS_TO_INITIATE,INSUFFICIENT_DISTRIBUTION_BALANCE, orEMAIL_SEND_QUOTA_EXCEEDED(the quota rejection includeslimit,used, andrequested). On success returns202with thebatchId, the accepted count, and the total amount. An optionaldryRunflag runs the same selection and validations (including the409rejections) and returns the would-be summary with200and a nullbatchId, without initiating anything. Rate-limited per organization (429). The singularPOST /payout-instructions/{id}/:initiateis unchanged and keeps sending immediately.
[V2]The entire Campaign Manager API (/v2/campaign-manager/*) has been removed (sunset). All endpoints now return404. Consumers must migrate to the Distribution Manager API (/v3/distribution-manager/*), which exposes the equivalent functionality. Thecampaign-manager-v2OpenAPI/Swagger documents are no longer published, and the default API version is now3. Request/response shapes shared with V3 (e.g. theCampaign*types, user-management and invitation contracts) are unaffected.
[V3]PUT /v3/distribution-manager/brand/slug— set the brand slug (string). The slug is required for the branding to take effect and is immutable once set. Returns 409BRAND_SLUG_LOCKED(slug already set to a different value) orBRAND_SLUG_TAKEN(slug used by another organization).
[V3]Brand config is now treated as always-existing (1:1 per organization). All brandPUTendpoints are upsert ("create or update"): no separate create step is required and call order does not matter.[V3]GET /v3/distribution-manager/brandnow returns200with an empty configuration (nullslug/displayName) when none has been set up yet, instead of404.[V3]PUT /v3/distribution-manager/brand/company-detailsnow also acceptsdisplayName(required) andsupportEmail(optional), alongsidecompanyName+companyAddress.displayNameis the short UI label (tab names, browser history);companyNameis the legal name used in contact details.
[Public][Beneficiary]GET /public/brand/*andGET /beneficiary/brand/*now return404until the brand config is "live" (bothsluganddisplayNameset — the minimum identity; colors/logo remain optional). Since these fields are set via separate PUTs, a partially-configured org no longer leaks a half-branded response — callers fall back to default Talli branding on 404. When200is returned,sluganddisplayNameare guaranteed present.
[V3]POST /v3/distribution-manager/brand(bootstrap) — removed. Brand config is now created implicitly via the individualPUTendpoints (upsert).[V3]PUT /v3/distribution-manager/brand/contact-details— removed.displayNameandsupportEmailmoved to/company-details;fromEmailmoved to the new admin-only/from-emailendpoint. This removes the duplicatedisplayNamethat previously appeared in both branding and contact details.
[V2, V3]POST /v3/distribution-manager/bank-accounts,POST /v3/distribution-manager/funds-holders, andPOST /v2/campaign-manager/funds-holdersno longer return500("Bank Provider is not configured for this organization.") for correctly-onboarded organizations that had no explicit default bank provider set. These calls now succeed for any properly onboarded organization.
[V3]GET /v3/distribution-manager/brand— read merged brand config.[V3]POST /v3/distribution-manager/brand— bootstrap brand config with slug + displayName. Returns 409BRAND_CONFIG_EXISTSif config already exists for the organization; 409BRAND_SLUG_TAKENif slug used by another organization.[V3]PUT /v3/distribution-manager/brand/colors— set/clear primary + secondary brand colors (paired). Body:BrandColors { primaryColor, secondaryColor }.[V3]PUT /v3/distribution-manager/brand/company-details— set company name + address. Body:SetBrandCompanyDetailsRequest { companyName, companyAddress }.[V3]PUT /v3/distribution-manager/brand/contact-details— set displayName, supportEmail, fromEmail. Changing the fromEmail domain resetsfromEmailActivetofalseuntil the new domain is verified.[V3]POST /v3/distribution-manager/brand/logo— upload brand logo (multipart). Accepts SVG ≤ 200KB or PNG ≤ 400×120px ≤ 200KB.[V3]POST /v3/distribution-manager/brand/favicon— upload brand favicon (multipart). Accepts ICO ≤ 32×32px ≤ 20KB.
[V2]GET /campaigns/{id}/withdrawal-accountsandPOST /campaigns/{id}/withdrawal-accountsare deprecated. Use the bank-account-scoped equivalentsGET /bank-accounts/{bankAccountId}/withdrawal-accountsandPOST /bank-accounts/{bankAccountId}/withdrawal-accountsinstead (pass the campaign's bank-account id rather than the campaign id). The campaign-scoped routes continue to work and are now flagged as deprecated in the OpenAPI/Swagger definition.
[V2, V3]TemplateTypeenum — addedPayoutAvailableEmail = 6for payout available email notifications sent when gift card payouts are fulfilled.
[V2, V3]BankAccountResponsenow includes astatusfield (Pending|Active). Existing callers continue to work; the new field is additive.[V2, V3]GET /bank-accountsquery parameterincludePending(defaultfalse). Whenfalse(default),Pendingbank accounts are excluded from the list and the total count. Whentrue, all bank accounts are returned includingPendingones.
[V2, V3]GET /bank-accounts/{id}always returns the bank account regardless of status. The response includes the newstatusfield so callers can distinguishPendingfromActive. No request shape change.
[V3]POST /v3/distribution-manager/funds-holdersnow returnsCreateFundsHolderResponse { fundsHolderId, bankAccountId }instead of the barefundsHolderIdGuid. The bank account is returned inPendingstatus and becomesActiveautomatically once the funds holder is approved.[V2]POST /v2/campaign-manager/funds-holdersnow creates the associated bank account immediately (inPendingstatus) rather than only after KYB approval, so itsbankAccountIdis available straight away. The response shape is unchanged (still the barefundsHolderId).[V2, V3]POST /v2/campaign-manager/campaign-drafts/{id}/:promoteandPOST /v3/distribution-manager/distribution-drafts/{id}/:promotenow return400 ValidationProblem(keyed onbankAccountId) when the draft's assigned bank account is stillPending. Wait until the bank account's status isActivebefore promoting.
[Beneficiary]GET /beneficiary/payout-instruction/response now includesbankPartner(BankProviderenum) — the issuing bank partner, so callers can render issuer-specific copy (T&C, disclaimers, cardholder agreement URLs) keyed by the partner.[Beneficiary]payoutMethodGroups.prepaidCards[].description— optional per-partner description for the prepaid-card row.
[Beneficiary]payoutMethodGroups.prepaidCards[].namefor the prepaid-card row is now resolved per bank partner. Patriot campaigns keep"Easy Prepaid Mastercard®"; Stearns campaigns return"Prepaid Mastercard®". Other payout methods are unchanged.
[V3]POST /v3/distribution-manager/bank-accountsno longer requiresfundsHolderIdin the request body. The request body now only accepts an optionalnamefield.
[V2, V3]POST /bank-accounts/{id}/:withdraw—descriptionnow rejected if longer than 512 characters
[V3]Distribution Manager API (/v3/distribution-manager/*) — new public API for distribution management