> For the complete documentation index, see [llms.txt](https://synthesys-2.gitbook.io/synthesys-docs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://synthesys-2.gitbook.io/synthesys-docs/network-overview/api-reference/transactions-wip.md).

# Transactions \[WIP]

Orders are how investors subscribe into a share class. Placing an order is a **two-step flow**: first you create the order on the network (which validates the request, prices it against the live NAV, and lands it in `created` state), and then you push it through to the supply-side platform for execution (which transitions it to `submitted`). Splitting placement from push gives your application a clean audit point — you can inspect the priced order, share it with the investor for confirmation, or cancel it before any settlement work begins.

This page covers all five subscription-order endpoints, the parameters distributors supply, the response shapes (including the rich pricing detail returned at placement), and the patterns we recommend for the lifecycle. Orders are placed by a distributor's backend on behalf of a registered investor — they should never be issued directly from a browser or mobile client.

### Prerequisites for placing an order

Before a subscription order can be created for an investor, the following must already be in place:

1. The investor is registered under your distributor — see Investors.
2. The investor has a wallet configured for the share class's default chain.
3. The investor is granted access to the share class.
4. The share class has a live NAV published.

Wallet setup and share-class grants are handled out-of-band by the Synthesys operations team. If a prerequisite is missing, `POST /orders/subscribe` will fail with a clear `errorMsg` (e.g. *"Investor not granted access to SC000001"* or *"No wallet configured for investor on chain 1"*).

### Order lifecycle

A subscription moves through three observable states:

| State             | Set by                                     | Means                                                                                                                     |
| ----------------- | ------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------- |
| `created`         | `POST /orders/subscribe`                   | The order is priced and parked on the network. Nothing has been pushed downstream yet. Cancellable. Pushable              |
| `submitted`       | `POST /orders/subscriptions/push/:orderId` | The order has been forwarded to the supply-side platform and accepted into its queue. No longer cancellable from this API |
| (terminal states) | downstream                                 | Acceptance, rejection, and settlement happen on the supply-side platform and are reflected back into `statusHistory`      |

The `statusHistory` map on every order records the timestamp, status, and the actor (the value of the `x-actor` header, or `"Network"` for system transitions) for every state change. Use it to render a timeline in your back-office UI.

***

### Create subscription order

Place a subscription. The order is priced immediately against the live NAV; the response includes the full pricing breakdown so you can present it to the investor for confirmation.

**`POST /orders/subscribe`**

#### Request body

| Field                | Type   | Required | Description                                                                                   |
| -------------------- | ------ | -------- | --------------------------------------------------------------------------------------------- |
| `distributorId`      | string | Yes      | The distributor placing the order. Format `DT######`                                          |
| `investorId`         | string | Yes      | The investor on whose behalf the order is placed. Format `IN######`                           |
| `shareClassId`       | string | Yes      | The share class being subscribed into. Format `SC######`                                      |
| `amountSubscription` | number | Yes      | Fiat amount of the subscription (e.g. `100000` for 100,000 USD)                               |
| `currency`           | string | No       | ISO currency code. Defaults to the share class's denomination currency                        |
| `externalRef`        | string | No       | Your own correlation identifier for this order. Stored verbatim and echoed back on every read |

The destination chain and wallet are resolved automatically from the share class's `defaultChainId` and the investor's wallet on that chain. You do **not** pass `chainId` or `walletAddress` in the request body.

#### Response

The successful response carries the full priced order:

json

```json
{
  "isError": false,
  "errorMsg": "NA",
  "data": {
    "orderId": "OR000001",
    "orderType": "SUBSCRIPTION",
    "status": "created",
    "distributorId": "DT000001",
    "investorId": "IN000002",
    "shareClassId": "SC000003",
    "amountSubscription": 1000,
    "amountFeesTotal": 1,
    "amountPayable": 1001,
    "amountTokensEstimated": "0.9108",
    "feesSubBps": 10,
    "currency": "USD",
    "deliveryMode": "passthrough",
    "deliveryWallet": "0xAAAA000000000000000000000000000000000001",
    "nav": "1098.0000",
    "navPublishedAt": 1761807929692,
    "externalRef": "first test txn of subscription through network",
    "createdAt": 1761808000000,
    "createdBy": "Network",
    "statusHistory": {
      "1761808000000": { "status": "created", "by": "Network" }
    }
  }
}
```

| Field                   | Notes                                                                                                                                                                     |
| ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `orderId`               | Synthesys-side order identifier, format `OR######`. Persist this immediately                                                                                              |
| `orderType`             | Always `"SUBSCRIPTION"` for this endpoint                                                                                                                                 |
| `status`                | Starts at `"created"`. Will not change until you call the push endpoint                                                                                                   |
| `amountFeesTotal`       | Fee in the order's currency, derived from `amountSubscription × feesSubBps / 10000`                                                                                       |
| `amountPayable`         | The total the investor must transfer: `amountSubscription + amountFeesTotal`                                                                                              |
| `amountTokensEstimated` | Estimated tokens at the current NAV. String (decimal precision). Final tokens are computed at settlement against the NAV in effect then                                   |
| `feesSubBps`            | Subscription fee in basis points, copied from the share class at order time (10 bps = 0.1%)                                                                               |
| `deliveryMode`          | `"passthrough"` (tokens go to the investor's wallet) or `"hold"` (tokens go to the distributor's wallet). Determined by how the share class is granted to the distributor |
| `deliveryWallet`        | The actual address where tokens will land — derived from `deliveryMode`                                                                                                   |
| `nav`                   | The NAV used for pricing, as a decimal string                                                                                                                             |
| `navPublishedAt`        | Epoch ms at which that NAV was struck                                                                                                                                     |
| `statusHistory`         | Map keyed by epoch-ms timestamp. Every state transition appends a new entry                                                                                               |

If no live NAV is published when the order is created, `nav` will be `-1` and `amountTokensEstimated` will be `"-1"`. The order is still valid; it just hasn't been priced.

#### Examples

**curl:**

bash

```bash
curl -sX POST $BASE/orders/subscribe \
  -H 'Content-Type: application/json' \
  -H 'x-actor: apex-orders' \
  -d '{
    "distributorId": "DT000001",
    "investorId": "IN000002",
    "shareClassId": "SC000003",
    "amountSubscription": 1000,
    "currency": "USD",
    "externalRef": "first test txn of subscription through network"
  }'
```

**Node.js:**

javascript

```javascript
async function createSubscription({ distributorId, investorId, shareClassId, amountSubscription, currency, externalRef }) {
  const res = await fetch(`${BASE}/orders/subscribe`, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'x-actor': process.env.ACTOR ?? 'system',
    },
    body: JSON.stringify({ distributorId, investorId, shareClassId, amountSubscription, currency, externalRef }),
  });
  const payload = await res.json();
  if (payload.isError) throw new Error(payload.errorMsg);
  return payload.data;
}

const order = await createSubscription({
  distributorId: 'DT000001',
  investorId: 'IN000002',
  shareClassId: 'SC000003',
  amountSubscription: 1000,
  currency: 'USD',
  externalRef: 'first test txn of subscription through network',
});
console.log(`${order.orderId}: ${order.amountPayable} ${order.currency}, est ${order.amountTokensEstimated} tokens`);
```

**Python:**

python

```python
import os, requests

def create_subscription(distributor_id, investor_id, share_class_id, amount_subscription, *,
                        currency=None, external_ref=None):
    body = {
        "distributorId": distributor_id,
        "investorId": investor_id,
        "shareClassId": share_class_id,
        "amountSubscription": amount_subscription,
    }
    if currency:      body["currency"] = currency
    if external_ref:  body["externalRef"] = external_ref

    r = requests.post(
        f"{BASE}/orders/subscribe",
        json=body,
        headers={"x-actor": os.getenv("ACTOR", "system")},
        timeout=10,
    )
    payload = r.json()
    if payload["isError"]:
        raise RuntimeError(payload["errorMsg"])
    return payload["data"]

order = create_subscription(
    "DT000001",
    "IN000002",
    "SC000003",
    1000,
    currency="USD",
    external_ref="first test txn of subscription through network",
)
print(f"{order['orderId']}: {order['amountPayable']} {order['currency']}")
```

***

### Get order by ID

Read a single order, including its full `statusHistory`. Use this to refresh state for an order your application already knows the ID for.

**`GET /orders/:orderId`**

#### Path parameters

| Param     | Type   | Description                                           |
| --------- | ------ | ----------------------------------------------------- |
| `orderId` | string | The order ID returned at placement, format `OR######` |

#### Response

json

```json
{
  "isError": false,
  "errorMsg": "NA",
  "data": {
    "orderId": "OR000001",
    "orderType": "SUBSCRIPTION",
    "status": "submitted",
    "distributorId": "DT000001",
    "investorId": "IN000001",
    "shareClassId": "SC000001",
    "amountSubscription": 100000,
    "amountPayable": 100100,
    "supplyRefs": {
      "mint": { "orderReference": "SUB000123", "submittedAt": 1761808129700 }
    },
    "statusHistory": {
      "1761808000000": { "status": "created", "by": "Network" },
      "1761808129700": { "status": "submitted", "by": "Network", "note": "pushed to mint" }
    }
  }
}
```

The `supplyRefs` block is present once an order has been pushed to a supply-side platform. It carries the supply-side platform's own reference for the order — useful for cross-system reconciliation.

#### Examples

**curl:**

bash

```bash
curl -s $BASE/orders/OR000001 | jq .
```

**Node.js:**

javascript

```javascript
const order = await call('GET', `/orders/${encodeURIComponent(orderId)}`);
```

**Python:**

python

```python
order = call("GET", f"/orders/{order_id}")
```

***

### Push subscription order

Forward a `created` order to the supply-side platform for execution. On success, the order's status transitions to `submitted` and `supplyRefs` is populated with the supply-side reference.

**`POST /orders/subscriptions/push/:orderId`**

#### Path parameters

| Param     | Type   | Description                             |
| --------- | ------ | --------------------------------------- |
| `orderId` | string | The order ID to push, format `OR######` |

#### Request body

None. The push endpoint takes no body; everything it needs is on the order.

#### Headers

The optional `x-actor` header stamps `updatedBy` on the order for audit attribution. If omitted, `updatedBy` defaults to `"Network"`.

#### Response

json

```json
{
  "isError": false,
  "errorMsg": "NA",
  "data": {
    "orderId": "OR000001",
    "status": "submitted",
    "updatedAt": 1761808129700,
    "updatedBy": "Network",
    "supplyRefs": {
      "mint": {
        "orderReference": "SUB000123",
        "submittedAt": 1761808129700,
        "raw": { "request": {}, "response": {} }
      }
    },
    "statusHistory": {
      "1761808000000": { "status": "created", "by": "Network" },
      "1761808129700": { "status": "submitted", "by": "Network", "note": "pushed to mint" }
    }
  }
}
```

The `supplyRefs.<provider>.raw` block records the literal request and response exchanged with the supply-side platform for forensic purposes. Most applications can ignore it.

#### Idempotency

Push is **not** idempotent today. Calling it a second time on an already-submitted order returns:

json

```json
{
  "isError": true,
  "errorMsg": "Cannot push subscription in status \"submitted\" (only \"created\")",
  "data": {}
}
```

This is intentional — push is a state-mutating action that may take side effects downstream, and the API protects against accidental double-pushes.

#### Examples

**curl:**

bash

```bash
curl -sX POST $BASE/orders/subscriptions/push/OR000001 \
  -H 'x-actor: apex-orders'
```

**Node.js:**

javascript

```javascript
const result = await call('POST', `/orders/subscriptions/push/${encodeURIComponent(orderId)}`);
console.log(result.supplyRefs?.mint?.orderReference);
```

**Python:**

python

```python
result = call("POST", f"/orders/subscriptions/push/{order_id}")
print(result.get("supplyRefs", {}).get("mint", {}).get("orderReference"))
```

***

### Cancel subscription order

Cancel a `created` order. Once an order has been pushed (`submitted`), cancellation through this API is no longer possible — the supply-side platform owns that state and any cancellation will need to happen there.

**`POST /orders/subscriptions/cancel/:orderId`**

#### Path parameters

| Param     | Type   | Description                               |
| --------- | ------ | ----------------------------------------- |
| `orderId` | string | The order ID to cancel, format `OR######` |

#### Request body

| Field  | Type   | Required | Description                                             |
| ------ | ------ | -------- | ------------------------------------------------------- |
| `note` | string | No       | Optional free-form reason recorded into `statusHistory` |

#### Response

json

```json
{
  "isError": false,
  "errorMsg": "NA",
  "data": {
    "orderId": "OR000001",
    "status": "cancelled",
    "updatedAt": 1761808200000,
    "statusHistory": {
      "1761808000000": { "status": "created", "by": "Network" },
      "1761808200000": { "status": "cancelled", "by": "Network", "note": "investor changed their mind" }
    }
  }
}
```

Calling cancel on an already-submitted order returns:

json

```json
{
  "isError": true,
  "errorMsg": "Cannot cancel subscription in status \"submitted\" (only \"created\")",
  "data": {}
}
```

#### Examples

**curl:**

bash

```bash
curl -sX POST $BASE/orders/subscriptions/cancel/OR000001 \
  -H 'Content-Type: application/json' \
  -d '{ "note": "investor changed their mind" }'
```

**Node.js:**

javascript

```javascript
await call('POST', `/orders/subscriptions/cancel/${encodeURIComponent(orderId)}`, {
  note: 'investor changed their mind',
});
```

**Python:**

python

```python
call("POST", f"/orders/subscriptions/cancel/{order_id}", { "note": "investor changed their mind" })
```

***

### List subscriptions by distributor

Return every subscription order placed under a given distributor. Useful for back-office views and reconciliation.

**`GET /orders/subscriptions/distributor/:distributorId`**

#### Path parameters

| Param           | Type   | Description       |
| --------------- | ------ | ----------------- |
| `distributorId` | string | Format `DT######` |

#### Query parameters

| Param    | Type   | Required | Description                                                 |
| -------- | ------ | -------- | ----------------------------------------------------------- |
| `status` | string | No       | Filter by status (e.g. `created`, `submitted`, `cancelled`) |

#### Response

json

```json
{
  "isError": false,
  "errorMsg": "NA",
  "data": {
    "items": [
      {
        "orderId": "OR000001",
        "orderType": "SUBSCRIPTION",
        "status": "submitted",
        "investorId": "IN000001",
        "shareClassId": "SC000001",
        "amountSubscription": 100000,
        "amountPayable": 100100,
        "createdAt": 1761808000000
      }
    ],
    "count": 1
  }
}
```

#### Examples

**curl:**

bash

```bash
curl -s "$BASE/orders/subscriptions/distributor/DT000001?status=submitted" | jq .
```

**Node.js:**

javascript

```javascript
async function listByDistributor(distributorId, { status } = {}) {
  const url = new URL(`${BASE}/orders/subscriptions/distributor/${encodeURIComponent(distributorId)}`);
  if (status) url.searchParams.set('status', status);
  const res = await fetch(url);
  const payload = await res.json();
  if (payload.isError) throw new Error(payload.errorMsg);
  return payload.data;
}
```

**Python:**

python

```python
def list_by_distributor(distributor_id, status=None):
    params = {"status": status} if status else None
    r = requests.get(f"{BASE}/orders/subscriptions/distributor/{distributor_id}", params=params, timeout=10)
    payload = r.json()
    if payload["isError"]:
        raise RuntimeError(payload["errorMsg"])
    return payload["data"]
```

***

### List subscriptions by investor

Return every subscription order placed for a specific investor. Use this to render an investor's order history.

**`GET /orders/subscriptions/investor/:investorId`**

#### Path parameters

| Param        | Type   | Description       |
| ------------ | ------ | ----------------- |
| `investorId` | string | Format `IN######` |

#### Query parameters

| Param    | Type   | Required | Description      |
| -------- | ------ | -------- | ---------------- |
| `status` | string | No       | Filter by status |

#### Response

Same shape as the by-distributor list — `{ items: [], count: N }`.

#### Examples

**curl:**

bash

```bash
curl -s $BASE/orders/subscriptions/investor/IN000001 | jq .
```

**Node.js:**

javascript

```javascript
const { items, count } = await call('GET', `/orders/subscriptions/investor/${encodeURIComponent(investorId)}`);
```

**Python:**

python

```python
result = call("GET", f"/orders/subscriptions/investor/{investor_id}")
items, count = result["items"], result["count"]
```

***

### Common errors

* **`Investor not granted access to <shareClassId>`** — The investor is not granted the requested share class. Granting is handled by Synthesys operations.
* **`No wallet configured for investor on chain <chainId>`** — The investor has no wallet on the share class's default chain. Wallet setup is handled by Synthesys operations.
* **`Cannot push subscription in status "<status>" (only "created")`** — You tried to push an order that has already been pushed or cancelled.
* **`Cannot cancel subscription in status "<status>" (only "created")`** — Cancellation is only valid before the push.
* **Push fails with a supply-side error** — The supply-side platform rejected the order (e.g. *"fund not found"*). The order stays in `created` and `errorMsg` carries the upstream reason.

### Patterns

* **Persist `orderId` immediately at placement.** The order's lifecycle is long-running; logging the ID against the user-visible context at placement is the single most useful debugging artefact you can produce.
* **Surface the pricing detail to investors before pushing.** The `created` state exists specifically to let your UI show the investor the priced order — `amountPayable`, `amountTokensEstimated`, the NAV — and obtain confirmation before any downstream commitment.
* **Always pass `x-actor`.** Both the placement and the push will populate audit fields from this header. Audit data is useless if every order is attributed to `"Network"`.
* **Validate inputs in your application first.** Negative or zero `amountSubscription`, missing IDs, wrong format — catch these client-side for faster feedback to the end-user.
* **Use `externalRef` to make reconciliation trivial.** Pass your own order ID as `externalRef` on every subscription; it's stored verbatim and surfaces in every read, so cross-system reconciliation is a one-field join.
