Changelog
Changelog
Changes to the API and webhooks, newest first.
Changes to the API and webhooks that affect an integration, newest first. Changes to the product that do not touch this surface are not listed.
2026-10-05: The agent sends again on its own only what one approval may pay
- An
ap_reconcileormilestone_reconcileentry can now carrynotResubmittedBecause: "workspace.two_approvals", withexecution.resultingStatus: "held". The payment was never sent and is above the workspace's figure for two approvals. Two people's approvals of it had not been used to pay it, so the agent held it rather than sending it again. - Every ledger entry is still appended the same way, and its shape does not change.
- Nothing changes in
/api/v1.
2026-10-05: Approve and pay draws on the USYC reserve, and a person's cash back stays
- Approve and pay brings back what the operating wallet lacks. A person's paying approval of a new USDC payment from the operating wallet, on Arc testnet or through CCTP with its fee, may now first bring cash back from the reserve. It does so when the wallet cannot cover the payment and the reserve can cover the rest. It brings back the difference, rounded up to the next micro-USDC, with a fifth more of a CCTP fee, which is read again just before the burn. A CCTP payout whose fee could not be read is not covered. Pay now on a held milestone does the same.
- A new kind of
cash_brought_backentry: actorhuman, withdetail{ by, reason: "approval", invoiceId | milestoneId, amount, neededUsdc, feeCushionUsdc?, operatingBalance, reserveBalance, earnMode, executed, executionNote?, execution? }. It comes before the payment's own entry. - A redemption that did not move is recorded too, with
executed: falseand why inexecutionNote, and nothing is paid. One Arc testnet had not yet confirmed may still land; asking again for the same payment finds that same redemption rather than sending a second one. approval_paidandmilestone_approval_paidthen carryfromReserveUsdc.- When the wallet and the reserve together fall short, Approve and pay refuses, as before, before anything moves. No ledger entry is written for a refusal.
- A new kind of
- A person's Bring cash back stays for 24 hours. For 24 hours after a
cash_brought_backentry withreason: "person", the agent's treasury decisions sweep nothing.- Their entries (
hold, orredeem_from_usycfor what falls due) then carrypersonCashBack: { amount, at, until }. - A sweep the model chose anyway is recorded as a
hold, with what it chose inboundedByCode.
- Their entries (
- Nothing changes in
/api/v1.
2026-10-05: A person's payout to another chain takes the agent's route
- An
approval_paidentry for a new payment to another chain can now carrypayout.route: "gateway", withgatewayBalanceUsdc, on a person's first attempt. A person's Approve and pay now chooses the route as the agent does: Gateway when the workspace's Gateway balance covers the amount and Gateway's fee, and Gateway costs no more than CCTP; CCTP otherwise. Before, a person's first attempt always went through CCTP. - Approve and pay now refuses, before anything is sent, a CCTP payout the operating wallet cannot cover with its fee, and a Gateway payout the Gateway balance cannot cover. No ledger entry is written for a refusal.
- Nothing changes in
/api/v1.
2026-10-05: Two approvals above a figure the workspace sets
- A new guardrail rule,
workspace.two_approvals. Above the figure a workspace's owner sets, the agent never pays or schedules a payable, or releases a milestone: code holds it for two people. Its entry carriesguardrailBlocked: true,execution.resultingStatus: "held"andobserved.twoApprovalsAbove, the figure it was weighed against. A EURC payable is weighed at itsusdcValue. - New ledger actions, each with actor
human:approval_given(domainap) andmilestone_approval_given(domaincontractor): the first of two approvals, which sends nothing. Itsdetailis{ by, invoiceId | milestoneId, counterpartyId, amount, currency, address, usdcValue?, twoApprovalsAbove, fewApprovers? }.approval_policy_changed(domainsystem): an owner set, raised, lowered or turned off the figure. Itsdetailis{ by, from, to }, each a figure in USDC or null for off.
approval_paidandmilestone_approval_paidgainapprovals: [{ by, at }, …], the earlier first, andtwoApprovalsAbovewhen two people approved the payment. Theirsummarythen ends "(the second of two approvals)".- They also carry
fewApprovers: truewhen whoever entered the payment, or gave its address, gave one of the two because no one else could. As many of the two as can come from other people must.
- They also carry
- An
invoice_reopenedentry'sfollowUp.changescan say that two approvals were turned off, or the figure raised to cover the payment. Amilestone_reopenedentry's can say the same. - Nothing changes in
/api/v1.
2026-10-05: A payment Circle did not answer is looked for, never sent again blind
- A payment whose send Circle did not answer is now in flight, not held. Its decision's
execution.resultingStatusismatched, as for any payment Circle is still settling.- Vestiarion then looks for the send on Circle by its reference before anything is sent again.
- If Circle has it, it is recorded as the payment.
- If Circle has listed nothing 15 minutes after the send, the payment is sent as a new one.
- Reject and Return look for it the same way before they act.
- Payments can also be switched off from the database (
npm run payments -- off "<reason>"). Every running deployment stops within 10 seconds, with no redeploy. While they are off, the agent appends no entries. - Approve and pay still records a transfer that was already sent while payments are off, since that only reads Circle.
- Nothing changes in
/api/v1.
2026-10-05: Checked addresses, a stop switch, and no rejection over an unknown transfer
POST /api/v1/counterpartiesrefuses anaddressthat mixes capital and small letters when they do not match its EIP-55 checksum:400 invalid_request, with the messageaddress: This address's capital letters do not match its checksum, so a character is likely wrong.An address written in one case alone is taken as written, as before.- Payments can be switched off for every workspace by whoever runs the deployment (
PAYMENTS_DISABLED). While they are, no agent cycle runs, so the agent appends no entries and webhooks receive none from it. - When a payment's send ends without Circle saying what became of it, the invoice can be neither rejected nor returned until a person approves it, which asks Circle again under the same key. No ledger entry changes shape.
- On a deployment with no screening service, a live workspace's counterparties stay
unscreenedinstead of being screened against the demo list. Itscompliance_sweepentries say the screening is incomplete. - Otherwise nothing changes in
/api/v1.
2026-10-05: Two people before the first payment to an address
-
A new guardrail rule,
counterparty.new_payee. Wherever payments are real, the agent never makes a payment or milestone release that would be the first to an address unless two different parties stand behind it:- the payee sent the address and a member confirmed it; or
- one member gave it and another confirmed it.
Its entry carries
guardrailBlocked: trueandexecution.resultingStatus: "held". -
A decision on a first payment records
observed.newPayee: { addressBy, confirmedBy, twoParties }.addressByis a member id,"payee", or null when no entry says who gave it. -
approval_paidandmilestone_approval_paidgainfirstPayment: truewhen a person made an address's first payment. Approve and pay, and Pay now, now refuse the member who gave the address, unless they are the workspace's only approver. -
An
invoice_reopenedentry'sfollowUp.changescan namethe payee's address has received a confirmed payment sinceora second person now stands behind the payee's address. -
Nothing changes in
/api/v1.
2026-10-05: A EURC payable decided again when the rate changes what held it
- An
invoice_reopenedentry gainsreevaluationwhen a fresh quote cleared what held a EURC payable. Its shape is{ trigger, previousDecision: { seq, action, guardrailRule }, before: { rate, usdcValue, swapCostPercent }, after: { rate, usdcValue, swapCostPercent, quotedAt } }.triggeris one ofrate_available,swap_available,swap_cost_within_caporvalue_within_limit.followUp.changessays the same in words.
- The
ap_*decision that follows such a reopen in the same cycle gainsreevaluation: { reopenedSeq, trigger, previousDecisionSeq, previousAction }. Its ownfxis the rate it was decided at. - A
cycle_completeentry'seventscan includefx_changed. The FX watch started that cycle, every 5 minutes at most, not a person, so itsbyis absent. - Nothing changes in
/api/v1.
2026-10-05: The three-way match, checked in code
- A new guardrail rule,
invoice.match_incomplete. When the agent chooses to pay or schedule a payable whose goods are not confirmed received, or whose counterparty needs a purchase order and has none on file, code refuses it. Itsap_payorap_scheduleentry carriesguardrailBlocked: true,guardrailRule: "invoice.match_incomplete"andexecution.resultingStatus: "awaiting_info", and the invoice waits for its details. - An AP decision's
detail.observedgainspurchaseOrderRequired: whether the counterparty needed a purchase order when it was decided. Every counterparty needs one unless an owner or admin marked it as paid without them. - A new ledger action, delivered to webhooks as
ledger.appendedlike every entry:counterparty_purchase_orders_changed, actorhuman, domaincompliance. Itsdetailis{ by, counterpartyId, from, to }, wherefromandtosay whether the counterparty needed a purchase order before and after. - A
cycle_completeentry'seventscan includepurchase_orders_waived: a counterparty was marked as paid without purchase orders, so its payables that waited for one were decided again within a minute. - The
invoice_reopenedentry for such a payable namesthe counterparty is now paid without purchase ordersinfollowUp.changes. - Nothing changes in
/api/v1.
2026-10-04: A merge starts the cycle
- A
cycle_completeentry'seventscan now includepull_request_merged. A pull request was merged in a repository the workspace's GitHub connection covers, and a pending milestone waited on it, so the cycle that verifies and pays it started within a minute of the merge rather than at the next scheduled cycle. - Nothing changes in
/api/v1.
2026-10-04: Bounties from a pull request comment
- A new ledger action, delivered to webhooks as
ledger.appendedlike every entry:github_bounty_attached, actorhuman, domaincontractor. Someone who can write to a repository attached a bounty to a pull request with/bounty. Itsdetailis{ by, via: "github", installationId, login, pullRequest, author, amount, milestoneId, counterpartyId, commentUrl }.byis the member who connected GitHub, under whose name the milestone was added, andloginis the GitHub user who commented. See Show payments on GitHub. create_counterpartyandcreate_milestoneentries gainvia: "github",installationIdandloginfor the contractor and the milestone a bounty added.counterparty_address_changedentries gainvia: "github",installationId,loginandcommentUrlwhen a pull request's author set their address with/payto. Theirbyisnull, as for an address sent through a payee link, and the address waits for a member's confirmation the same way.- Nothing changes in
/api/v1.
2026-10-04: Emailed invoices finished by hand
- A
create_invoiceentry withvia: "email"no longer always carrieschanged: []. An owner or admin can now finish an emailed invoice in the invoice form on AP / AR, with Edit and add or Finish and add. Itsdocument.changedthen lists the fields they changed from what was read, as for an invoice added from a document. - Such an entry carries no
documentwhen the email could not be read and the invoice was typed in. - Nothing changes in
/api/v1.
2026-10-04: Cash back for milestones too
- A
cash_brought_backentry withreason: "payments_due_today"now also counts the verified milestones waiting to be paid inneededUsdcandpayments, so a cycle brings their cash back from the reserve before it releases them. A milestone whose USDC is locked in escrow is not counted. - A person's Pay now on a milestone for a pull request now gets its
pull_request_commentedentry once the payment is confirmed, rather than at the next cycle.
2026-10-04: Payments said on the pull request
- A workspace can connect GitHub from Settings, by installing the Vestiarion app on the repositories it chooses. See Show payments on GitHub.
- Three new ledger actions, delivered to webhooks as
ledger.appendedlike every entry:github_connected, withinstallationId,account,accountTypeandrepositorySelection;github_disconnected, withinstallationIdandaccount;pull_request_commented, withmilestoneId,pullRequest,commentUrl,amount,tokenandtxHash. It is written when the app commented on the pull request a milestone was paid for. The comment names the amount, the network, the paying workspace and the transaction, never the payee.
- Private pull requests verify in a repository the app is installed on, so
verify_milestone_githubcan now follow a private pull request's merge. - Nothing changes in
/api/v1.
2026-10-03: Invoices by email
- New ledger actions, delivered to webhooks like every entry:
invoice_inbox_on,invoice_inbox_changedandinvoice_inbox_off, actorhuman, domainsystem: an owner or admin turned on the workspace's address for invoices by email, replaced it, or turned it off. Theirdetailis{ by }; the address is never recorded.invoice_email_received, actorsystem, domainap: an email arrived at the address and was read. Itsdetailis{ inboxEmailId, from, authentication, read, document }: the sender with most of its name hidden, SPF/DKIM/DMARC as Resend reported them,readone ofready,needs_detailsorunreadable, and the document'skindandsha256, ornull.invoice_email_dismissed, actorhuman, domainap:{ by, inboxEmailId }.
create_invoiceentries gainvia: "email"andinboxEmailIdwhen a member added the payable from an email on AP / AR. Such an entry carriesdocumentas one added from a document does, withchanged: []. Nothing that arrives by email is added without a person.- Nothing changes in
/api/v1.
2026-10-03: Add milestones and payee links through the API
- Add a milestone,
POST /api/v1/milestones, with a read-and-write key.- It takes
{ contractorId, title, amount, verificationSource? }, checked by the rules of the console's Add milestone form, and answers201with the milestone, shaped as List milestones returns it. - The milestone starts
pending, and the API cannot verify it. A GitHub pull request inverificationSourceis verified by the agent once it is merged; any other link waits for a person. - It takes an
Idempotency-Key, as every write does. - It is recorded as
create_milestonewithvia: "api"andapiKeyId.
- It takes
- Create a payee link,
POST /api/v1/payee-links, with a read-and-write key.- It takes
{ counterpartyId }, a vendor or a contractor, and answers201with{ id, counterpartyId, url, expiresAt }. urlis in that answer only, which is sent withCache-Control: no-store.- The operation keeps no outcome for an
Idempotency-Key, which would store the link. A repeat makes a new link, and the unused one stops working. - It is recorded as
payee_link_createdwithvia: "api"andapiKeyId. The address a payee enters through it waits for a person to confirm it, as before.
- It takes
- A write now checks the key's creator as they are now. Only owners and admins can add records. If an owner moves the creator to approver or viewer, the key keeps reading, and every write, invoices and counterparties included, answers
403 forbidden: "This key's issuer can no longer add records in this workspace." - A milestone records who added it, from the console, Pay a freelancer or the API. So the self-approval rule now applies to milestones too. When the agent held a milestone on its own judgment, the person who added it cannot choose Pay now for it, unless they are the workspace's only approver. Milestones added before keep no creator.
- MCP gains
create_milestoneandcreate_payee_link.create_payee_linkis marked destructive, because a new link revokes the unused one. @vestiarion/sdk0.2.0 addsmilestones.createandpayeeLinks.create.- Settings: the box that gives a key write access is now Can also add records.
2026-10-03: Add an invoice from Slack
create_invoiceentries gainvia: "slack"andlinkIdwhen an owner or admin added the payable from a message in Slack with Add invoice. Such an entry carriesdocumentas one added from a document on AP / AR does, withchanged: [].- The Slack app asks for one more permission,
files:read, to read the file someone chooses. A Slack connected before this is connected again from Settings with Reconnect Slack; its members stay connected. - Nothing changes in
/api/v1.
2026-10-03: A member's API keys end with their membership
- A workspace API key now works only while the person who created it is a member of the workspace. When they leave, are removed by an owner or an admin, or delete their account, every active key they created in that workspace, read-only or read-and-write, is revoked in the same step, and from then on answers
401 unauthorizedon/api/v1and on MCP, like any revoked key. Keys that other members created, and the person's keys in workspaces they still belong to, keep working. - Each such key gets its own
api_key_revokedentry, delivered to webhooks like every entry, right after the member'smember_leftormember_removed. Itsdetailgainsreason:member_left,member_removed(withmember, the person removed, whilebyis who removed them) oraccount_deleted. A key revoked in Settings now carriesreason: "person". - A key can no longer be created for someone who is not a member of the workspace.
- A read-and-write key whose issuer has left can no longer add records, so a
create_counterpartyorcreate_invoiceentry withvia: "api"always names its issuer inby. - Nothing was revoked when this shipped: no active key had outlived its creator's membership.
2026-10-03: The TypeScript SDK on npm
npm install @vestiarion/sdkinstalls it from the npm registry.- Version 0.1.0 there is the same tarball, byte for byte, as
https://www.vestiarion.xyz/sdk/vestiarion-sdk-0.1.0.tgz, which keeps working.
2026-10-03: A member's notifications move to Settings
telegram_disconnectedentries for a chat its member disconnected from Vestiarion now carryvia: "settings": the Telegram card moved from Members to the Notifications section at the top of Settings, with the switch for the email about payments that need a decision. Entries made before keepvia: "members_page";via: "telegram"andvia: "blocked"are unchanged.- Nothing else changes in the ledger, the webhooks or
/api/v1.
2026-10-03: A TypeScript SDK
@vestiarion/sdk0.1.0, installed withnpm install https://www.vestiarion.xyz/sdk/vestiarion-sdk-0.1.0.tgz. It is a typed client for every/api/v1operation, with no dependencies, for Node 20 or later, Deno, Bun and edge runtimes. See TypeScript SDK.- Its types are generated from the OpenAPI document, so they follow each change listed here.
listAllandpagesfollowpage.nextCursorto the end of a collection.- Requests are retried after:
- a
429, when itsRetry-Afteris 60 seconds or less; - a
5xx; - a timeout or a network failure.
- a
- Every write carries an
Idempotency-Key, so a retry never adds a record twice. verifyWebhookchecks a delivery'sVestiarion-Signature.verifyLedgerEntrychecks an entry's body hash, Ed25519 signature and chain hash.- Nothing in the API or the webhooks changed.
2026-10-03: The agent's decisions in Slack
- New ledger actions, domain
system, delivered to webhooks like every entry:slack_installed, actorhuman: an owner or admin connected a Slack workspace from Settings, and picked the channel the agent's decisions go to. Itsdetailis{ by, teamId, channelId }.slack_uninstalled: the workspace's Slack was disconnected, with every member's Slack link. Actorhumanwhen an owner or admin chose Remove Slack (via: "settings"); actorsystemwhen Slack said the app was removed from its workspace (via: "slack"), withbynull. Itsdetailis{ by, teamId, via }.slack_member_connected, actorhuman: a member connected their own Slack account. Itsdetailis{ by, linkId }, plusvia: "install"for the account of the person who connected the workspace.slack_member_disconnected, actorhuman: a member disconnected their own Slack account, with/vestiarion disconnect(via: "slack") or from Settings (via: "settings"). Itsdetailis{ by, userId, linkId, via }. A member removed from the workspace loses the link with the membership, and that is recorded as the removal.slack_decisions_limit_changed, actorhuman: an owner set the most a payment approved from Slack may be, or turned deciding from Slack off. Itsdetailis{ by, from, to }, in USDC,nullfor off.
approval_paid,approval_rejectedandapproval_returnedentries gainvia: "slack"andlinkIdwhen a member decided the payable from its message in Slack, andagent_pauseddoes when they paused the agent with/vestiarion pause. Everything else in them is what the console writes. Entries for actions taken in the console carry novia, as before.- A cycle's journal has a new stage,
slack, aftertelegram, the last. It needs no other stage to have succeeded. - Nothing changes in
/api/v1.
2026-10-03: A payable held for an unconfirmed address goes back to the agent once it is confirmed
- A payable the agent held because its counterparty's address changed and no one had confirmed it (
guardrailRule: "counterparty.address_unconfirmed",observed.addressUnconfirmed: true) now goes back to the agent when a person confirms the address: aninvoice_reopenedentry whosefollowUp.changesreads "the counterparty's new address has since been confirmed", then a new decision in the same cycle. Until now it stayed held until a person approved it. Approving it still pays it, and confirms the address. - The API's two write examples are now responses captured in testnet-2.
2026-10-03: Add counterparties and invoices through the API
- New operations:
POST /api/v1/counterpartiesandPOST /api/v1/invoices. Each takes a JSON body of at most 64 KB, checked by the console form's rules, and answers201with the record, shaped as its list returns it. A body that does not validate, including one with a field the operation does not take, answers400 invalid_request, naming the field. AcounterpartyIdthe workspace does not hold answers400too. - A new scope,
write. A key is read-only, as every existing key stays, or reads and writes; an owner or admin chooses when creating it. A read-only key on a write answers403 forbidden.api_key_createdlistsscopes: ["read", "write"]for such a key. Idempotency-Keyon both writes. A repeat with the same key and body within 24 hours gets the first answer back, withIdempotent-Replayed: true. The same key with a different body, or while the first request is still being handled, answers the new error codeconflict(409). A5xxanswer, and a body that fails validation, are not kept, and a key whose first request never finished is free again after 10 minutes.- Writes are limited to 30 a minute per key; over that,
429 rate_limitedwithRetry-After: 60. Reads stay unlimited. create_counterpartyandcreate_invoiceentries for records added through the API carryvia: "api"andapiKeyId, withbythe key's issuer, ornullonce their account is gone. Such acreate_counterpartyalso carriesaddressNeedsConfirmation: an address added through the API waits for a person to confirm it, and the agent holds payments to it (counterparty.address_unconfirmed) until then.- A payable added through the API starts a cycle, whose
cycle_completeentry listsinvoice_addedinevents, as one added in the console does. - MCP: two new tools,
create_counterpartyandcreate_invoice, annotatedreadOnlyHint: false,destructiveHint: false,idempotentHint: false, with an optionalidempotencyKey. A read-only key gets the403as a tool error. - The OpenAPI document describes each write's request body, its
Idempotency-Keyheader and its201.
2026-10-03: A sweep's yield is counted over how long the cash would stay
- In treasury entries (
sweep_to_usyc,redeem_from_usyc,hold, actoragent),economics.expectedHoldDaysis now how long the swept cash would stay in the reserve, on average over the next 30 days. What falls due is paid from the operating wallet first, and only what it cannot cover comes back, on its day. Before, it was the days until the next obligation.economics.projectedYieldUsdfollows, and the written policy'sreferenceDecisionwith it.
2026-10-03: Code bounds the treasury's moves, and a payable to a client waits for a person
- A new value of
guardrailRule:counterparty.client_payable. The agent holds a payment, or a schedule, to a counterparty whose role isclient, for a person: a client pays the business, so a payable to one is a refund, or an invoice entered in the wrong direction. A person may still approve it. - Treasury entries (
sweep_to_usyc,redeem_from_usyc,hold, actoragent, domaintreasury):decisionis now the move code allowed. A sweep takes at most the cash above the 7-day buffer. A redemption brings back at most 115% of what falls due within 14 days, less the operating balance, and at least the written policy's.- When code changed the model's move,
boundedByCode: { chosen: { action, amount }, reason }keeps what the model chose and why, anddecision.reasoningends with "[Code limited this: …]". agreedWithReferencenow also needs the amount within 5% ofreferenceDecision.amount, or 0.01 USDC.
ar_reminders_ongainsreplacedLink: true only when turning reminders on replaced a link made before October 3, which stops working.madeNewLinkis also true when the receivable had no link.
2026-10-03: The agent reminds clients of what they owe
- New ledger actions, domain
ar, delivered to webhooks like every entry:ar_reminders_onandar_reminders_off, actorhuman: an owner or admin turned the agent's reminders on or off for a receivable.detailis{ by, invoiceId, counterpartyId }, and forar_reminders_onalsolinkIdandmadeNewLink(a link made before October 3 was replaced so the reminders can carry it).ar_reminder_sent, actoragent: the agent emailed the client a reminder.detailis{ invoiceId, counterpartyId, to, number, tone, daysFromDue, amount, currency, linkId, decision, referenceDecision, decisionMode, agreedWithReference }.tois the address with most of its name hidden;numberis 1 to 4;toneisfriendly,firmorfinal;daysFromDueis negative before the due date.toneLimited: { chosen, sent }is there when code sent a softer tone than the model chose.ar_reminder_deferred, actoragent: the agent decided to wait before reminding.detailis{ invoiceId, counterpartyId, until, daysFromDue, remindersSent, decision, referenceDecision, decisionMode, agreedWithReference }.
- Turning reminders on starts a cycle; its
cycle_completeentry lists the new event kindreminders_oninevents. - A cycle's journal has a new stage,
collections, betweenproposalsandnotices. It runs only whenreceiptscompleted. counterparty_notice_email_changednow covers a counterparty's billing email, which also receives a client's reminders; itssummaryreads "Billing email for name: address", or "... removed".
2026-10-03: The agent's decisions in Telegram
- New ledger actions, domain
system, delivered to webhooks like every entry:telegram_connected, actorhuman: a member connected their own Telegram chat to the workspace, from Members. Itsdetailis{ by, username }, whereusernameis the Telegram username with most of it hidden (@li***), ornull.telegram_disconnected: a chat no longer gets the workspace's decisions. Actorhumanwhen the member disconnected it, from Members (via: "members_page") or with/disconnectin the chat (via: "telegram"); actorsystemwhen Telegram answered that the member had blocked the bot (via: "blocked"). Itsdetailis{ by, userId, via, username }, withbynullforblocked. A member removed from the workspace loses the connection with the membership, and that is recorded as the removal.
create_invoiceentries gainvia: "telegram"when a member added the payable by tapping Add on an invoice the bot read. Such an entry carriesdocumentas one added from a document on AP / AR does, withchanged: [].- A cycle's journal has a new stage,
telegram, afternotices, the last. It needs no other stage to have succeeded. - Nothing changes in
/api/v1.
2026-10-03: Cash comes back from the reserve for payments
- New ledger action
cash_brought_back, domaintreasury, delivered to webhooks like every entry: USDC came back from the reserve to the operating wallet.- Actor
agent: each cycle, before it decides payments, the agent brings back what the USDC payables due today need beyond the operating balance. Itsdetailis{ reason: "payments_due_today", amount, neededUsdc, payments, operatingBalance, reserveBalance, executed, executionNote, earnMode, execution? }.executionholds the transactions of a real USYC redemption. When nothing could move,executedisfalse,executionNotesays why, and the entry'ssummarybegins "Could not bring"; while the agent is paused,heldBecauseisagent_paused. - Actor
human: an owner or admin chose Bring cash back. Itsdetailis{ by, reason: "person", amount, all, reserveBalance, earnMode, execution? }, whereallsays they brought back everything. It moves cash even while the agent is paused, and starts a cycle, whosecycle_completeentry lists the new event kindcash_returnedinevents.
- Actor
- The redemption itself is also a
treasury_actionsrow withaction: "redeem_from_usyc", as the treasury stage's are. - An AP decision held because the cash was not there carries
execution.heldBecause: "cash_shortfall", withexecution.cashNeededUsdc(what it needed, with the payables due before it) andexecution.cashSeen: { operating, reserve }(the balances it saw). Once cash has come in since and covers it, the next cycle reopens the payable with aninvoice_reopenedentry whosefollowUp.changesreads "the cash it needs is there now (… USDC in the operating wallet and the reserve)", and decides it again. - A cycle's journal has a new stage,
liquidity, betweenservicesandap.
2026-10-03: Payment notices
- New ledger actions, delivered to webhooks like every entry:
payment_notice_sent, actorsystem, domainaporcontractor: a payee was emailed that a payment to it was confirmed. Itsdetailis{ invoiceId or milestoneId, counterpartyId, to, amount, token, txHash }, wheretois the address with most of its name hidden (li***@example.com).counterparty_notice_email_changed, actorhuman, domaincompliance: a person set, changed or cleared where a counterparty's payment notices go. Itsdetailis{ by, counterpartyId, from, to }, both addresses hidden the same way,nullfor none.
create_counterpartyentries gainnoticeEmail, hidden the same way, ornull.- The address itself is not part of
/api/v1/counterparties.
2026-10-03: Add what a held payable was missing
- New ledger action
invoice_details_added, actorhuman, domainap, delivered to webhooks like every entry: a person added the purchase order a held, flagged or awaiting-information payable lacked, or marked its goods or services received. Itsdetailis{ by, invoiceId, counterpartyId, added }, whereaddedholdspoReference,goodsReceived: true, or both. Nothing already on the invoice changes, and itsstatusstays as it was. - At its next cycle the agent reopens the payable with an
invoice_reopenedentry whosefollowUp.changesnames what was added, and decides it again. Adding details starts that cycle within seconds, and itscycle_completeentry lists the new event kinddetails_addedinevents. - In
/api/v1/invoices, the payable'spoReferenceandgoodsReceivedshow what was added.
2026-10-03: A sandbox screens against the bundled watchlist
GET /api/v1/statusreportsscreening: "simulate"for a sandbox workspace, whatever the deployment configures; a live workspace reportslivewhen OpenSanctions is configured, as before.- A sandbox's counterparties are screened against the bundled watchlist at every cycle, and their
compliance_sweepentries name that source. One screened by OpenSanctions before is screened again against the watchlist at its next cycle.
2026-10-03: Nothing is paid to a counterparty never screened
- A new value of
guardrailRule:counterparty.unscreened. The agent holds a payment, a schedule or a milestone release to a counterparty whoseriskLevelis stillunscreened, because screening could not run when it was added. The decision is made again once screening gives a verdict. A counterparty whose later screening fails keeps its earlier verdict, as before.
2026-10-03: The spending limit, enforced on Arc
- New ledger actions, actor
human, domainsystem, delivered to webhooks like every entry:spending_limit_enforced: a person enforced the agent's spending limit on Arc testnet. Itsdetailis{ by, contract, agent, treasury, dailyUsdc, weeklyUsdc, deployTxHash, approveTxHash, setLimitsTxHash }, whereagentis the agent's own wallet andtreasurythe operating wallet.spending_limit_unenforced: a person turned it off. Itsdetailis{ by, contract, txHash }.
agent_budget_changedgainsonChain: { contract, txHash }when the figures were changed on the contract too.- While the limit is enforced, the agent's payment decisions (
ap_*andmilestone_*entries, actoragent) carryonChainLimit: { contract, agent, ref, covered, uncoveredBecause?, verdict }.coveredsays whether the payment can go through the contract;verdictis the contract's answer before anything was sent:{ state: "allowed" },{ state: "refused", error, spent?, amount?, limit? },{ state: "unreadable", reason }, ornullwhen it was not asked. - Two new values of
guardrailRule:workspace.onchain_limit(the contract would refuse the payment) andworkspace.onchain_limit_route(the payment is in EURC, or to another chain, which the contract cannot carry). - A payment sent through the contract has, as its transaction, a call to the contract's
pay(address,uint256,bytes32)by the agent's wallet. Its logs hold the USDC transfer from the operating wallet to the payee.
2026-10-03: A workspace's only approver may approve what they entered
- When the person who entered a payable, or added a milestone, is the only member of the workspace who may approve payments (the only owner, admin or approver), they may now approve it themselves. The
approval_paidormilestone_approval_paidentry then carriessoleApprover: truein itsdetail, and itssummaryends "(entered and approved by the workspace's only approver)". An approval by anyone else carries neither. Once a second member may approve, the person who entered a record cannot approve it again.
2026-10-02: The agent's reasoning is written in plain English
- New decisions'
agentReasoningin/api/v1/invoicesand/api/v1/milestones, anddetail.decision.reasoningin ledger entries, are written as 2 to 4 plain-English sentences, without the field names of the facts the agent was shown. Nothing about earlier records changes: the API and the ledger return what was stored.
2026-10-02: One review dismisses every screening match
- A
screening_match_dismissedentry can now dismiss several matches at once: the ones the person reviewed together. ItsdetailgainsmatchedEntities: [{ id, caption, score }], every match it dismissed.matchedEntityId,matchedCaptionandmatchedScorestill name the counterparty's verdict match, the first in the list. - Screening now reads every candidate it needs. Previously, after five dismissals of one name, a re-screen could read
clearwhile further matches were never looked at. Such a counterparty may now be screenedmediumorhighagain, with arisk_level_changedentry.
2026-10-02: A person decides a held milestone
- Milestones can be
closed. In/api/v1/milestones,statusmay beclosed: a person closed the milestone without paying it, and it is never paid after.?status=closedlists them. Every milestone has two new fields,nullunless it is closed:closedAt: when it was closed;closeReason: the reason the person gave.
- New ledger actions, delivered to webhooks like every entry:
milestone_approval_paid(actorhuman, domaincontractor): a person paid a held milestone. Itsdetailis{ by, milestoneId, counterpartyId, amount, currency, overrode, heldFor, txRef, status, attempt?, retriedAfter? }.statusispaid,verified(submitted, not yet confirmed) orheld(the transfer failed).heldForsays what it was held for: for exampletransfer_failed,agent_heldoroutflow_budget.retriedAfteris present when Circle had failed the previous attempt, which this one sent again.
milestone_closed(actorhuman, domaincontractor): a person closed a held milestone without paying it. Itsdetailis{ by, milestoneId, counterpartyId, amount, currency, heldFor, reason }.
2026-10-02: The agent buys payee history over x402
- New public endpoint:
GET /api/x402/payee-history?address=0x…. It is paid per call over x402: 0.001 USDC on Arc testnet (eip155:5042002), schemeexact, settled through Circle Gateway batching (GatewayWalletBatched, GatewayWallet0x0077777d7EBA4688BDeF3E311b846F25870A19B9).- Without a
PAYMENT-SIGNATUREheader it answers402with the offer inPAYMENT-REQUIRED. - With a payment Circle's facilitator verifies and settles, it answers
200with{ address, workspacesPaid, paymentsConfirmed, firstPaidAt, lastPaidAt, asOf, seller, priceUsdc }and the settlement inPAYMENT-RESPONSE. - A missing or malformed address answers
400before any offer. When the history cannot be read, it answers503and nothing is charged.
- Without a
- New ledger actions:
service_purchased(actoragent, domaincompliance). Itsdetailis{ purchaseId, counterpartyId, address, seller, priceUsdc, payer, payTo, nonce, settlement, result }.service_purchase_refused(actoragent, domaincompliance). Itsdetailis{ purchaseId, counterpartyId, address, seller, rule, reason }.ruleis one ofseller.not_allowed,offer.mismatch,price.above_max,budget.dayorpurse.short.service_purchase_failed(actoragent, domaincompliance). Itsdetailis{ purchaseId, counterpartyId, address, seller, reason }.service_budget_funded(actorhuman, domaintreasury). Itsdetailis{ by, amountUsdc, signer, depositor, txHash, balanceUsdc }.
- AP decision entries and
milestone_release/milestone_holdentries carryobserved.addressHistorywhen the agent bought the counterparty's address history within 7 days:{ about, workspacesPaid, paymentsConfirmed, firstPaidAt, lastPaidAt, boughtAt, priceUsdc }. - A cycle's
detail.stagesincludesservices, which runs afterrecurringand beforeap.
2026-10-02: Batches through the operating wallet's own executeBatch
- A batch of milestones is now one call to the operating wallet's own
executeBatch, as Circle documents for its smart accounts. The wallet sends USDC to each payee in turn, and if one transfer fails, none happens. - The first version sent batches through Arc's Multicall3From. Multicall3From keeps the sender only when the sender signed the transaction itself, and a Circle smart account's transaction is sent by Circle's bundler. Circle refused that batch at estimation (
ESTIMATION_ERROR), and no money moved. Its milestones were held, and theirmilestone_releaseentries carryexecution.batchwithresultingStatus: "held". - For a short time in between, workspaces with live wallets paid each milestone alone.
2026-10-02: Several milestones in one transaction
- When a cycle releases two or more milestones paid in USDC on Arc testnet, they go out in one transaction. Each is still decided, checked and signed on its own. A milestone locked in escrow is always released on its own.
- A
milestone_releaseentry for a milestone paid this way carriesexecution.batch:{ key, size }.keyidentifies the batch, andsizeis how many milestones it paid.execution.txRefis the batch's transaction, shared with the other milestones in it.execution.feeUsdis this milestone's share of the transaction's fee.
- In
/api/v1/milestones, several milestones can have the sametx_ref. A transaction hash no longer identifies one payment: key on the milestone'sid.
2026-10-02: The agent proposes limits from people's approvals
- When people approve at least two payments to one counterparty above its payment limit within 30 days, and reject none, the agent may propose raising the limit. The counterparty must be screened clear. An owner or admin accepts or dismisses the proposal in Approvals.
- New ledger actions, in the
compliancedomain:policy_proposal_made(actoragent). Itsdetailis{ proposalId, counterpartyId, kind, fromLimit, toLimit, evidence, reasoning, decisionMode, referenceLimit, agreedWithReference }.kindisraise_limit.evidencelists the approvals it rests on:{ invoiceId, amountUsdc, approvedAt, approvedBy, agentAction, guardrailRule }.policy_proposal_declined(actoragent): the model decided not to propose. Itsdetailis{ counterpartyId, currentLimit, evidence, reasoning, decisionMode, agreedWithReference }.policy_proposal_accepted(actorhuman). Itsdetailis{ by, proposalId, counterpartyId, fromLimit, toLimit, currentLimit }. Acounterparty_limit_changedentry precedes it, as for any limit change.policy_proposal_dismissed(actorhuman). Itsdetailis{ by, proposalId, counterpartyId, toLimit }.
- A cycle's
detail.stagesincludesproposals, which runs last, afterforecast.
2026-10-02: Recurring payments
- An owner or admin can set up a payment that repeats every N days, weeks or months. Each period's invoice is created as it comes near and decided like any other.
- New ledger actions, in the
apdomain:recurring_payable_created(actorhuman). Itsdetailis{ by, recurringId, counterpartyId, amount, currency, memo, poReference, goodsReceived, everyCount, everyUnit, startsOn, endsOn }.recurring_invoice_created(actoragent), when a cycle creates a period's invoice. Itsdetailis{ invoiceId, recurringId, period, counterpartyId, amount, currency, goodsReceived }.periodis the invoice's due date,YYYY-MM-DD.recurring_payable_stopped(actorhuman). Itsdetailis{ by, recurringId }.
- A cycle's
detail.stagesincludesrecurring, which runs afterfollow_upand beforeap. - An event cycle's
detail.eventscan includerecurring_added: someone set up a recurring payment. - Two invoices created for different periods of one recurring payment are never reported as duplicates of each other. Their decision entries'
observed.duplicateCheckreflects that.
2026-10-02: A real USYC reserve
- A live workspace's owner or admin can turn on a real USYC reserve on Arc testnet, once Circle has allowlisted its operating and reserve wallets. Until then, the reserve stays simulated, as before.
- New ledger action
usyc_reserve_enabled, in thetreasurydomain, appended when the reserve is turned on. Itsdetailis{ by, operatingAddress, reserveAddress }. - Treasury decision entries (
sweep_to_usyc,redeem_from_usyc,hold) of a workspace with a real reserve:- carry
earnMode: "live"; - carry
usycSubscriptionsOpen: whether USYC could be bought then, ornullwhen that could not be read; - when a move was made, carry
execution. A sweep's is{ approveTxHash, depositTxHash, shares, price }; a redemption's is{ redeemTxHash, shares, price }.sharesis in USYC,pricein USDC per USYC.
- carry
- A sweep decided while USYC cannot be bought has
executed: false, and anexecutionNotesaying so. - In
GET /api/v1/status,data.provenance.yieldreadslivefor such a workspace. - The reserve account's
balanceis its USYC's value in USDC at the latest price, read from Arc testnet every cycle. Itsapyis the fund's yield over at least the last 5 days, from the same oracle.
2026-10-02: The agent's spending limit
- An owner or admin can set what the agent may pay on its own per UTC day and per 7 days, in USDC. A payment past either is held for a person.
- New value of
guardrailRule,workspace.outflow_budget, on anap_payormilestone_releaseentry: paying it would have taken the agent past its limit.- The entry's
execution.heldBecauseisoutflow_budget. - A payable goes to Approvals as
held. A milestone stayshelduntil the limit has room for it.
- The entry's
- New field
outflowBudgeton anap_payormilestone_releaseentry, present when a limit is set:{ dailyUsdc, weeklyUsdc, spentToday, spentThisWeek, remaining, binding }.- It is what the limit left when the payment was weighed.
bindingisdayorweek, whichever figure left less.
- Milestone decision entries now carry
guardrailRule:counterparty.high_risk,counterparty.payment_limit,workspace.outflow_budget, ornull. - New ledger action
agent_budget_changed, in thesystemdomain, appended when someone changes the limit.- Its
detailis{ by, from, to }.fromandtoare each{ dailyUsdc, weeklyUsdc }, andnullmeans no figure.
- Its
- What counts against the limit is what the agent's own
ap_payandmilestone_releasedecisions sent: entries whoseexecution.resultingStatusispaidormatched, at their USDC value. A payment a person approves does not count. invoice_reopenedandmilestone_reopenedentries can list the change "the agent's spending limit has room for it again" or "the agent's spending limit was removed".- An event cycle's
detail.eventscan includebudget_raised: someone raised or removed a figure of the limit.
2026-10-01: Receivables paid on Arc
- New ledger action
pay_link_created, in theardomain: an owner or admin made a link for a client to pay a receivable. Itsdetailis{ by, invoiceId, linkId }. - New ledger action
ar_received, in theardomain, appended by the agent when a transfer that arrived in the operating wallet settles an open receivable.- Its
detailis{ invoiceId, counterpartyId, amount, currency, txHash, from, circleTxId, matchedBy, receivedAt }. matchedByissenderwhen the client's address on file sent it, andamountwhen it was the only open receivable of that currency and amount.
- Its
- A receivable's
statusbecomesreceived, withsettledAtandtxRefset, when a transfer settles it. - A cycle's
detail.stagesincludesreceipts, which runs afterreconcile. - An event cycle's
detail.eventscan includepayment_received: a client's "I have paid" check found the receivable paid.
2026-10-01: Dismissing a screening match
- New ledger action
screening_match_dismissed, in thecompliancedomain, delivered as aledger.appendedevent. It is appended when a member who can decide approvals dismisses a counterparty's screening match as not the same person.- Its
detailis{ by, counterpartyId, matchedEntityId, matchedCaption, matchedScore, screenedName, reason }. matchedEntityIdis the screening service's id for the entity dismissed;screenedNameis the counterparty's name when it was dismissed.
- Its
- Screening skips a dismissed entity for that counterparty while its name stays the same, and judges the next match, if any. The counterparty is screened again at once, so a
screen_counterpartyentry with its new verdict follows. - An event cycle's
detail.eventscan includematch_dismissed.
2026-10-01: Held milestones decided again
- New ledger action
milestone_reopened, in thecontractordomain, delivered as aledger.appendedevent. The agent appends it when it returns a held milestone toverifiedbecause a fact its decision rested on changed: the contractor's risk level or payment limit, the milestone's verification source, or the agent was paused when it held it.- Its
detailis{ milestoneId, previousStatus, followUp: { action, reason, changes } }.previousStatusisheld,followUp.actionisreopen, andchangeslists each fact that moved, in words. - The milestone's next decision entry follows in the same cycle.
- Its
- A milestone's
statuscan now go fromheldback toverifiedwithout anyone verifying it again. - An event cycle's
detail.eventscan includelimit_raised: someone raised a counterparty's payment limit and screening allows more than zero.
2026-10-01: EURC payments funded by a swap
- A live workspace can pay a EURC payable after swapping USDC for the EURC it needs, through Circle's Stablecoin Service on Arc testnet.
- New ledger action
fx_swap, in thetreasurydomain, delivered as aledger.appendedevent when a swap ends.- Its
detailis{ paysInvoiceId, swapId, state, usdcIn, eurcMinimum, eurcEstimated, eurcReceived, usdcPerEurc, costPercent, provider, adapter, approveTxHash, swapTxHash, failure, reasoning, resumed }. stateisconfirmedorfailed.eurcReceivedis the EURC the swap's transaction sent to the operating wallet, read from its receipt, ornullwhen it could not be read.resumedistruefor a swap whose first answer was lost, finished by a later cycle. Itsreasoningis thennull.- It carries
paysInvoiceId, notinvoiceId: it is not a decision on the invoice.
- Its
- New fields on a EURC payable's decision entry:
usdcDueWithin7Days;swapOffer:{ usdcIn, eurcEstimated, eurcMinimum, usdcPerEurc, costPercent, provider }when a swap was offered, ornull;swapUnavailable: why there was none, ornull;swap:{ swapId, state, usdcIn, eurcReceived, swapTxHash, reason }for the swap made to fund the payment, ornull.stateisconfirmed,pendingorfailed.decision.fundWithSwapistruewhen the model chose to pay with the swap. It appears only on a payable the wallet's EURC was short of.swapOffer.costPercentis what each EURC costs through the swap above the rate the payable was weighed at, in percent, rounded up to two places.
- Two new values of
guardrailRule:fx.swap_cost_above_cap, for a swap costing more than 3% above the quoted rate;fx.swap_usdc_short, for a swap that would leave the USDC below what falls due in USDC within 7 days.
- When a swap does not go through:
- A failed swap holds the payable with
guardrailRule: null, as a failed transfer does. Its reason is inswap.reason. - A swap still in flight at Circle leaves the payable
pending. The next cycle finishes the swap, then decides the payable again.
- A failed swap holds the payable with
2026-10-01: Milestone escrow
- A live workspace can lock a milestone's USDC in its own escrow contract on Arc testnet. Three new ledger actions, in the
contractordomain, are delivered asledger.appendedevents:escrow_deployed, once per workspace. Itsdetailis{ by, address, payer, deployer, circleContractId, txHash }.escrow_funded, when a milestone is locked. Itsdetailis{ by, milestoneId, contract, holdId, payee, amountUsdc, refundAfter, fundTxHash }.fundTxHashisnullwhen the hold was found already on chain and recorded.escrow_refunded, when a hold is taken back after its refund date. Itsdetailis{ by, milestoneId, contract, holdId, amountUsdc, refundTxHash }.
- A milestone locked in escrow is paid by releasing its hold rather than by a transfer. Its release decision's
execution.txRefis that release transaction on Arc testnet.
2026-10-01: Payment receipts
- An owner or admin can share a paid payable's receipt as a public link. Three new ledger actions, in the
apdomain, are delivered asledger.appendedevents:receipt_shared, appended once per payment, the first time it is shared. Itsdetailis{ receipt, records }and holds no names and no user ids:receiptis{ amount, token, paidAt, payee, chain, txHash, route, sourceTxHash?, feeUsdc? };chainandtxHashare where the payee was paid: the transfer on Arc testnet, or the mint on the payee's chain;routeisdirect,cctporgateway;sourceTxHashis a CCTP payout's burn;recordsis{ seq, hash }of the entry that recorded the payment.
receipt_link_renewed, when a new link replaces an earlier one or a revoked receipt is shared again. Itsdetailis{ by, receiptId }.receipt_revoked, when a receipt's link is turned off. Itsdetailis{ by, receiptId }.- None of the three carries
invoiceIdindetail: a receipt's entries are about sharing a payment, not deciding an invoice.
2026-10-01: Payouts from a Gateway balance
- A payout to a payee on another chain can go from the workspace's Circle Gateway balance as well as through CCTP. Its decision entry's
payout.routeis thengateway,payout.feeUsdcis Gateway's fee, andpayout.gatewayBalanceUsdcis the Gateway balance it was weighed against. - A decision entry for a USDC payout to another chain also carries
payout.quotes: { cctpFeeUsdc, gatewayFeeUsdc }: both routes' fees as read for that decision, eachnullwhen there was none.gatewayFeeUsdcisnullfor a workspace with no Gateway balance. - An
approval_paidentry for a new payment to another chain carries the samepayout, read when the person approved:{ chain, route, domain, feeUsdc, quotes }, withgatewayBalanceUsdcwhen the route isgateway. An approval that only records a transfer already sent carries none. - A payout keeps the route its first attempt took. A new value of
guardrailRule,bridge.gateway_balance_short, holds one that went through Gateway when the Gateway balance no longer covers it with its fee. - For a Gateway payout:
- the decision entry's
execution.txRefis the mint on the payee's chain, withexecution.destinationChainandexecution.mintTxHash. No Arc transaction belongs to one Gateway payout: Gateway burns on Arc in batches; GET /api/v1/invoicesreturns that mint as the invoice's transaction once it is paid. Until then the invoice ismatched, as a CCTP payout is.
- the decision entry's
- Three new ledger actions, in the
treasurydomain, delivered asledger.appendedevents:gateway_signer_created, whosedetailis{ by, signer, circleWalletId };gateway_delegate_added, whosedetailis{ by, depositor, signer, txHash };gateway_deposit, whosedetailis{ by, amountUsdc, depositor, signer, approveTxHash, depositTxHash, balanceUsdc }.balanceUsdcis the Gateway balance once it includes the deposit, ornullwhen Gateway had not counted the deposit yet.
2026-10-01: Invoices read from a document
- A
create_invoiceentry for an invoice read from a document carriesdocumentin itsdetail:kind:pdf,email(an.emlfile: its text and the text of its PDF attachments) ortext;sha256: the hash of the document's bytes, or of the pasted text;reader: the model that read it (anthropic,openai,deepseek), orheuristicfor the rule-based reader;changed: the form fields the member changed from what was read, amongamount,currency,dueDate,poReference,earlyPayDiscountPct,discountDeadline,memoandcounterpartyId.
- An invoice typed in carries no
document. The document itself is not stored.
2026-10-01: First screens in the compliance sweep
- Each entry in a
compliance_sweepentry'sdetail.screenednow carriesfirstScreen:truewhen the counterparty had no verdict before.changedis stilltruefor a first screen. - When a sweep includes first screens, its
summarycounts them apart: "Screened 6 of 6 counterparties: 6 for the first time, 0 changed". A sweep of re-screens only still reads "Re-screened 6 of 6 counterparties; 0 changed".
2026-10-01: Payees on other chains
- A counterparty's
chainis one ofARC-TESTNET,BASE-SEPOLIA,ARB-SEPOLIAorETH-SEPOLIA.GET /api/v1/counterpartiesreturns it as before. - A payee on a chain other than Arc testnet is paid from Arc through CCTP V2 with Circle's Forwarding Service.
- Its decision entry (
ap_pay,ap_hold,ap_flag_fraud,ap_request_info,ap_schedule) carriespayout: { chain, route: "cctp", domain, feeUsdc }indetail, wherechainis the chain id (BASE-SEPOLIA) andfeeUsdcisnullwhen Circle gave no fee. - Only a vendor can be paid on another chain; a contractor's milestones are released on Arc testnet.
- Its
executioncarriesdestinationChainandmintTxHashonce the payment was sent.execution.txRefis the burn on Arc testnet, orcctp:<Circle transaction id>while the burn has no hash yet. mintTxHashisnulluntil the Forwarding Service mints. A laterap_reconcileentry carries it.
- Its decision entry (
- Three new values of
guardrailRule, each of which holds the payable for a person:bridge.fee_above_cap, when the fee is above 10% of the amount;bridge.fee_unavailable, when Circle gave no fee;bridge.unsupported_token, for an invoice in EURC to a payee on another chain.
- A bridged invoice is
matchedfrom the burn until the mint, thenpaid.GET /api/v1/invoicesshows it so. One still not minted two hours after it was sent isheldfor a person, by anap_reconcileentry saying so.
2026-10-01: Invoices in EURC
-
An invoice can be in EURC as well as USDC.
GET /api/v1/invoicesreturns itscurrency, which is nowUSDCorEURC;amountandpaidAmountare in that currency. -
A EURC payable is paid in EURC from the operating wallet. Its decision entry (
ap_pay,ap_hold,ap_flag_fraud,ap_request_info,ap_schedule) carriescurrencyindetail, and, for EURC only:usdcValue, its value in USDC, which is what counts against the counterparty's limit;fx: { rate, source: "circle-stablecoin-quote", quotedAt, usdcMinimum }, ornullwhen Circle gave no quote;eurcBalance, the operating wallet's EURC as read for the decision, ornullwhen payments are simulated or the balance could not be read.
Those five decision entries carry
currency: "USDC"for a USDC invoice.ap_reconcilecarries nocurrency. -
Two new values of
guardrailRulefor a EURC payable, both of which hold it for a person:fx.rate_unavailable, when there was no quote to weigh it at, andtreasury.insufficient_eurc, when the wallet's EURC cannot cover the payment or could not be read. As with every rule,guardrailRuleis set when code refuses a payment or schedule the model decided on; a payable the model held itself for the same reason hasguardrailRule: null, withusdcValue: nullor aneurcBalancebelowobserved.amountin itsdetail. -
A duplicate is the same amount only in the same currency. A purchase order billed in one currency and again in the other is reported as
purchase_order_rebilled, which goes to the model and does not block on its own. -
approval_paidand thecreate_invoiceandimport_invoiceentries gaincurrencyindetail, and their summaries name it. -
The invoice CSV import takes an optional
currencycolumn. A blank or missing value means USDC.
2026-10-01: Milestones added in the app
- A member can now add a contractor milestone on the Contractors page. It starts
pendingand unverified, like any milestone inGET /api/v1/milestones. - Its
verificationSourceis the evidence link the member gave, ornull: a GitHub pull request in its canonical form (https://github.com/<owner>/<repo>/pull/<number>, which the cycle checks for a merge as before), or any otherhttps://address, which only a person verifies. - A new ledger action, delivered as a
ledger.appendedevent:create_milestone, in thecontractordomain withactor: "human". Itsdetailis{ by, milestoneId, counterpartyId, counterpartyName, amount, verificationSource }, withamountas the decimal string entered. - A new event kind,
milestone_added, may appear in an event cycle'sdetail.events. It is raised only for a milestone whose evidence is a pull request, so the merge is checked within a minute.
No /api/v1 endpoint, parameter or response schema changed.
2026-10-01: Payment timing
- An invoice can carry early-payment terms: a percent off and the date it lapses, set on the invoice form or by CSV import.
GET /api/v1/invoicesnow returnsearlyPayDiscount: { percent, deadline } | nullfor any invoice that carries them. - A payable the agent has committed to pay later reaches status
scheduled, decided again each cycle until the day it pays.GET /api/v1/invoices'sstatusfilter now acceptsscheduled. GET /api/v1/invoicesalso returnsscheduledFor(an ISO timestamp, ornull) andpaidAmount: what actually left, once an invoice's status ispaid.paidAmountstaysnullbefore then, even while a submitted transfer already carries an amount.- A new ledger action, delivered as a
ledger.appendedevent:ap_schedule, in theapdomain, recorded when the agent schedules a payable. Itsdetailcarriesdecision: { action: "schedule", payOn, reasoning, confidence },timingandtimingRule(the policy's figures, and any correction made to the model's date),requestedPayOnwhen a date was corrected,terms: { earlyPayDiscount },observed, andexecution.resultingStatus: "scheduled". - Every
ap_*decision entry (ap_pay,ap_hold,ap_flag_fraud,ap_request_info,ap_schedule) now carriestiming,timingRuleandtermsin itsdetail; an entry for a previously scheduled invoice also carriesscheduledFor. ap_payandapproval_paidgainamountPaidanddiscountTakenindetail: bothnullwhen nothing went out, or the discounted amount and the percent taken when an invoice was paid by its discount deadline.cycle_complete'sdetail.outcomesgainsscheduledCount.- A duplicate is now also refused against a payable already
scheduled, already being paid (matched), or being decided by a person on Approvals (processing), not only one alreadypaidorreceived. The refusing entry's reasoning names which of these it repeats.
2026-09-30: Payee links
- Two new ledger actions, delivered as
ledger.appendedevents, both in thecompliancedomain withactor: "human":payee_link_created, whosedetailis{ by, counterpartyId, linkId, expiresAt };payee_link_revoked, whosedetailis{ by, counterpartyId, linkId }.
- A
counterparty_address_changedentry made by a payee through a payee link hasby: null, and carriesvia: "payee_link"andlinkIdnext tocounterpartyId,fromandto. - Such a change waits for confirmation like any other; a later
counterparty_address_confirmedrecords who confirmed it.
No /api/v1 endpoint, parameter or response schema changed.
2026-09-30: What started a cycle
- A
cycle_completeledger entry'sdetailcan now carrytrigger, which says what started the cycle:"schedule"for the six-hourly run;"manual"for Run cycle now;"event"when something in the workspace gave the agent a decision to make.
- An event cycle's
detailalso carriesevents, the kinds that led to it:invoice_added,milestone_verified,payable_returned,address_confirmed,agent_resumedandsample_loaded.by, when present, is the person whose action started the cycle. - These fields are visible on
ledger.appendedevents and inGET /api/v1/ledger. - A cycle no longer starts while another one in the same workspace is running, so one workspace's cycles do not overlap.
No /api/v1 endpoint, parameter or response schema changed.
2026-09-30: Failed-payment retry
- An
approval_paidledger entry'sdetailcan now carryattempt, the transfer attempt this approval ended on (starting at 1), and, only when that attempt is a retry,retriedAfter:{ providerTxId, providerState, failureReason }for the earlier attempt Circle ended in a terminal failure. Both are visible onledger.appendedevents and inGET /api/v1/ledger. - Circle's
STUCKtransfer state is no longer a terminal failure; it is now reported the same way a transfer stillpendingis. OnlyCANCELLED,DENIEDandFAILEDend a transfer attempt.
No /api/v1 endpoint, parameter or response schema changed.
2026-09-30: Ledger signing key rotation
- An owner can rotate a workspace's ledger signing key from Settings. The rotation is a ledger entry, delivered as a
ledger.appendedevent like every other:ledger_key_rotated, in thesystemdomain, withactor: "human". Itsdetailis{ from, to, by }: the retired key's id, the new key's id, and the owner who rotated. See Key identity and rotation. - From that entry on,
signingKeyIdis the new key's id. Earlier entries keep the old id and keep verifying against the retired key. ledgerRetiredKeyCountinGET /api/v1/statusnow counts the workspace's own retired keys.
No /api/v1 endpoint, parameter or response schema changed.
2026-09-30: Counterparty limit changes
- A new ledger action, delivered as a
ledger.appendedevent:counterparty_limit_changed, in thecompliancedomain. Itsdetailis{ by, counterpartyId, from, to, currentLimit }, wherefromandtoare the configured limits in USDC andcurrentLimitis the limit the counterparty's risk now allows. A client's limit may benull.
No /api/v1 endpoint, parameter or response changed.
2026-09-30: Counterparty address changes
- Two new ledger actions, delivered as
ledger.appendedevents like every other entry, both in thecompliancedomain:counterparty_address_changed, whosedetailis{ by, counterpartyId, from, to }.fromortoisnullwhen there was no address before, or it was cleared.counterparty_address_confirmed, whosedetailis{ by, counterpartyId, address, via }.viais"approval"when approving a payment confirmed it, and"confirm"when someone confirmed it on the counterparty.
- An agent decision entry (
ap_payand the otherap_*actions) can now carryguardrailRule: "counterparty.address_unconfirmed". It means the payment was held because the counterparty's address changed and no one has confirmed it. Itsobservedobject gainsaddressUnconfirmed, a boolean. - An
ap_reconcileentry can now carrynotResubmittedBecause: "counterparty.address_unconfirmed", beside the existing"counterparty.high_risk". A payment that never reached the network is not sent again to a changed address that no one has confirmed; it is held for a person.
No /api/v1 endpoint, parameter or response changed.
2026-09-29: MCP server
- A remote MCP server at
https://www.vestiarion.xyz/api/mcp, over Streamable HTTP. An AI agent connects with a workspace API key, sent asAuthorization: Bearer <key>, and reads that workspace's records through one tool per/api/v1read operation. A tool returns the operation's JSON exactly. See MCP server. - A missing, malformed, unknown or revoked key answers
401with aWWW-Authenticateheader; a key without thereadscope answers403. - There are no write tools, and no OAuth.
No /api/v1 endpoint, parameter or response changed.
2026-09-29: Webhooks sent right after each append
- A
ledger.appendeddelivery now goes out right after the request that appended its entry (approving a payment, pausing the agent, changing a member, a key or an endpoint), not on the next scheduled dispatch. Entries appended by the agent's scheduled run still go out right after that run. The 10-minute schedule remains for retries. See Timing.
No payload, header or signature changed.
2026-09-29: Developer docs, OpenAPI document and llms.txt
- These docs, at
/docs: a quickstart, guides, and a reference page for every endpoint, with a Try it panel that sends a real request with your key. GET /api/v1/openapi.json: an OpenAPI 3.1 document for every/api/v1endpoint. It is public and needs no key.- A Markdown view of every page at its URL plus
.md, and/llms.txtand/llms-full.txt: see AI integration. - Cursors are checked per endpoint. A
cursorthat the endpoint could not have issued, such as a ledger cursor sent to/invoicesor an altered one, now answers400 invalid_request. It could reach the database before and answer500. See Pagination.
No other endpoint, parameter or response changed.
2026-09-29: Signed webhooks
- An owner or an admin can add up to 5 HTTPS endpoints per workspace in Settings. Each one receives a
ledger.appendedevent for every new ledger entry, and awebhook.testevent on demand. See Webhooks. - Every delivery is signed with HMAC-SHA256 in the
Vestiarion-Signatureheader, using the endpoint's own secret, shown once when it is added. See Verifying signatures. - Delivery is at least once and not in order. A failed delivery is retried up to 7 attempts in total, and an endpoint is disabled after 20 consecutive failed attempts. See Retries and disabling.
2026-09-29: Workspace API keys
/api/v1now authenticates with a workspace API key,vxk_<prefix>_<secret>, sent asAuthorization: Bearer <key>. An owner or an admin creates keys on the workspace's Settings page; each key is shown once and reads only its own workspace. See Authentication.- Breaking: the platform token,
AGENT_API_TOKEN, no longer opens/api/v1, and answers401. It had read only the founding workspace. Move any caller that used it to a workspace key. - A missing, malformed, unknown or revoked key answers the same
401 unauthorized. A key whose scopes do not cover a route answers403 forbidden. GET /api/v1/statusreports the calling key's own workspace, and itsconfigurationno longer includes the deployment'sdatabaseblock.