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

# Programmatic Access

> Webhook + REST + SSE for institutions integrating Musubi with a TMS, ERP, or custom treasury automation.

<Note>
  Most institutions use scheduled downloads — see [Statements](/institution/integration/statements). This page is for institutions wiring Musubi into a TMS/ERP (Kyriba, SAP, Oracle) or with real-time reconciliation needs that can't wait for batch exports.
</Note>

Musubi exposes REST + SSE APIs and (coming) a webhook callback. Most treasury systems prefer push webhooks over long-lived SSE; SSE is available for services that can keep a streaming connection open.

## When to use this

* You're wiring Musubi into a TMS or ERP (Kyriba, SAP S/4HANA, Oracle TRM, etc.)
* Your accounting posts trades to the GL in real time
* Your treasury automation initiates orders based on internal rules (payment schedules, sweep triggers)
* You're building a custom treasury dashboard

If none of those apply, Console + Statements is the simpler path.

## Authentication

JWT bearer token, typically refreshed every hour. Full details: [Authentication](/authentication). Cache the token, refresh \~60 seconds before expiry, retry once on 401 after a forced refresh.

## Integration modes

<Tabs>
  <Tab title="Webhook (recommended for TMS/ERP)">
    <Note>
      **Status: webhook endpoint coming.** In the interim, use SSE (next tab) or a scheduled poll of `GET /api/v1/orders?status=SETTLED&updated_after=...` to pick up newly-settled orders.
    </Note>

    Register a callback URL with Musubi. On order status changes and settlement, Musubi POSTs a signed payload to your URL:

    ```json theme={null}
    POST https://your-tms.example.com/webhooks/musubi
    Content-Type: application/json
    X-Musubi-Signature: sha256=<hmac of body with shared secret>

    {
      "event": "order_updated",
      "intent_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
      "status": "SETTLED",
      "transaction_hash": "a3f2c1d9e8b74f6a2c1d9e8b74f6a2c14401",
      "settled_at": "2025-03-15T09:00:15.000Z",
      "source_currency": "JPYSC0",
      "source_amount_actual": "11200000",
      "target_currency": "USDCx",
      "target_amount": "100000",
      "fx_rate": "112.0"
    }
    ```

    Your endpoint verifies the HMAC signature, acknowledges with a 2xx, and queues the event for processing. Musubi retries with exponential backoff if your endpoint is unreachable or returns a non-2xx response.

    Typical TMS integration pattern:

    * Idempotency key = `intent_id + transaction_hash`
    * On webhook receipt, post a journal entry to the GL and mark the payment complete in the TMS
    * For `EXECUTING` events, optionally update dashboard state without posting
  </Tab>

  <Tab title="SSE (streaming)">
    Subscribe to `GET /api/v1/orders/events`. Event types: `order_updated`, `quote_received`, `heartbeat`. Filter to `order_updated` with `status == SETTLED` for accounting handoff.

    ```java theme={null}
    public record OrderEvent(
        String intentId, String status, String transactionHash,
        Instant settledAt, String sourceAmountActual, String targetAmount,
        String fxRate) {}
    ```

    ```java theme={null}
    @Component
    public class MusubiInstitutionListener {
      private static final Logger log = LoggerFactory.getLogger(MusubiInstitutionListener.class);

      private final WebClient rest;
      private final AccountingService accounting;

      public MusubiInstitutionListener(WebClient.Builder b,
                                       @Value("${musubi.backend-url}") String url,
                                       MusubiAuthProvider auth,
                                       AccountingService accounting) {
        this.rest = b.baseUrl(url)
            .filter((req, next) -> next.exchange(
                ClientRequest.from(req).headers(h -> h.setBearerAuth(auth.token())).build()))
            .build();
        this.accounting = accounting;
      }

      @EventListener(ApplicationReadyEvent.class)
      public void subscribe() {
        var type = new ParameterizedTypeReference<ServerSentEvent<OrderEvent>>() {};
        rest.get().uri("/api/v1/orders/events")
            .accept(MediaType.TEXT_EVENT_STREAM)
            .retrieve()
            .bodyToFlux(type)
            .retryWhen(Retry.backoff(Long.MAX_VALUE, Duration.ofSeconds(1))
                .maxBackoff(Duration.ofSeconds(30)))
            .filter(sse -> "order_updated".equals(sse.event()))
            .mapNotNull(ServerSentEvent::data)
            .filter(ev -> "SETTLED".equals(ev.status()))
            .doOnNext(ev -> accounting.post(ev.intentId(), ev.transactionHash(),
                ev.settledAt(), ev.sourceAmountActual(), ev.targetAmount(), ev.fxRate()))
            .subscribe();
      }
    }
    ```

    Canton's `order_updated` events are per-contract; you may see `EXECUTING` then `SETTLED` for the same intent. Filter to `SETTLED` only if your GL cares about the terminal state.
  </Tab>
