Skip to content

Orgabot is in private alpha. Request access, or join our Discord.

← All documentation
The organization

Accounting financial actions

How a payment, journal, credit, or expense is prepared, approved, executed, and confirmed, why the controls are re-evaluated at execution time, and why an unconfirmed action is a real outcome you reconcile rather than retry.

Accounting can evaluate financial controls, and it can act on them. The path is four separate steps, and their separateness is the point.

# What this would cost, evaluated. Writes nothing, sends nothing.
orgabot organization department accounting action preview <org> accounting payment 250000 USD \
  --preparer actor_ap --preparer-role accounts-payable \
  --approver actor_controller --approver-role controller

# Record a durable request. Still sends nothing.
orgabot organization department accounting action prepare <org> accounting payment 250000 USD \
  --connector conn_123 --reference INV-1001 \
  --preparer actor_ap --preparer-role accounts-payable \
  --approver actor_controller --approver-role controller

# Approve it. The preparer may never approve their own action.
orgabot organization department accounting action approve <org> <action-id> --by actor_controller

# Execute it. Re-evaluates the controls, sends, then reads the effect back.
orgabot organization department accounting action execute <org> <action-id>

orgabot organization department accounting action list <org>
# --provider-reference records the provider's id for the reversing effect.
orgabot organization department accounting action reverse <org> <action-id> \
  --reason "duplicate invoice" --by actor_controller --provider-reference rev_88

Amounts are whole minor units, so 250000 USD is $2,500.00.

On the dashboard, everything except executing

The accounting department's own page carries a Financial actions panel: preview, prepare, approve, reverse, and the listing of what this department has recorded. It is the same governed endpoints the commands above call.

Executing has no dashboard route, deliberately. Sending the effect needs a live gateway and a secret store, and it spends the organization's money against a third party, so the CLI - which holds both - is the way in. The panel says so in place rather than leaving a gap you have to discover, and approving tells you the same thing: approving is not executing.

The listing is organization-scoped, so the panel filters it to this department - plus every row Orgabot could not read, which names no department and is exactly the one you must not lose sight of. A failed load keeps the actions already on screen and reports the error, because this is where you find a payment you still have to settle.

The controls are re-evaluated at execution time

An approval is evidence of what was allowed when it was given, not permission to act now. The policy can have been reconfigured, a threshold raised, an assignment expired, or a role deactivated since. If the live evaluation refuses, nothing is sent and the action is recorded as refused with the reasons, which is a different thing to read than "this was never allowed".

The connector is re-read too. A suspended connector is a decision an operator made, and spending against it would be ignoring that decision.

An irreversible action needs its own approval, whatever the amount

A payment that left the account is not undone by an API call, so payments and credits require a separate approval even below the materiality threshold. "Small" is not the same as "reversible".

Separation of duties is enforced on the actor ids you supply: a preparer may never approve their own action. Those ids come from the command line, or from the dashboard request body, so on that surface the separation is between two identities one caller names rather than between two authenticated people. It is the same identity model the rest of the department commands use, and it is worth knowing on a surface that moves money: the audit line records who the request said acted, and the dashboard is localhost-only for that reason.

`unconfirmed` is a real outcome, and the important one

Money moving is the one effect Orgabot must not take its own word for. The provider call returning is not evidence, because a timeout after the provider committed looks identical to one before it. So the outcome is decided by reading the effect back through a separate call, and a read that fails leaves the action unconfirmed - neither succeeded nor failed.

There is one exception, and it is narrow. Some providers index the effect by their OWN id and answer nothing at all to a lookup keyed on ours, so the read-back can never confirm anything and every send would settle unconfirmed forever. When the read cannot answer AND the provider's own reply to the send named the created resource, that reply is adopted: the action settles on the provider's acknowledgement, and the audit line says so in those words rather than claiming a read-back that did not happen.

A reply that names no reference is not evidence of an effect and is discarded - with one exception. A reply that DEFINITIVELY declines the request (failed, rejected, cancelled) is an answer whether or not it names anything, so it is adopted too: the action goes back to approved and stays retryable. Without that, a payment the provider explicitly refused settled unconfirmed, and a manual reversal was the only way out of it.

That is a state you reconcile, not one you retry blindly. action list marks it and says so.

The record is marked unconfirmed before the request is sent, not after it returns. That is the honest order: from the moment the send begins, the effect may have happened, and a machine that dies in between must not come back believing nothing was attempted. A request the provider definitively rejects puts the record back where THIS run found it. If an earlier attempt had already left it unconfirmed, it stays unconfirmed: a rejection of this attempt says nothing about that one.

A 4xx is a rejection - the provider looked at the request and declined it, so nothing happened. A 5xx is not: it may have committed and then failed to answer, so it is settled by reading the effect back like any other unknown. A 2xx saying pending is a third answer again, and does not authorize a re-send.

A provider that rate-limits the request (HTTP 429) has not processed it, so that is recorded as a definitive rejection and the action stays retryable. A timeout (408) and an idempotency conflict (409) are not: the provider may have begun or already completed the effect, so both are settled by reading it back.

A run that claims a send leaves a claim on the record until it settles. While that claim is fresh - ten minutes - another run will not send, because the first one's request may still be open. Once it is stale the claiming process is gone; the record is still unconfirmed, so nothing is assumed about the effect, and a reconciling re-send is allowed again.

Retrying is safe, because retrying reconciles

The idempotency key is derived from the request's own id, so every attempt at the same request carries the same key and the provider's own machinery collapses them. Executing an unconfirmed action observes first, and only sends again if the provider ANSWERS that the effect FAILED. "Not found" is not that answer: while another run's request is in flight the provider has not recorded the effect yet, and a provider that treats the idempotency key as a header rather than a lookup path answers the same way for a payment it completed. A read that fails, or one that merely finds nothing, refuses rather than sending. A confirmed action is never executed twice.

A refused action is re-executable too, for the same reason: a refusal is usually a transient condition - stale controls after a settings change, an expired assignment - and it clears.

A reversal never erases what happened

It is a separate recorded act with its own reason and actor; the original record stays exactly as it was. An audit trail that rewrote itself would be a false account of what the organization did. You can reverse an unconfirmed action too - that is how you settle one either way after reconciling by hand.

An action Orgabot cannot read is an action nobody acts on

A record the store cannot decode is kept, marked unreadable, and refused by every command. Dropping it would permanently erase the record of a payment that may have happened, since the normalized document is what gets written back.

The provider is reached over TLS, or not at all

A financial action carries a bearer credential and an instruction to move money in the same request. The egress rule pins it to the connector's own host; this one additionally refuses a connector whose base URL is not https, because over plain HTTP both the credential and the instruction travel in cleartext.

Nothing here handles a credential

The request carries the connector's id, never its secret. Resolving that into a token happens behind the broker's own permission model at call time, and no credential material enters a record, a log, or an error.