> 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/errors.md).

# Errors

The Synthesys Network API uses a single, uniform response envelope across every endpoint, every method, and every error case. This means the work of "did this request succeed?" is always the same one-line check, and the work of "what went wrong?" always lives in the same field. This page covers the envelope, the HTTP status conventions, and the patterns we recommend for retries and observability.

### The response envelope

Every response — `2xx`, `4xx`, and `5xx` alike — has this shape:

**Success:**

json

```json
{
  "isError": false,
  "errorMsg": "NA",
  "data": { }
}
```

**Error:**

json

```json
{
  "isError": true,
  "errorMsg": "Investor not granted access to SC000001",
  "data": {}
}
```

| Field      | Type            | Notes                                                                               |
| ---------- | --------------- | ----------------------------------------------------------------------------------- |
| `isError`  | boolean         | `false` on success, `true` on error. The single source of truth for success/failure |
| `errorMsg` | string          | The literal value `"NA"` on success, or a human-readable error message on failure   |
| `data`     | object \| array | Payload on success; an empty object `{}` on error                                   |

Two consequences of using a uniform envelope are worth calling out:

* **You cannot tell success from failure by HTTP status code alone.** Always check `isError`. A request that returns `200 OK` with `isError: true` is rare but possible (for example, a partial validation failure on a write). Conversely, a `500` will always have `isError: true` and a useful `errorMsg`.
* **Your application should unwrap `data` once.** Build a tiny helper that returns `data` on success and raises with `errorMsg` on `isError === true`. That helper becomes the only place you ever need to know about the envelope.

#### A minimal unwrap helper

**Node.js:**

javascript

```javascript
async function call(method, path, body) {
  const res = await fetch(`${BASE}${path}`, {
    method,
    headers: { 'Content-Type': 'application/json' },
    body: body ? JSON.stringify(body) : undefined,
  });
  const payload = await res.json().catch(() => ({}));
  if (!res.ok || payload.isError) {
    const msg = payload.errorMsg || `HTTP ${res.status}`;
    const err = new Error(`Synthesys API error: ${msg}`);
    err.status = res.status;
    err.body = payload;
    throw err;
  }
  return payload.data;
}
```

**Python:**

python

```python
import requests

class SynthesysError(RuntimeError):
    def __init__(self, status, message, body):
        super().__init__(f"Synthesys API error ({status}): {message}")
        self.status = status
        self.body = body

def call(method, path, body=None):
    r = requests.request(method, f"{BASE}{path}", json=body, timeout=10)
    payload = r.json() if r.headers.get("content-type", "").startswith("application/json") else {}
    if not r.ok or payload.get("isError"):
        raise SynthesysError(r.status_code, payload.get("errorMsg") or f"HTTP {r.status_code}", payload)
    return payload.get("data")
```

### HTTP status conventions

The API follows standard HTTP semantics:

| Status                      | Meaning                                                                            | Retry                                                   |
| --------------------------- | ---------------------------------------------------------------------------------- | ------------------------------------------------------- |
| `200 OK`                    | Success. `isError` will be `false` and `errorMsg` will be `"NA"`                   | n/a                                                     |
| `400 Bad Request`           | Malformed JSON, missing required field, or invalid enum value. Caller error        | No — fix the request                                    |
| `404 Not Found`             | A referenced entity (e.g. `shareClassId`) does not exist                           | No — verify the ID                                      |
| `409 Conflict`              | The request conflicts with current state (e.g. pushing an already-submitted order) | No — re-read the state and retry the appropriate action |
| `500 Internal Server Error` | Unhandled server-side failure                                                      | Yes, with backoff (see below)                           |
| `503 Service Unavailable`   | Backing store or upstream not reachable                                            | Yes, with backoff                                       |

### Common error scenarios

These are the errors you will hit most often in practice. Handling them in your application up front will save a lot of debugging time:

* **Unknown referenced ID.** `POST /investor/register` with a `distributorId` that doesn't exist returns `errorMsg: "distributorId not found: DT…"`. Cause: the distributor wasn't created in this environment, or the ID is wrong.
* **Missing required field.** Any write endpoint will return `isError: true` if a required field is missing or `null`. The `errorMsg` will name the field. Fix: check the request body against the field table on the relevant endpoint page.
* **Investor onboarding incomplete.** `POST /orders/subscribe` returns errors like *"Investor not granted access to SC000001"* or *"No wallet configured for investor on chain 1"* when the investor's wallet or grant has not been set up. Coordinate with Synthesys operations to complete onboarding.
* **State transition violations.** Push and cancel only operate on `created` orders. Attempting either on a `submitted` order returns *"Cannot push subscription in status "submitted" (only "created")"* or *"Cannot cancel subscription in status "submitted" (only "created")"*. Re-read the order first if you're unsure of its state.
* **Supply-side rejection on push.** `POST /orders/subscriptions/push/:orderId` may surface an error from the downstream supply-side platform (e.g. *"Mint /createSubscriptionOrder rejected: fund not found"*). The order stays in `created` and `errorMsg` carries the upstream reason.

### Retry strategy

Retries on writes carry a small risk of double-creation in the rare case where the server processed the first attempt but the response was lost in transit. We recommend the following posture:

* **Reads** — retry up to 3 times with exponential backoff (e.g. 200ms, 800ms, 2s) on `5xx` and on connection errors.
* **Writes that allocate IDs** (`POST /investor/register`, `POST /orders/subscribe`) — do **not** retry blindly on `5xx`; instead, log the failure, defer the retry, and reconcile by reading (e.g. by `externalRef` for orders, by `externalInvestorId` for investors). The cost of a duplicate investor or order is real; the cost of a delayed creation is low.
* **State-mutating writes that aren't naturally idempotent** (`POST /orders/subscriptions/push/:orderId`, `POST /orders/subscriptions/cancel/:orderId`) — do **not** auto-retry. Re-read the order first to confirm whether the previous attempt landed.

A simple wrapper:

**Node.js:**

javascript

```javascript
async function callWithRetry(method, path, body, { attempts = 3 } = {}) {
  let lastErr;
  for (let i = 0; i < attempts; i++) {
    try {
      return await call(method, path, body);
    } catch (err) {
      lastErr = err;
      if (!err.status || err.status < 500) throw err; // don't retry 4xx
      await new Promise(r => setTimeout(r, 200 * Math.pow(4, i)));
    }
  }
  throw lastErr;
}
```

**Python:**

python

```python
import time

def call_with_retry(method, path, body=None, attempts=3):
    last = None
    for i in range(attempts):
        try:
            return call(method, path, body)
        except SynthesysError as e:
            last = e
            if e.status < 500:
                raise
            time.sleep(0.2 * (4 ** i))
    raise last
```

### Logging and observability

Because the envelope is uniform, a single log line per outbound call is enough:

```
[synthesys] POST /orders/subscribe status=200 isError=false latencyMs=42
[synthesys] POST /orders/subscriptions/push/OR000001 status=409 isError=true errorMsg="Cannot push subscription in status \"submitted\" (only \"created\")" latencyMs=18
```

Capture `status`, `isError`, `errorMsg`, and a correlation ID you generate on your side. That gives you a clean view of error rates without parsing free-form messages.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://synthesys-2.gitbook.io/synthesys-docs/network-overview/api-reference/errors.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
