> ## Documentation Index
> Fetch the complete documentation index at: https://developers.fireblocks.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Write Canton Transactions

> Make a Canton call to act on Canton, and answer an offer to respond to one. Both return the outgoing transaction; the on-chain outcome always arrives by webhook.

<Warning>
  Make a Canton call and Answer an offer are not yet available in production. The request and response shapes below are the committed contract, so you can build your integration against them now, but calls will fail until Fireblocks announces general availability.
</Warning>

There are two ways to write to Canton: make a call to act on your own wallet or a contract, or answer an offer to respond to one. Both are `POST` requests that return `201` with the outgoing transaction that carries the action — never the on-chain result. Track that transaction with the [Canton Transaction Webhooks](/docs/canton-transaction-webhooks) or by [reading it directly](/docs/read-canton-transactions) to see how it resolved.

## Idempotency

Both endpoints accept the optional `Idempotency-Key` header. Resending a request with the same key returns the original response instead of submitting a second time. See [API Idempotency](/reference/api-idempotency).

<Tip>
  Set an idempotency key on every write. A network timeout does not tell you whether the request landed, and neither endpoint is safe to blindly retry without one.
</Tip>

## Make a Canton call

See the full generated reference at [Make a Canton call](/api-reference/canton-beta/make-a-canton-call).

```http theme={"system"}
POST /v1/operations/canton/calls
```

`type` selects the call and the shape of `payload`. An unknown or missing `type`, or a `payload` missing a required field, is a `400`.

