Overview
The corporate actions catalogue is one collection, /, that records a named lifecycle event on an issued security, then runs the mechanism that applies it to holders. type names the legal event: a cash dividend, a bond coupon, a bonus issue, a rights issue, a capital decrease, a stock split, a spin-off, a maturity redemption, a distribution, a partial redemption, a conversion, a buyback, or a coordinated distribution across share classes. A supply change runs as create, preview and confirm, with abort to stop it, in place of minting or burning to each holder yourself. For the status lifecycle and the in-flight rule across all thirteen types, read the corporate actions catalogue reference. For the request and response fields of each route, use the v2 API explorer.
Create checks what the token carries before it records anything, and the asset type plays no part. A missing feature is refused with CORPORATE_. Missing capital, or a type the register-lines feature excludes, is refused with CORPORATE_.
The token read's capabilities lists what a token carries. A token linked to a template keeps the capabilities that template declared when the token was issued. A token with no template link gets them derived when it is read, from its asset type and its attached features: equity without an attached conversion feature has share-capital, fund and real-estate have unit-capital, and any other type has none. cashDividend and distribution are the same payout and are both accepted on any token with dividend-yield. A token with the register-lines feature, which the Console calls Booked movements, refuses the types the table marks, because its units move only through a booked movement.
Creating a cashDividend record, or a distribution paid in cash, already declares the dividend on chain through the dividend-yield feature. Review the approved terms before create. Preview and confirm happen after that declaration, and the payout eligibility check runs before it. Preview of a cash dividend is refused with snapshot-not-ready (HTTP 409, DALP-12051) until the record date has passed on chain and its block is final. Send it again after that. A declared dividend can still be cancelled until its record-date snapshot is recorded.
GET /api/ reports the payout asset's available funding, the unclaimed amount already payable, and the projected amount committed to other dividends. When you read it for a corporate-action plan, add excludeDividend with that plan's dividend id so the projection does not count the plan itself.
Three types name an attached feature program (bond coupon, maturity redemption, conversion): a holder claims yield, redemption, or conversion on that feature's own routes afterward, not through this catalogue. A bond coupon and a conversion complete the moment you create them. A maturity redemption calls the bond's mature() on create. The record stays executing until that transaction lands, then reads redemptionOpen. Create answers 202 with the executing record while mature() is queued, unless Prefer: wait let it land within the request. Read the record, or replay the same Idempotency-Key, to see it settle. Holders then redeem their own tokens. Nothing is paid to them automatically. The record stays redemptionOpen while any token of the bond is outstanding, and reads completed once every holder has redeemed and the bond's supply is zero. A bond with no supply left when mature() lands reads completed at once. Create is refused with maturity- (HTTP 409, CORPORATE_, DALP-14049) before the maturity date on chain, and with bond-already-matured (HTTP 409, CORPORATE_, DALP-14050) once the bond has matured. Both carry data.maturityDate. Abort of an executing maturity redemption is refused while its transaction may still land, and returns the record redemptionOpen when the bond has matured. Abort of a redemptionOpen record is refused, because the bond has already matured. A redemptionOpen record is not in flight and raises no webhook event. A completed record raises the usual completion event.
Seven types use stored holder allocations, and this page calls them the composition types: bonus issue, rights issue, capital decrease, stock split, spin-off, partial redemption, and buyback. Most apply mint or burn batches. A rights issue sent with path: "token instead adds holders to the whitelist of the token sale named by tokenSaleId. With no numerator and denominator it adds every holder on the book and mints nothing: quote, preview and confirm list each holder with an amount of 0, and filter[receives]=true keeps all of them. With a ratio it adds the holders the ratio entitles. Half a ratio is refused. A bonus issue, an allotment rights issue, and a spin-off mint the floored ratio amount itself.
A rights issue sent with path: "tradable" mints whole rights, in proportion to the holders on the recordDate, into an existing rights token. The share balances do not change. The terms name the rightsTokenAddress, the ratio, exDate, recordDate, tradingEnd, the denominationAsset and subscriptionPrice a holder pays later, the payee wallet that receives each payment and the exercised rights, and the subscription window subscriptionStart to subscriptionEnd. referencePrice is optional. recordDate is required, and the dates must run in this order: exDate before recordDate, recordDate before tradingEnd, tradingEnd before subscriptionEnd, and subscriptionStart on or after recordDate and before subscriptionEnd. The end of the subscription window is when the rights expire. The rights token must be empty, carry the hold feature, and use the same decimals as the share token. The subscription asset must be a token indexed in this system. Create, preview, and confirm refuse a subscription asset that is the share token or the rights token with rights- (HTTP 409, CORPORATE_, DALP-184651). Each holder receives the balance times the ratio, rounded down to a whole right. The record's childTokenAddress is the rights token. Create, preview, and confirm refuse a rights issue once tradingEnd has passed with rights-trading-ended (HTTP 409, CORPORATE_, DALP-184652). Abort the rights issue and declare a new one to correct either terms field. A second operator approves the plan before confirm when the organisation has turned on the corporate-actions approval, as Approve or reject a plan describes. A spin-off adjusts the ratio for any difference between parent and child token decimals. A stock split mints or burns the difference between that amount and the holder's current balance. A capital decrease, a partial redemption, and a buyback burn the amount you announce per holder.
A bonus issue, a rights issue, a stock split, and a spin-off count holders when preview runs, and accept an optional recordDate. Without it, preview reads the holder book at the moment it runs. With one, preview is refused while that date is still ahead of the chain time the indexer has reached. Once the date is reached, preview reads the holders at that date from the token's historical-balances feature. A date before the feature began is refused. For a token without that feature, preview reads the current holder book while the indexed chain time is within one minute after the date, and is refused later. A preview that reads the current holder book waits until the indexer has caught up with chain finality as it stood when the preview started, and is refused as unavailable only if that takes longer than 30 seconds. The stored snapshot recordDate is the instant whose holders were counted: the record date when preview read the holders at it, otherwise the instant preview read the book. Preview reads the whole holder book whatever its size, and refuses rather than plan from a partly read one.
On a mirror token, preview of any corporate action is refused with reconciliation- (CORPORATE_, HTTP 409, DALP-184676) while a reconciliation break on the token, first seen on or before the record date, is open or under investigation. Without a record date, the day preview runs counts as the record date. Nothing is stored. Resolve the break, or write it off through its approval, then send preview again. See Mirror tokens.
A cashDividend, bondCoupon or distribution sent with payment pays holders in new units of the token instead of cash. An in-kind cashDividend is a stock dividend. It carries numerator, denominator, and an optional recordDate, and none of the cash terms. It then runs as a composition type: it mints the floored ratio amount to every snapshot holder, takes the token's in-flight slot, and needs a second operator's approval before confirm when the organisation has turned on the corporate-actions approval. Its recordDate follows the same rule as a bonus issue. Omitting paymentKind means cash, which keeps the behaviour described above.
Check payout eligibility before declaring
Declaration is refused with CORPORATE_ (DALP-14021) when the check finds a holder with a positive payout whose claim would fail because the payout token refuses the transfer from the treasury. A holder fails when it is not verified in the payout token's identity registry, or when the payout token's compliance refuses that holder's payout. On a V1 payout token, or a registry that only records membership, the registry test is whether the holder is registered. When the payout token charges a transfer fee, the net payout and the fee leg to the fee recipient are each checked.
A record date in the future is checked against current holders and balances, and a past record date against the holders and balances on that date. Holders whose identity is marked lost are skipped, because the snapshot pays them nothing. The treasury and venue fee accounts block a declaration only when they also hold the dividend token at the record date and so receive a payout. The check probes the largest 250 holders, by balance and then address. A probe that gets no answer in time does not block. A declaration that passes can therefore still leave holders whose claims fail: a holder outside the largest 250, a holder whose probe timed out, or a holder whose verification changes after declaration.
When the payout token charges an external transfer fee in a separate fee token, the treasury pays it on every claim, so its fee-token balance and its allowance to the fee feature must cover one fee per paying holder, including paying holders beyond the largest 250. The claims beyond that capacity are refused. What is left for the new dividend excludes what the same treasury's other open dividends still need. An open dividend that charges its fee in the same fee token reduces the available balance, unless the treasury itself receives that fee. One that uses the same fee feature reduces the available allowance. When an open dividend pays out in the fee token itself, its unclaimed payouts reduce the available balance.
The refusal's ineligibleCount says how many holders failed. To see each party's reason, call the denomination eligibility read, GET /api/, which asks for the token's governance role. It lists the largest 250 holders and sets holdersTruncated when there are more. It also lists the treasury and venue fee accounts, even when they do not block. Because the fee count covers every paying holder, ineligibleCount can exceed the parties the read lists.
Create, preview, confirm
A composition type follows four steps: create, preview, confirm, and, if needed, abort. A partial redemption, a stock split, a bonus issue, a rights issue, a spin-off, a capital decrease, a buyback, and a dividend, bond coupon or distribution paid in kind, add a fifth between preview and confirm when the organisation has turned on the corporate-actions approval: a second operator approves or rejects the previewed plan, as Approve or reject a plan describes.
curl -X POST "https://your-instance.example.com/api/v2/tokens/0x8a791620dd6260079bf849dc5567adc3f2fdc318/corporate-actions" \
-H "X-Api-Key: sm_dalp_..." \
-H "Idempotency-Key: split-2026-09-03-001" \
-H "Content-Type: application/json" \
-d '{
"type": "stockSplit",
"direction": "forward",
"numerator": 3,
"denominator": 1
}'The returned record includes the fields below. This excerpt omits timestamps, related identifiers, approval and appliedSplit:
{
"id": "0198f1a2-3c4d-7e8f-9a0b-1c2d3e4f5a6b",
"tokenAddress": "0x8a791620dd6260079bf849dc5567adc3f2fdc318",
"type": "stockSplit",
"status": "drafted",
"terms": {
"type": "stockSplit",
"direction": "forward",
"numerator": 3,
"denominator": 1
},
"snapshot": null,
"holders": [],
"transactionHashes": []
}For a composition type, create stores the event as drafted without applying the supply change. A composition type takes only one in-flight record per token: creating a second composition record while the first is not completed or aborted is refused with CORPORATE_ (HTTP 409, DALP-12067), and the same code refuses a primitive mint or burn on that token while the record is in flight. Wait for the open record to reach completed, or abort it, then send the call again. A second spin-off or tradable rights issue that would mint into the same token as an open one is refused with the same code. Send Idempotency-Key on create so a retried request replays the same record instead of declaring the event twice. See Idempotency and async settlement for the header contract that every mutating route on this catalogue shares. Reusing a key with different terms is refused with CORPORATE_ (HTTP 409, DALP-12065), because replaying would change what the first request created. Send a new key for a new create, or repeat the original request unchanged.
Preview computes and stores the allocation snapshot without applying anything:
curl -X POST "https://your-instance.example.com/api/v2/tokens/0x8a791620dd6260079bf849dc5567adc3f2fdc318/corporate-actions/0198f1a2-3c4d-7e8f-9a0b-1c2d3e4f5a6b/preview" \
-H "X-Api-Key: sm_dalp_..." \
-H "Content-Type: application/json" \
-d '{}'Ratios travel as two positive integers, numerator and denominator, on every write and read. The API takes no percentage. To send a ratio announced as a percentage, write the percentage as an integer over a power of ten and reduce it: 10.59% is 1059 / 10000, so send numerator: 1059 and denominator: 10000, and 20% is 1 / 5. The Console accepts a percentage with up to four decimal places and converts it the same way before it calls the API.
The preview response contains corporateAction, including its snapshot and holders array. Each holder entry has an address, amount, state, and balance, the holder's balance at the snapshot. A plan stored before balance existed answers balance: null. Nonzero allocations start as remaining. Amounts that floor to zero start as zeroAfterFloor. Per-holder amounts round down: a ratio type computes floor(balance × numerator / denominator) for each holder, and the record's remainder is the sum over holders of (balance × numerator) mod denominator, counted in denominator-ths of a base unit rather than in whole base units. A holder whose amount floors to zero is stored as zeroAfterFloor and never receives a chunk. Read the computed snapshot against the approved terms before you confirm. Nothing has moved yet.
Confirm applies the stored preview. It fails without one for a composition type. Confirm answers 200 with the record when nothing it sent is still in flight, and 202 with the same record and Location set to the record read while the record is executing. See Idempotency and async settlement for how Prefer: wait changes that. A partial redemption or buyback with a cash leg sends one unit at a time, each unit paying and burning together: when a unit is still queued at the end of the request, send the same confirm again once it lands, and the run continues from the next unit. A supply change can require multiple on-chain batches. Follow an accepted transaction handle and read the corporate-action record to reconcile the holder states. A plain read of a record that is still executing, whether a get or a list, reconciles it against the batches that landed: the record reads completed once every batch has settled, and aborted when a batch failed. A record that marked custodial contracts reads releasing first, as described below. A completed mint or burn composition has applied every snapshot holder, recorded a zero allocation, or skipped a holder whose election takes nothing. A pause or missing historical-balance watermark blocks confirmation. A record whose batch failed ends aborted and cannot resume. Review any applied holders before starting another action. A buyback sent with call: "all" is refused with CORPORATE_ (DALP-12056) when holder balances have moved since preview, so abort it and raise a new call.
Some plans leave value behind, so confirm needs acknowledgements, one entry for each consequence the stored plan has: not-declared for a class whose dividend is not declared, residue for money a declared class leaves in the waterfall, excluded-holders for a holder left out or rounded to zero, and underfunded for a funding shortfall. The platform reads these consequences from the stored plan, not from the request. When an entry is missing, confirm is refused with CORPORATE_ (HTTP 409, DALP-14107) and data. lists the missing entries. The rule is the same for a plan a second operator approved: that approval does not acknowledge anything for the operator who confirms. A record that is already executing or settled is not checked again, so a confirm that resumes a run does not need them.
Abort stops a drafted, previewed, approved, or executing record. Status becomes aborted, or releasing first for a record that marked custodial contracts, and the platform submits no further chunk. A chunk already submitted when abort lands can still apply, so read the record's holders before you raise a follow-up. Holders already applied when abort lands keep their change: abort does not roll back. For a composition type, confirm of a completed record answers that record, and so does confirm of an aborted record, so replaying the confirm Idempotency-Key after an abort returns the aborted record and runs nothing. A record that a rejection or a lapsed approval ended is the exception: confirm refuses it.
An action with a custodial step can mark custodial contracts with a freeze when it is created: a stockSplit, a bonusIssue, a non-tradable rightsIssue, and a cashDividend, distribution or bondCoupon paid in kind. A record that marked custodial contracts, or whose plan re-sizes a custodian's records, never ends straight from an open status: an abort, a rejection, a lapsed approval and a finished run all move it to releasing first. releasing keeps the token's one open composition slot, so a create of a composition or an in-kind payout on the token answers 409 naming the releasing record. Mint, burn and cash payouts are not held. The record leaves releasing only once a chain read of every marked contract finds none carrying the freeze and no freeze request the record queued can still land. A freeze request that failed, or was dead-lettered before it reached the chain, cannot land and does not hold the record. A freeze that lands after the record ended aborted takes it back to releasing, and the next abort of it releases it. Its releasingTo field names where it goes: aborted after an abort, a rejection or a lapsed approval, and completed after a finished run. A get or list of the record runs that check, and so does the Actions inbox. A chain or queue read that fails leaves the record releasing.
Once a run has started moving the units behind custodial records, abort refuses it with run-completed (CORPORATE_, DALP-14072) and lifts no freeze: the records stay frozen until the run finishes, so confirm the action again to finish it. This also holds when the abort arrives while confirm is starting that run.
Any wallet that holds the token's supply management role releases the record by sending abort on it with wallet verification. On a record bound for aborted, that call writes the custodial step to each contract whose own units the run already moved, and queues a lift of every other freeze the record marked, including one on a contract that drained before the record date. A run whose holder batch failed part way is bound for aborted, never completed, and a contract whose units never arrived takes no step. On a record bound for completed, it sends again every custodial step that has not landed and lifts the freeze only on a contract that took no step, because a step that lands clears its contract's freeze. Abort sends no second step, catch-up or lift whose same call still waits in the queue or for a custody approval. Every record response carries releaseInFlight: true while a lift, custodial step or catch-up of the release waits, false when none does or the record is not releasing, and null when the queue could not be read, so whether one waits is unknown. Each abort sends its calls under idempotency keys of its own, so an abort after a failed or dead-lettered request sends it again, and a step that cannot be sent does not stop the lifts. Until the chain is clear, abort answers the releasing record. With Prefer: respond- the lift is queued and the record stays releasing until it lands. The next read, or another abort, ends it. A lift that fails or is cancelled leaves the record releasing, and another abort sends a new lift. Abort of a releasing record whose freezes are already lifted answers the ended record, and two aborts at once end it once and both answer the record. A record whose create never placed a freeze on chain, with no freeze request left in the queue, ends in the same call. An abort sent while the create is still queueing its freezes answers a releasing record that keeps the token until those freezes are queued and lifted. A record whose create stopped before it recorded its freezes stops waiting for them 30 minutes after it was created. When a freeze lands after a record already ended aborted and a newer action holds the token, abort lifts it once it has landed and is refused while it still waits in the queue.
A cash dividend or cash distribution declares its dividend on chain when you create it, and abort does not cancel that dividend. Abort of such a drafted, previewed, or approved record is refused with 409 CORPORATE_ while the dividend is not cancelled. The error's data.dividendId names it. Cancel it with POST /api/ and {"transition": "cancel"}, then abort again. A dividend whose snapshot is already recorded can no longer be cancelled, so confirm that record to complete it instead.
Approve or reject a plan
An organisation can require a second operator before confirm for a partialRedemption, a stockSplit (forward or reverse), a rightsIssue, a capitalDecrease, a coordinatedDistribution, a buyback that declares a cash leg, a cashDividend or distribution paid in cash or in kind, and a bondCoupon paid in kind, because none of them can be undone after confirm. The requirement starts off: it applies once the organisation turns on the corporate-actions row of its approval policy, and it is waived while the organisation has no owner other than the author. Every record carries an approval block. Its policy reads required when the record waits for that decision. Its state reads pending until someone decides. Its proposerUserId is the user who opened the approval, and its lastPreviewedBy is the user who previewed the plan last. Neither can approve it. Its planDigest identifies the plan the record holds now. Its approvedByViewer is true when the caller's own approval is already recorded on the record's current approval request. With an approval policy that needs more than one approver, state stays pending after that approval until enough others approve, and the caller does not decide again. Its approvalsNeeded counts the approvals the pending approval request still needs, and reads null when no approval request is pending. Other types read not_required or not_applicable and confirm exactly as Create, preview, confirm describes.
After preview, a different person reads the stored plan and sends its planDigest back as reviewedDigest to POST .../ with decision: "approve":
{ "decision": "approve", "reviewedDigest": "3b8f1d2c4a5e6f708192a3b4c5d6e7f8091a2b3c4d5e6f708192a3b4c5d6e7f8" }Approve moves the record to approved, and confirm can then run it. Reject, on .../decisions, takes the same digest and a required, non-empty reason. It ends the record aborted with approval state denied (releasing first when it marked custodial contracts, see abort) and keeps the reason on the record. Neither call signs or queues a transaction for the action itself, but each needs a wallet step-up, like every other corporate action write. A cash dividend or cash distribution is the exception: rejecting it, or sending a decision after its approval window closed, first cancels the dividend it declared, through the same dividend-yield cancel and with the checker's wallet, so the checker needs the token's governance role for that cancel. While the cancel is refused or still queued, the record stays previewed, the rejection stays recorded, and the decision answers 409 CORPORATE_ with data.dividendId. A rejected record cannot be previewed again. It ends once that dividend is cancelled. A decision without a wallet step-up is refused before anything is decided. A decision sent with an API key needs no step-up.
The rules a decision must pass:
- The checker is an organisation owner or a platform administrator. The checker can decide from a signed-in session or with their own API key. An impersonated session cannot decide.
- The checker did not create the record and did not run its last preview.
- The digest still matches the stored plan. A plan that moved since the checker read it is refused.
- Approve is refused when the stored preview shows the paying treasury short of the cash leg.
A refused decision answers DALP-12165, and data.violation names the rule that failed.
A decision stays open for 72 hours from the last preview, or until the holder election's deadline when that is later. Each new preview replaces the plan, clears any earlier decision, and restarts that window. A late decision from an eligible decider ends the record aborted (releasing first when it marked custodial contracts) with approval state expired. A create on the same token does the same to a record whose window has closed, so a forgotten decision does not hold the in-flight slot. On a cash dividend or cash distribution, the dividend is cancelled first with the wallet proof of that decision, of an abort of the record, or of a coordinated distribution create on the same token. If that cancel is refused or still in flight the record stays open: a decision or an abort answers dividend-declared, and a create answers its conflict. Reads never send a transaction, so a lapsed record nobody acts on stays previewed and its dividend stays declared. The first read after the dividend is cancelled ends the record.
Confirm answers DALP-12052 when the record holds no preview it can confirm: a record in drafted, such as a coordinated distribution whose new preview started and has not finished, or a confirm of a previewed cash dividend sent without the root and totals its preview returned. Preview again, then confirm.
Confirm refuses a buyback with call: "all" whose balances moved since preview, and a rights close whose exercises or rights held changed, with CORPORATE_ (HTTP 409, DALP-12056). It refuses a stock split whose share price moved with CORPORATE_ (HTTP 409, DALP-12053). The record keeps its status and the token's in-flight slot. A refusal before execution applies nothing: preview a previewed record again. An approved record cannot be previewed again, so abort it and raise a new action, which needs a new approval while the gate is on. A rights-issue close refused after its new shares were minted keeps them, so abort it and raise the next close with followsUpOn set to the aborted one, which mints no share again.
The gate follows the corporate-actions row of the organisation's approval policy (PUT /api/), and that row starts off, so an organisation turns the gate on there. Turning it off again needs the caller's wallet verification. While the organisation has the Administration approval or any protection group on, the change also waits for another owner's or a platform administrator's approval. The CORPORATE_ setting reads and writes the same row. An organisation with no owner other than the author cannot staff the gate either. In both cases the record opens with policy not_required and state not_required, and waiver names the reason: organization-disabled or too-few-approvers. Confirm checks that answer again, except on a cash dividend or cash distribution, which keeps its waiver. When the organisation can staff the gate at that point, confirm opens a pending decision on the record, which stays previewed, and refuses the run until a second operator approves.
A partial redemption can also declare a cash leg. Send denominationAsset, pricePerUnit, and payingTreasury together on create, and confirm from the paying treasury. Tradable rights, cash legs and issuer calls describes how the cash and the burn settle.
Holder elections
A supply change can let each holder choose whether to take part. Send election in the create or quote body, or inside terms on commit, with a deadline in the future, at least two options, and a defaultOption for holders who do not choose. Each option has a key, a label and a settlementLeg: exactly one option is primary, which takes the allocation, and the others are none, which take nothing. Create refuses a deadline that has passed with election- (DALP-12175). Only a type with one settlement leg can carry an election, so create refuses one on a type with no stored allocation, on any action with a cash leg (a partial redemption or buyback that pays cash, or a reverse split that pays cash for fractions), or on a rights issue through a token sale, with election-shape-invalid (DALP-12176). An election with fewer than two options, duplicate keys, no primary option or more than one, or a defaultOption that names no option fails request validation instead. An illustrative election:
{
"election": {
"deadline": "2026-12-01T12:00:00Z",
"options": [
{ "key": "take-part", "label": "Take part", "settlementLeg": "primary" },
{ "key": "decline", "label": "Decline", "settlementLeg": "none" }
],
"defaultOption": "take-part"
}
}Holders choose between the preview and the deadline:
GET .../returns thecorporate- actions/ {corporate Action Id}/ election deadline, theoptionsand thedefaultOption. An action without an election answerselection-absent(DALP-12179).POST .../records a holder's choice, withcorporate- actions/ {corporate Action Id}/ election holder,option, thewalletVerificationand anIdempotency-Key. The verified wallet must be the holder named in the body, and the holder must be in the stored plan with a positive amount. A second submission before the deadline replaces the first. It is refused withelection-closed(DALP-12171) when the record is notpreviewedorapprovedor the deadline has passed,election-option-invalid(DALP-12172) for an unknown option,election-(DALP-12173) for a holder outside the plan, andholder- ineligible election-(DALP-12174) when the verified wallet is not that holder.holder- unauthorized GET .../returns the holder's recorded choice, orcorporate- actions/ {corporate Action Id}/ election/ choice/ {holder} choice: nullbefore they choose. The holder must be one of the caller's own wallets. Another address answerselection-(DALP-12174).holder- unauthorized
The operator follows the vote with two reads, which need the role that runs the action on the token, supply management for a supply change or a payment in kind:
GET .../counts the eligible holders, those who chose and those left on the default, and per option the holders and the amount.election/ tally entriescounts holders who did not choose under the default option, so it matches what confirm applies.submittedEntriescounts submitted choices only. An option withsettlementLegnonetotals an amount of0, the change it causes, the same as in the outcome.GET .../lists each holder with the option that applies, whether it is the default, and whether the holder stayselection/ outcome remainingor isskipped.finalis false before the deadline, when the outcome is still provisional. A person who may decide the action's approval, or who decided it, can read the outcome without the action's role.
Once a holder has chosen, or once the deadline has passed, preview is refused with election-locked (DALP-12178), so the plan the holders chose against stays fixed. A lapsed approval on such an action therefore cannot be reopened by previewing again: abort the record and record a new one. Confirm is refused with election-open (DALP-12177) until the deadline has passed. Confirm then skips every holder whose choice, or default, is an option that takes nothing. All election-* refusals answer HTTP 409.
Exercise and close tradable rights
Once a tradable rights issue reads completed, its rights exist, and a holder can exercise them during the subscription window. The exercise pays for new shares. The shares are minted when the issuer closes the issue. POST .../ takes rights, the rights to exercise in the rights token's base units, with the walletVerification and an Idempotency-Key. rights must be a whole number of rights, so 5 rights on an 18-decimal rights token is "5000000000000000000". The exercise runs from the caller's own wallet. One transaction moves the subscription price for each whole right, in the subscription asset, and the exercised rights, from the holder to the payee. The answer carries the rights, the cashAmount, the denominationAsset and a status of submitted, settled or failed. A repeated request with the same key returns the same exercise and moves nothing twice. The same key with a different wallet, issue or amount is refused with CORPORATE_. When the earlier exercise under that key failed, the replay is refused with rights-exercise-invalid, and a new key starts a new exercise. An exercise needs no second approver.
The exercise is refused with rights-window-closed (DALP-184671) outside the subscription window, with rights-exercise-invalid (DALP-184672) for more rights than the holder holds, too little of the subscription asset to pay for them, a fraction of a right, a record that is not a completed tradable rights issue, an issue that names no payee, or the payee exercising itself, with rights- (DALP-184673) when the holder cannot receive the new shares, and with rights-payee-invalid (DALP-184675) when the payee no longer qualifies to receive the payment. All of them answer HTTP 409.
GET .../ returns what that holder has exercised and settled: rights, cashAmount, denominationAsset, the shares those rights buy, and sharesIssued, the shares already minted to the holder. It also returns the excess shares the holder applied for (excessShares), what they paid for them (excessCashAmount), and, once a close has been confirmed, the excess shares allotted (excessAllocated) and the refund (excessRefund). Both are null before then. The holder must be one of the caller's own wallets. Another address answers not found.
Apply for excess shares
A holder who has exercised every right they hold can apply for more shares than their rights buy. POST .../ takes shares, the excess shares in the share token's base units, with the walletVerification and an Idempotency-Key. shares must be a whole number of shares. The application pays the subscription price for each share, in the subscription asset, into escrow with the organisation's operations signer, not to the payee. The answer has the same shape as an exercise, with kind: "excess" and the shares applied for in rights. A holder can apply more than once during the subscription window. Their applications are added together.
In the Investor portal, the rights issue card on the holding page offers Apply for excess shares to a holder that has exercised every right while the subscription window is open. The sheet takes a whole number of shares, shows the price in the subscription asset and confirms with the wallet PIN. After the application the card shows the excess shares applied for and what the holder paid, and once the close is confirmed, the shares allotted and the refund.
An application is refused with rights-exercise-invalid when the holder still holds a right or never exercised one, for a fraction of a share, or for too little of the subscription asset. It is refused with rights-window-closed outside the subscription window and with rights- when the holder cannot receive new shares.
Excess shares come only from lapsed rights: the rights still held when the close is previewed, less the exercised rights the payee holds. The close shares them out like this:
- When the lapsed rights cover every application, each applicant gets every share they applied for.
- Otherwise each applicant gets the lapsed rights times their share of total excess demand, rounded down to a whole share. The shares left over by rounding are not issued.
- The quote's
rightsExcesslists each applicant with the sharesappliedandallocated, the amountpaidinto escrow, and therefund, so the approver sees each holder's allotment before approving. The approval covers every entry.
Confirm mints the allotted excess shares with the exercised shares, in the same approved batch. The issuer's maker raises the close, and when the organisation turns on the corporate-action approval a second operator approves it, so issuing excess shares sits under the same maker-checker as the rest of the close. Once the mint and the burn have landed, the same confirm has the escrow pay the allotted shares' price to the payee and refund the rest to each applicant, in the subscription asset. Each entry in snapshot.rightsExcess reads remaining until then and applied after. When the escrow transfer is still in the queue, or the escrow cannot send it, the close stays executing, and confirming it again finishes the refund. When the applications or the lapsed rights change after preview, confirm refuses the close with snapshot-drift.
After the subscription window ends, close the issue with a new rights issue on the share token sent with path: "close" and the rightsIssueId of the tradable issue. Its plan lists each exercising holder with the shares they receive, and the quote's rightsBurn lists the rights it burns. Confirm mints one new share per exercised right, then burns every right, exercised or lapsed. Create, preview and confirm refuse a close with rights-close-not-ready (DALP-184674) before the window ends, while an exercise is still settling, or when another close of the same issue is open or finished. Raise a close after an aborted close that already minted shares as a follow-up of it, so the holders already minted are skipped. When the exercises or the rights held change after the close is previewed, confirm refuses it with snapshot-drift.
Check a create before sending it
POST /api/ takes the same body as create and answers { "action": "create when the platform would accept it now. It stores nothing, queues nothing and needs no wallet verification or Idempotency-Key. When the state refuses the action, it answers the refusal create would return for the same state: a run already in flight for an action that settles by composition, a token that cannot run the action, a missing token role, or terms that do not fit the type. A cleared check does not guarantee the create, because the state can change before you send it, and create then returns its own refusal. The Console calls it while an operator fills the declaration and program-link sheets.
Quote, then commit in one call
A quote shows what an action would do before you record it. A commit then records, previews and confirms it in one call, with one wallet verification instead of one per step.
POST /api/ takes the same body as create and stores nothing. It needs no wallet verification and no Idempotency-Key, and it asks for the same token role as create. The answer lists the plan:
holderCount,totalAmount,remainderanddenominationShortfall.treasuryUnitsExcluded, the units the treasury held at the record date that a cash dividend or distribution leaves out, or null.rightsBurn, only on the first page of a rights close: the rights the close burns once it has minted the new shares.priceAdjustment, which is null except for a stock split of a share that has a reference price. It carries thecurrencyand the pricebeforeandafterthe split. The split multiplies the price by the denominator and divides it by the numerator, rounded half away from zero at 8 fraction digits, the precision of the price feeds, so an 18:1 reverse split moves 6.00 AED to 108.00 AED and a 3:1 split of 10 AED plans 3.33333333 AED. Quote and preview need only the role the split needs, and refuse a feed that cannot be re-priced withprice-unadjustable(HTTP 409,CORPORATE_, DALP-184653). Confirm needs the tokenACTION_ PRICE_ UNADJUSTABLE adminrole on top of the supply role, refuses withpreview-mismatchwhen the share's price moved since the preview (abort the split and raise it again), and sets exactly this price once every chunk of holder units has landed. The record'ssnapshot.priceStepcarriesstate(pending,submitted,settledorfailed),attempt,transactionRequestIdand, when the step failed, afailurethat names why and afailureCode(drifted,refused,queue-failedorstored-unreadable). The action staysexecutinguntil the price settles. Confirming afailedstep sends the price again once the share's price is back at the approvedbefore, or already at the approvedafter. While the price is anywhere else, confirm refuses withprice-drifted(HTTP 409,CORPORATE_, DALP-184654), and the message names the price to set. Once every chunk of holder units has landed, abort refuses withACTION_ PRICE_ DRIFTED run-completed, because only the price update remains. If an abort raced the last chunk and that chunk landed anyway, the action staysabortedwhile the price update is sent, and afailedstep on it is confirmed again the same way: the step moves on and the status staysaborted. On anabortedaction, confirm does nothing but that retry: unless the step isfailedand every holder update landed, it answers with the record unchanged. A split whose ratio leaves the price unchanged at 8 fraction digits is refused withprice-unadjustable. The price history stays as it is. The venue keeps its own price-band anchors.data, a page of holder rows withholder,balance,amountandstate. Page it withpage[offset]andpage[limit], up to 200.acknowledgements, the consequences the commit has to acknowledge.approval, withrequirementset torequired,not-neededorwaived.waivedcomes with the reason.quoteFingerprint, which equals theplanDigestthe stored preview of the same plan gets.coordinated, only for a coordinated distribution: every class dividend the commit will declare. It names thewaterfallFeature, thedenominationAssetof the pool, theappliedDeterminationKind(null for the ordinary split), thepoolAmountit moves, and one entry per class withclassToken,role,rank,allocation(the slice the waterfall split gives it),amountPerShare(what its class dividend pays),included,holderCounton the record date and anotDeclaredreason when it pays nothing.totalAmountis thepoolAmount, and each holder row carries theclassTokenthat pays it, so one holder appears once per class.
GET .../ reads a record that exists. An approved record answers its stored plan, with the balance the preview kept for each holder, or null for a plan stored before it did and for a cash dividend or distribution. A drafted or previewed record answers a fresh computation. A cash dividend or distribution can only be quoted this way, once its record date has passed. The terms of a bond coupon, a maturity redemption, a conversion, or a cash dividend or distribution have no quote before the record exists. A quote of them is refused with CORPORATE_ (HTTP 409, DALP-14427). A coordinated distribution is quoted before it is recorded, once its record date has passed. Before that it keeps the preview's refusal, CORPORATE_ (HTTP 409). Its fingerprint covers the split every class dividend will run and the pool it moves, not the per-holder amounts, which each class dividend recomputes from the record-date book when it is declared.
curl -X POST "https://your-instance.example.com/api/v2/tokens/0x8a791620dd6260079bf849dc5567adc3f2fdc318/corporate-actions/commit" \
-H "X-Api-Key: $DALP_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"terms": { "type": "bonusIssue", "numerator": 1, "denominator": 10 },
"quoteFingerprint": "3b8f1d2c4a5e6f708192a3b4c5d6e7f8091a2b3c4d5e6f708192a3b4c5d6e7f8",
"acknowledgements": ["excluded-holders"]
}'Commit takes terms, the quoteFingerprint from the quote, the acknowledgements, the walletVerification and an Idempotency-Key. It checks the wallet verification once and runs each step as the verified caller. If the plan it computes no longer has the fingerprint you sent, it stops before anything moves on chain and answers CORPORATE_ (HTTP 409, DALP-14426). The error carries the current quoteFingerprint in data. Read the quote again, review the new plan, and commit with that value.
If the holders move after the commit has saved its record, the platform aborts the record before it answers CORPORATE_, so the supply is free again. Send your next attempt with a new Idempotency-Key. The old key finds the aborted record and answers CORPORATE_ with HTTP 409. A commit of any aborted record answers the same way, including a cash dividend or distribution that an operator cancelled.
What the commit does next depends on the quote's approval:
- With no approval needed, it confirms. It answers
202while the record isexecuting, as a confirm does. Read the record atLocationuntil it settles. - With approval needed, it stops at
previewed, opens the approval and notifies the checkers. A second operator approves it, as Approve or reject a plan describes. ThenPOST .../confirms the approved plan without previewing again, after the same fingerprint check. A commit sent again while the record still waits for approval checks the fingerprint too, and answerscorporate- actions/ {corporate Action Id}/ commit quote-stalewhen it differs from the stored plan. - A cash dividend or distribution stops after it is recorded and answers
draftedwithpreviewAvailableAt, set to its record date. While its dividend declaration is still queued, the answer is202anddividendIdisnull. Read the record untildividendIdis set before you quote it. Send noquoteFingerprinton that first commit, because the plan cannot be quoted before the record exists. When the record date has passed,previewAvailableAtis in the past and you can quote and commit the record now. If the snapshot is not final yet, the preview answerssnapshot-not-readyand the record staysdrafted.
Cash dividends and distributions
Create already declares the dividend on chain. Preview then stores the holder allocations and returns dividendConfirmation: merkleRoot, totalEntitled, and totalRequired. Send all three values together when confirming the reviewed allocation. totalEntitled is a holder count. totalRequired is an amount in denomination-asset base units.
Confirm rejects a missing or mismatched confirmation group. A completed corporate action means the dividend snapshot was committed. Holders still claim payment through the dividend-yield feature.
The dividend itself is paid and managed on the dividend-yield feature's routes, not on this collection. Each route sits under /:
POST .../pays the calling holder's entitlement. It asks for no token role.claims POST .../withtransitions {"transition": "cancel"}cancels a declared dividend. It asks for the token's governance role.POST .../with a positivetop- ups amountadds that much of the denomination asset to the dividend's treasury. When the calling wallet is the treasury itself, it raises the treasury's allowance to the feature byamountinstead. It asks for no token role.
The feature numbers its dividends from 0, and {dividendId} is that on-chain number. The Console counts from 1, so the dividend it shows as Dividend #1 has dividendId 0.
The interest-yield feature accrues interest instead of declaring dividends, and has its own routes under /: POST .../ pays the calling holder, and POST .../ with a positive amount funds the interest treasury. Neither asks for a token role.
Coordinated distributions across share classes
A coordinatedDistribution pays every share class of a multi-class instrument from one pool, at one record date. Raise it on the class token that carries the distribution-waterfall feature. A token that is not a class of a multi-class instrument, or a class without that feature, is refused. The instrument's waterfall decides the split from each class's rank and contribution basis. Classes at the same rank share their part of the pool pro rata.
Create takes poolAmount in base units of the waterfall's denomination asset, denominationAsset (the address of that asset: preview refuses any other), and exDate, recordDate and paymentDate in that order. claimDeadline, when set, falls on or after the payment date. determinationKind is optional: it asks for the waterfall's priority order for that issuer event, and the waterfall applies it only when a valid determination claim of that kind is in force. Create stores the record as drafted and moves no money.
A priority row can owe a class its contribution basis less every distribution the class has already received, instead of its basis less the capital it has had back. This is called netting, and it is off by default. POST /api/ turns it on or off for one class under one row. Send determinationKind as the row's hash from the waterfall's priorityRows in the feature list (the keccak-256 hash of the row's text label), role as the class role, and nets. The executing wallet needs governance on the class token. The change is refused with DISTRIBUTION_ (409) once a distribution was funded under that row, and with DISTRIBUTION_ or DISTRIBUTION_ (400) for a row or role the waterfall does not have. The feature list reports each row's nets, one entry per class in class order, and locked. POST /api/ folds up to maxDistributions recorded distributions into the running totals that netting reads. Only a waterfall that was upgraded after distributions were funded needs it, any account can send it, and it changes no split.
Preview is refused until the record date has passed on chain. It is also refused with waterfall-cannot-receive (DALP-14016) when the denomination asset refuses a transfer from the executing wallet into the waterfall, for example a paused asset or an identity-gated asset and a waterfall that has no registered identity. The distribution-waterfall factory registers an identity for each waterfall it deploys. For a waterfall deployed before that, the token's governance calls ensurePoolReceiver with the class token on that factory. Preview returns coordinatedSplit: the ordinary legs, and the priority legs when you named a determinationKind. Each leg names the class token, its role, its rank and its amount. Preview then declares one dividend per class the pool reaches, at a rate of that class's leg divided by its supply on the record date, rounded down, and waits for each class's record-date snapshot. Until every class is declared and its snapshot is computed, preview is refused with snapshot-not-ready. Send it again. When a class declaration is refused because the paying wallet cannot pay for gas, preview answers TRANSACTION_ (HTTP 400, DALP-14062) instead. Fund the wallet that the transaction request in data.transactionId names, then send preview again. Once they are, the record is previewed and its holders list every holder of every class with the amount they can claim, each entry tagged with its classToken. Declaring moves no money. A second operator then approves the plan, as Approve or reject a plan describes. The approval covers the split, each class's dividend and rate, and every holder amount. If the split moved since an earlier preview, preview cancels the class dividends it declared for the old split before declaring new ones.
Confirm is refused until the record date has been reached on chain. It is also refused before any money moves when the claim deadline has passed, the waterfall or its split changed since the approved preview, a class can no longer serve the record date, a class dividend is no longer declared or its snapshot no longer matches the approved holder amounts, a class carries another declared dividend that was never recorded, a class's dividend-yield feature pays in another asset than the pool, the denomination asset refuses a transfer into the waterfall, or the confirming wallet lacks governance on any class. When the approval no longer covers the run, abort it and declare a new one. The executing wallet must hold the pool and approve the waterfall to spend it. POST /api/ sends that approval. Its amount is in base units of the denomination asset, like poolAmount, and sets the whole approval, so send at least the pool. Confirm then funds the waterfall once, bound to the approved split, and records each class's approved snapshot in rank order. The record is completed when every class it pays is recorded, which is when its holders can claim through that class's dividend-yield feature.
The record stays executing while the funding or a class record is still queued. Send confirm again to resume it. It records each class once its transaction lands. A funding transaction that fails, for example because it reverts, moves no money. Confirm returns that failure and puts the record back to approved, so the next confirm funds the pool again. When confirm finds that a queued funding failed, it funds the pool again in the same call. Abort cancels every class dividend the run declared and did not record, including on a record a reject or an approval expiry already ended: abort that record again to cancel its class dividends. The record's applied is true while any class dividend of the run is still declared or being cancelled. Its appliedSplit.fundingSent is true once the run sent a funding transaction, so an aborted run with it set had a funding that failed and moved no money. To know when to send preview or confirm again, read the record: each appliedSplit.legs[] entry carries requestId, the transaction request its class is waiting on, and applied is the latest funding attempt's transaction request. Wait on each one through the transaction status route or its stream, then send the call again. A funding request that ended FAILED is answered by sending confirm again, which funds the pool in a new attempt. Abort is allowed after the pool is funded. Classes already open for claims stay open until their claim deadline, and the rest are cancelled. Abort is refused while a class is still being recorded (abort once it settles) and while a sent funding may still land.
Reclaim and release a class's money
Read each class's money on the record. Every appliedSplit.legs[] entry reports state (it adds releasing and released), dividend (the dividend-yield feature address and the dividend id), and recovery (state, remaining, funder, and claimDeadline). The split also reports its distributionId.
POST /api/takes back the unclaimed rest of a class dividend after its claim deadline. Use the class token asv2/ tokens/ {asset Id}/ features/ dividend- yield/ dividends/ {dividend Id}/ reclaim assetIdand send the feature address as thefeatureAddressquery parameter. The operation isv2/token/reclaimDividend.POST /api/returns what the class still holds in the waterfall to the wallet that funded the run. The operation isv2/ tokens/ {asset Id}/ corporate- actions/ {corporate Action Id}/ legs/ {class Token}/ release v2/. It is refused while holders can still claim, once released, and when nothing is left. A release that fails can be sent again.token/ release Corporate Action Leg
Both need the governance role on the class token and a wallet step-up.
Holders a class did not pay
Once a class's claims can no longer pay, because its dividend was reclaimed or paid or its money was released, the record keeps each holder the class owed and did not pay.
GET /api/(v2/ tokens/ {asset Id}/ corporate- actions/ {corporate Action Id}/ unpaid- entitlements v2/) lists them one page at a time, ordered by class, then holder. Sendtoken/ list Unpaid Entitlements page[limit](1 to 200, default 50) andpage[offset]to move through the list.dataholds the page,meta.totalcounts every unpaid holder, andlinksgives the first, previous, next, and last page. Each entry carriesid,classToken,holder,amountin base units of the pool asset,reason(unclaimed, orlost-identityfor a holder left out of the snapshot),recordedAt,payment, which isnulluntil the holder is marked paid, andreopens, the earlier payment marks governance cleared, oldest first.pendingClassesnames closed classes whose claims are not indexed yet, whichever page you ask for. Read again later to see their holders. The caller needs the governance role or the auditor role.POST /api/(v2/ tokens/ {asset Id}/ corporate- actions/ {corporate Action Id}/ unpaid- entitlements/ {entry Id}/ paid v2/) records that you paid the holder outside the platform. Sendtoken/ mark Unpaid Entitlement Paid paymentReference, for example the bank transfer reference. It needs the governance role and a wallet step-up. The entry'spaymentthen carriespaidAt,paidBy, andreference. A second mark of the same entry is refused, and an entry is never removed.POST /api/(v2/ tokens/ {asset Id}/ corporate- actions/ {corporate Action Id}/ unpaid- entitlements/ {entry Id}/ reopen v2/) clears a payment mark you made by mistake. Sendtoken/ reopen Unpaid Entitlement Payment reason(1 to 500 characters) andpaidAt, thepayment.paidAtyou read. It needs the governance role and a wallet step-up. The entry becomes unpaid and can be marked again, and eachreopensitem keeps the clearedpaidAt,paidByandreferencewithreopenedAt,reopenedByandreason. An entry that is not paid is refused asCORPORATE_. AACTION_ ENTRY_ NOT_ PAID paidAtthat no longer matches the current mark is refused asCORPORATE_. Read the list again.ACTION_ ENTRY_ MARK_ CHANGED
Continuing a partial run
Abort leaves applied holders unchanged. When a new composition continues an earlier run, set followsUpOn to the earlier corporate-action identifier. Preview excludes holders already applied by that linked sequence. Review the remaining allocations before confirming the new run.
How a stock split applies
A stock split (direction: "forward") or reverse split (direction: "reverse") is not a single on-chain rebase of every balance. It applies the ratio through the same chunked mint or burn every other composition type uses. A reverse split can instead round each result to whole shares with fractionPolicy, lift a holder whose result is below one share up to one, and pay cash in lieu of the part below one whole share from a smart-account treasury. A forward split pays nothing for a fraction. Stock split direction lists the fields and how the cash is computed.
A split processes holders in chunks, so some balances can remain unchanged while the run advances. Inspect entries whose state is remaining to identify holders whose chunks have not completed.
Contracts that hold the token for their owners
An escrow (a trading venue's or a custody gateway's), a token sale, an XvP settlement and an airdrop hold units for other people. For an action that changes every holder's units by a ratio, the platform sizes each contract's units from the owners' balances at the record date, mints or burns that sum, and writes the action's step to the contract so its per-owner records follow. The snapshot lists each contract under custodialSteps (contract, kind, index).
The contracts are frozen for the action from its record date until its step lands. A movement of the token that the freeze refuses (an escrow deposit or lock, a sale purchase or claim, a new XvP deposit or settle, an airdrop claim, a new gateway request or claim) reverts and names the token and the action. The freeze needs a record date that is still ahead when you create the action, and it covers the contracts that hold the token at that time.
- Create with no record date, or one that has passed, and confirm when a contract started holding the token after the action was created, are refused as
CORPORATE_(409,ACTION_ CUSTODIAN_ FREEZE_ REQUIRED DALP-184677, not retryable). Create the action again with a record date that is still ahead. If a contract began holding the token after the action was created, abort the action first. - Confirm cancels the resting venue orders, mints or burns each contract's units, then writes each step. Writing the step lifts that contract's freeze in the same call. A run that stops between the units and the steps keeps the contracts frozen. Confirm again to finish it.
- Abort clears the freeze while no unit chunk and no step has been sent. After either, abort is refused as
CORPORATE_, and the way forward is to confirm again.ACTION_ RUN_ COMPLETED - A custody gateway request that the action rounds to zero shares ends as returned with the reason
corporate-action. It holds no receipts, so nothing is paid or returned. The pending and claimable redeem views of a request already show it on the new basis before anything touches it.
Next steps
- Corporate actions catalogue reference for the status lifecycle, the in-flight rule, and the stored holder-state shape across all thirteen types.
- Idempotency and async settlement for the
Idempotency-KeyandPrefercontract every mutating route on this catalogue shares. - Corporate actions in the Console for the same catalogue driven from screens.