</Tabs>

## REST — order initiation

For institutions automating order initiation (e.g., scheduled payments from a TMS):

| Endpoint                                                   | Purpose                          |
| ---------------------------------------------------------- | -------------------------------- |
| `POST /api/v1/orders`                                      | Create a new FX order            |
| `GET /api/v1/orders/{intent_id}/quotes`                    | List quotes for an order         |
| `POST /api/v1/orders/{intent_id}/quotes/{quote_id}/accept` | Accept a quote                   |
| `POST /api/v1/orders/{intent_id}/cancel`                   | Cancel a PENDING or QUOTED order |
| `GET /api/v1/orders/{intent_id}`                           | Get order by ID                  |

Request/response shapes: [API Reference](/institution/api-reference).

```java theme={null}
// Typed DTOs matching /institution/api-reference shapes.
public record ApiResponse<T>(T data) {}
public record CreateOrderRequest(
    String senderCustodianPartyId, String intentSignature,
    String receiverPartyId, String receiverCustodianPartyId,
    String sourceContractId, String sourceCurrency,
    String targetAmount, String targetCurrency,
    String sourceAmountMax, String memo) {}
public record OrderDto(
    String intentId, String status, String fxRate,
    String sourceAmountActual, String transactionHash,
    Instant settledAt) {}
```

```java theme={null}
@Service
public class MusubiInstitutionClient {
  private final WebClient rest;

  public MusubiInstitutionClient(WebClient.Builder b,
                                 @Value("${musubi.backend-url}") String url,
                                 MusubiAuthProvider auth) {
    this.rest = b.baseUrl(url)
        .filter((req, next) -> next.exchange(
            ClientRequest.from(req).headers(h -> h.setBearerAuth(auth.token())).build()))
        .build();
  }

  public OrderDto createOrder(CreateOrderRequest req) {
    return rest.post().uri("/api/v1/orders")
        .bodyValue(req)
        .retrieve()
        .bodyToMono(new ParameterizedTypeReference<ApiResponse<OrderDto>>() {})
        .map(ApiResponse::data)
        .block();
  }

  public OrderDto acceptQuote(String intentId, String quoteId) {
    return rest.post().uri("/api/v1/orders/{id}/quotes/{qid}/accept", intentId, quoteId)
        .retrieve()
        .bodyToMono(new ParameterizedTypeReference<ApiResponse<OrderDto>>() {})
        .map(ApiResponse::data)
        .block();
  }
}
```

## TMS/ERP integration notes

* **Idempotency** — key accounting posts by `intent_id + transaction_hash`. Webhook retries or SSE reconnects can replay events.
* **Reconciliation** — match `intent_id` back to your internal payment reference (store it as a `memo` prefix or in your own ID mapping table).
* **FX posting** — post the JPYSC0 out-leg at `source_amount_actual` × `fx_rate` equivalent, and the USDCx in-leg at `target_amount`. The `transaction_hash` is your settlement proof.
* **Cost-guard policy** — set `source_amount_max` from your treasury risk rules, not from the UI. Orders exceeding the guard return `SLIPPAGE` without settling.

## Other stacks

Same patterns apply:

* **Node** — `fetch` for REST; `EventSource` (native) or `eventsource` package for SSE; `express`/`fastify` HMAC-verifying webhook handler
* **Python** — `httpx` or `requests` for REST; `httpx-sse` for SSE; `Flask`/`FastAPI` with `hmac.compare_digest` for webhooks
* **Kotlin** — `WebClient` + `ktor-client-cio` works

## Next steps

* For institutions not automating, the Console + Statements path covers the same workflow without code — see [Console](/institution/integration/console) and [Statements](/institution/integration/statements).
* Full endpoint reference — [API Reference](/institution/api-reference).