| `type` | Payload | v1 status |
| - | - | - |
| `DTCC_PARTICIPANT_ONBOARDING` | [ParticipantOnboardingPayload](#participantonboardingpayload) | `501` — capability exists, no handler yet |
| `DTCC_END_INVESTOR_INVITE` | [EndInvestorPayload](#endinvestorpayload) | `501` — capability exists, no handler yet |
| `DTCC_END_INVESTOR_INVITE_CANCEL` | [EndInvestorPayload](#endinvestorpayload) | `501` — no Canton capability |
| `DTCC_END_INVESTOR_OFFBOARD` | [EndInvestorPayload](#endinvestorpayload) | `501` — no Canton capability |
| `DTCC_ALLOW_LIST_ADD` | [AllowListPayload](#allowlistpayload) | `501` — no Canton capability |
| `DTCC_ALLOW_LIST_REMOVE` | [AllowListPayload](#allowlistpayload) | `501` — no Canton capability |
| `ALLOCATION_WITHDRAW` | [AllocationWithdrawPayload](#allocationwithdrawpayload) | Implemented |

<Note>
  `ALLOCATION_WITHDRAW` is the only call implemented in v1. Every other `type` is a documented `501` today — the request shape below is the committed contract, not a preview of working behavior.
</Note>

### ParticipantOnboardingPayload

| Field | Required | Description |
| - | - | - |
| `vaultAccountId` | Yes | The vault account that acts as the participant. Its Canton party is derived for you. |
| `asset` | Yes | `CANTON` or `CANTON_TEST`. |
| `operator` | Yes | DTCC infra operator party id. |
| `compliance` | Yes | DTCC compliance party id. |
| `registrar` | Yes | DTCC registrar party id — co-signs the accept. |
| `clientOnboarder` | Yes | DTCC client onboarder party id — co-signs the accept. |
| `upgrader` | Yes | DTCC upgrader party id, the Model Upgrade Tool authority. Supplied by DTCC during off-chain registration alongside the other party ids. |
| `expiresAt` | No | RFC 3339 date-time when the onboarding request expires if unanswered. |

### EndInvestorPayload

Shared by invite, invite-cancel, and offboard — identical wire shape, different verb.

| Field | Required | Description |
| - | - | - |
| `vaultAccountId` | Yes | The vault account whose Canton wallet acts here. |
| `asset` | Yes | `CANTON` or `CANTON_TEST`. |
| `endInvestor` | Yes | The end investor's Canton party id. |

### AllowListPayload

| Field | Required | Description |
| - | - | - |
| `vaultAccountId` | Yes | The vault account whose Canton wallet acts here. |
| `asset` | Yes | `CANTON` or `CANTON_TEST`. |
| `wallets` | Yes | Canton party ids to add or remove. At least one. |

### AllocationWithdrawPayload

| Field | Required | Description |
| - | - | - |
| `allocationTransactionId` | Yes | The Fireblocks transaction id of the outgoing response that created the allocation. The allocation is resolved from it — Canton contract ids are never accepted here. |

Only accepted while that transaction sits at `status CONFIRMING` / `subStatus ALLOCATED`. See [The ALLOCATED lifecycle](/docs/read-canton-transactions#the-allocated-lifecycle).

```json theme={"system"}
{
  "type": "ALLOCATION_WITHDRAW",
  "payload": {
    "allocationTransactionId": "9f2b7c14-8d55-4a02-b6e1-5c3f90a7d488"
  }
}
```

A `201` returns the outgoing withdrawal transaction:

```json theme={"system"}
{
  "transactionId": "c4d1f8a2-7e93-4b06-a815-3f9c2e0b6d47",
  "status": "SUBMITTED"
}
```

`status` here is always `SUBMITTED` — it is the transaction's status at the moment of this response, not its outcome. Its resolution to `COMPLETED` or `CANCELLED` arrives later, by webhook or by re-reading the transaction.

### Responses

| Status | Meaning |
| - | - |
| `201` | The outgoing transaction that carries the call. |
| `400` | Unknown or missing `type`, a missing required payload field, or an invalid party id, asset, or expiry. |
| `404` | No Canton wallet for this vault account and asset, or an unknown transaction id for this tenant. |
| `409` | The target is not in a state this call can act on, or more than one candidate contract matched the wallet. |
| `501` | This call type is not available in v1. |

## Answer an offer

See the full generated reference at [Answer an offer](/api-reference/canton-beta/answer-an-offer).

```http theme={"system"}
POST /v1/operations/canton/offers/{offerId}/responses
```

`offerId` is the Fireblocks transaction id of the incoming offer — the same `id` you read from `cantonDetails.offerResponse` or from the webhook's `data.id`. See [Decide whether an offer can be responded to](/docs/read-canton-transactions#decide-whether-an-offer-can-be-responded-to) before calling this.

The body nests two layers of discrimination: `domain` selects `response`, and inside `response`, `responseType` selects the variant. Both layers are closed — sending an onboarding `responseType` under `domain: ALLOCATION` is a `400`, regardless of what the offer's own domain is.

| `domain` | `responseType` | Arguments | v1 status |
| - | - | - | - |
| `ONBOARDING` | `DTCC_END_INVESTOR_ONBOARDING_ACCEPT` | None | Implemented |
| `ONBOARDING` | `DTCC_END_INVESTOR_ONBOARDING_REJECT` | `reason` (required, 1–512 chars) | Implemented |
| `ONBOARDING` | `TRADEWEB_COSIGNING_DELEGATION_ACCEPT` | None | Implemented |
| `ONBOARDING` | `TRADEWEB_COSIGNING_DELEGATION_REJECT` | None | Implemented |
| `ALLOCATION` | `ALLOCATION_ACCEPT` | None | Implemented |
| `ALLOCATION` | `ALLOCATION_REJECT` | None | Implemented |
| `TRANSFER` | `TRANSFER_ACCEPT` | None | `501` — domain declared, not available in v1 |
| `TRANSFER` | `TRANSFER_REJECT` | None | `501` — domain declared, not available in v1 |

<Note>
  `domain: TRANSFER` exists so the endpoint's contract covers every offer domain, not because transfer responses work today. Both `TRANSFER_ACCEPT` and `TRANSFER_REJECT` return `501` in v1.
</Note>

`reason` on a DTCC onboarding rejection is recorded on-chain, where the counterparty can read it. The Tradeweb rejection carries no `reason` — its DAR has nowhere on-ledger to record one, so the field does not exist on that variant.

```json theme={"system"}
{
  "domain": "ONBOARDING",
  "response": {
    "responseType": "DTCC_END_INVESTOR_ONBOARDING_REJECT",
    "reason": "KYC not completed"
  }
}
```

A `201` echoes the `responseType` back so you can correlate the response without re-reading it:

```json theme={"system"}
{
  "transactionId": "5a1d8b0f-2c31-4c7e-9d0a-118e7b6c4a21",
  "status": "SUBMITTED",
  "responseType": "DTCC_END_INVESTOR_ONBOARDING_REJECT"
}
```

<Warning>
  This endpoint is the authority on answerability, not `availableResponses`. `availableResponses` states what the offer *type* accepts and does not change once the offer arrives — it does not tell you whether the offer is still open. This endpoint re-checks state and expiry on every call and answers `409` when either has moved.
</Warning>

### Responses

| Status | Meaning |
| - | - |
| `201` | The outgoing transaction that carries the response. Its on-chain outcome arrives by webhook as a status update on this transaction. |
| `400` | The `responseType` does not belong to the declared `domain`, or a required argument is missing. |
| `404` | Unknown offer id for this tenant. |
| `409` | The offer is not answerable — already answered, expired, or a response is in flight. |
| `501` | No handler is implemented for this domain yet. |
