# STAR API

- **OpenAPI Version:** `3.2.0`
- **API Version:** `1.0.0`

Read and update FX trades in STAR. Every call acts as the STAR user who signed in, so you see and change only what that user can.

## Authentication

Send a Microsoft Entra token on every `/v1` call:

```
Authorization: Bearer <token>
```

The token must be a v2.0 token issued for the application `15ff670b-f213-41fe-9bcf-0ef6d69a7834` (its `aud` claim) by an accepted tenant, and carry the user's `email` or `preferred_username`. A STAR user with that email must exist, be active and not be a Guest.

## Use this API with an AI agent

The whole reference is available as Markdown at <https://star-api.finmetrix.com/llms.txt> and as OpenAPI 3.1 at <https://star-api.finmetrix.com/openapi.json>. Paste this into your coding agent:

```text
Read https://star-api.finmetrix.com/llms.txt, then write a client for the STAR API.
- Send the Entra token I give you as "Authorization: Bearer <token>".
  Never log it.
- Before changing a trade, call PATCH with ?dryRun=true and show me
  the "changes" it returns. Send the real PATCH only after I agree.
- Send only the fields that should change.
- On 502 or 504, read the trade again before retrying:
  the update may have been saved.
- Handle errors by "error.code", not by the message.
```

## Errors

Every error has the same shape. `details` is present when the error is about specific fields.

```json
{
  "error": {
    "code": "invalid_body",
    "message": "Some fields are invalid",
    "details": [{ "field": "quantity", "message": "must be a number greater than 0" }]
  }
}
```

| Code                         | Meaning                                                                                                |
| ---------------------------- | ------------------------------------------------------------------------------------------------------ |
| `invalid_body`               | The body is not a JSON object, or a field is invalid or cannot be updated. `details` names each field. |
| `empty_patch`                | The body has no field to update.                                                                       |
| `invalid_trade_id`           | The trade id is not a positive integer.                                                                |
| `unauthorized`               | The token is missing, invalid, expired, issued for another application or tenant, or has no email.     |
| `star_user_not_allowed`      | No active STAR user with edit rights has the email in the token.                                       |
| `trade_not_found`            | No trade has this id, or it is not in one of your divisions.                                           |
| `not_found`                  | No such path.                                                                                          |
| `method_not_allowed`         | The path exists but not for this method.                                                               |
| `trade_not_editable`         | The deal is Cancelled or Executed.                                                                     |
| `maturity_before_trade_date` | A trade of the deal would mature before the trade date. `details` names each one.                      |
| `star_error`                 | STAR answered with an error. The update may not have been saved; read the trade before retrying.       |
| `star_unreachable`           | STAR could not be reached.                                                                             |
| `star_timeout`               | STAR did not answer within 60 seconds. Read the trade before retrying.                                 |
| `internal_error`             | Unexpected error.                                                                                      |

## Contents

**Operations**

- [GET /v1/trades/{operationId}](#scalar-operation-get-v1tradesoperationid)
- [PATCH /v1/trades/{operationId}](#scalar-operation-patch-v1tradesoperationid)
- [GET /health](#scalar-operation-get-health)

**Schemas**

- [TradePatch](#scalar-schema-tradepatch)
- [Trade](#scalar-schema-trade)
- [Change](#scalar-schema-change)
- [TradeUpdate](#scalar-schema-tradeupdate)
- [Error](#scalar-schema-error)

<a id="scalar-context-global-servers"></a>

## Servers

- **URL:** `https://star-api.finmetrix.com`

<a id="scalar-context-global-authentication"></a>

## Authentication

- **entra**: HTTP bearer (`JWT`)

  Microsoft Entra v2.0 token issued for application 15ff670b-f213-41fe-9bcf-0ef6d69a7834.

## Tags

### Trades

A trade is one FX operation of a deal. Its id is the Trade Id shown in STAR.

### Service

## Operations

<a id="scalar-operation-get-v1tradesoperationid"></a>

### Get a trade

- **Method:** `GET`
- **Path:** `/v1/trades/{operationId}`
- **Operation ID:** `getTrade`
- **Tags:** Trades

Returns the trade as the token user sees it.

**Servers:** [Inherited servers](#scalar-context-global-servers)

**Authentication:** [Global authentication](#scalar-context-global-authentication)

#### Path parameters

- **`operationId` (required)**: `integer`, minimum: `1`, maximum: `2147483647`

  The Trade Id shown in STAR.

  **Example:**

  ```json
  118730
  ```

#### Responses

##### 200 The trade

**Content type:** `application/json`

[Trade](#scalar-schema-trade)

**Example:**

```json
{
  "operationId": 118730,
  "dealId": 70869,
  "status": "confirmed",
  "tradeDate": "2026-11-03",
  "maturityDate": "2026-11-20",
  "contractRate": 1.4,
  "quantity": 3000000,
  "side": "buy",
  "currency": "CAD",
  "counterCurrency": "USD",
  "counterAmount": 2142857.14,
  "lastModified": "2026-11-10T14:02:11.480",
  "lastUser": "vega.hitti@finmetrix.com"
}
```

##### 400 The trade id is invalid

**Content type:** `application/json`

[Error](#scalar-schema-error)

**Example: invalid_trade_id**

```json
{
  "error": {
    "code": "invalid_trade_id",
    "message": "The trade id is not a positive integer."
  }
}
```

##### 401 The token was not accepted

**Content type:** `application/json`

[Error](#scalar-schema-error)

**Example: unauthorized**

```json
{
  "error": {
    "code": "unauthorized",
    "message": "The token is missing, invalid, expired, issued for another application or tenant, or has no email."
  }
}
```

##### 403 The token user cannot use STAR

**Content type:** `application/json`

[Error](#scalar-schema-error)

**Example: star_user_not_allowed**

```json
{
  "error": {
    "code": "star_user_not_allowed",
    "message": "No active STAR user with edit rights has the email in the token."
  }
}
```

##### 404 The trade is not visible to you

**Content type:** `application/json`

[Error](#scalar-schema-error)

**Example: trade_not_found**

```json
{
  "error": {
    "code": "trade_not_found",
    "message": "No trade has this id, or it is not in one of your divisions."
  }
}
```

##### 502 STAR failed

**Content type:** `application/json`

[Error](#scalar-schema-error)

**Example: star_error**

```json
{
  "error": {
    "code": "star_error",
    "message": "STAR answered with an error. The update may not have been saved; read the trade before retrying."
  }
}
```

**Example: star_unreachable**

```json
{
  "error": {
    "code": "star_unreachable",
    "message": "STAR could not be reached."
  }
}
```

##### 504 STAR timed out

**Content type:** `application/json`

[Error](#scalar-schema-error)

**Example: star_timeout**

```json
{
  "error": {
    "code": "star_timeout",
    "message": "STAR did not answer within 60 seconds. Read the trade before retrying."
  }
}
```

<a id="scalar-operation-patch-v1tradesoperationid"></a>

### Update a trade

- **Method:** `PATCH`
- **Path:** `/v1/trades/{operationId}`
- **Operation ID:** `updateTrade`
- **Tags:** Trades

Changes one or more fields of a trade and answers with the trade as saved.

- **Send only what changes.** At least one field is required and unknown fields are rejected.
- **Only Pending and Confirmed deals can be updated.**
- **The trade date belongs to the deal.** Changing it changes every trade of the deal (both legs of a swap); the change is reported with `"scope": "deal"`.
- **Derived values follow STAR's deal form.** A new rate or quantity recomputes `counterAmount`; a new rate or side recomputes the gain/loss.
- **`changes` lists only what differs.** If nothing does, nothing is saved and `changes` is empty.
- **The save has no concurrency check.** Avoid updating a deal while someone edits it in STAR.

**Servers:** [Inherited servers](#scalar-context-global-servers)

**Authentication:** [Global authentication](#scalar-context-global-authentication)

#### Path parameters

- **`operationId` (required)**: `integer`, minimum: `1`, maximum: `2147483647`

  The Trade Id shown in STAR.

  **Example:**

  ```json
  118730
  ```

#### Query parameters

- **`dryRun`**: `boolean`, default: `false`

  Validate and answer with the result without saving. Use it to preview `changes`.

#### Request body

**Required:** `true`

**Content type:** `application/json`

[TradePatch](#scalar-schema-tradepatch)

The fields to change. Send at least one.

**Example:**

```json
{
  "contractRate": 1.4,
  "maturityDate": "2026-11-20"
}
```

#### Responses

##### 200 The trade after the update, and what changed

**Content type:** `application/json`

[TradeUpdate](#scalar-schema-tradeupdate)

**Example:**

```json
{
  "operationId": 118730,
  "dealId": 70869,
  "status": "confirmed",
  "tradeDate": "2026-11-03",
  "maturityDate": "2026-11-20",
  "contractRate": 1.4,
  "quantity": 3000000,
  "side": "buy",
  "currency": "CAD",
  "counterCurrency": "USD",
  "counterAmount": 2142857.14,
  "lastModified": "2026-11-10T14:02:11.480",
  "lastUser": "vega.hitti@finmetrix.com",
  "changes": [
    {
      "field": "contractRate",
      "from": 1.398,
      "to": 1.4
    },
    {
      "field": "maturityDate",
      "from": "2026-11-19",
      "to": "2026-11-20"
    }
  ],
  "dryRun": false
}
```

##### 400 The request is invalid

**Content type:** `application/json`

[Error](#scalar-schema-error)

**Example: invalid_body**

```json
{
  "error": {
    "code": "invalid_body",
    "message": "The body is not a JSON object, or a field is invalid or cannot be updated. `details` names each field."
  }
}
```

**Example: empty_patch**

```json
{
  "error": {
    "code": "empty_patch",
    "message": "The body has no field to update."
  }
}
```

**Example: invalid_trade_id**

```json
{
  "error": {
    "code": "invalid_trade_id",
    "message": "The trade id is not a positive integer."
  }
}
```

##### 401 The token was not accepted

**Content type:** `application/json`

[Error](#scalar-schema-error)

**Example: unauthorized**

```json
{
  "error": {
    "code": "unauthorized",
    "message": "The token is missing, invalid, expired, issued for another application or tenant, or has no email."
  }
}
```

##### 403 The token user cannot use STAR

**Content type:** `application/json`

[Error](#scalar-schema-error)

**Example: star_user_not_allowed**

```json
{
  "error": {
    "code": "star_user_not_allowed",
    "message": "No active STAR user with edit rights has the email in the token."
  }
}
```

##### 404 The trade is not visible to you

**Content type:** `application/json`

[Error](#scalar-schema-error)

**Example: trade_not_found**

```json
{
  "error": {
    "code": "trade_not_found",
    "message": "No trade has this id, or it is not in one of your divisions."
  }
}
```

##### 409 The trade cannot be updated in its status

**Content type:** `application/json`

[Error](#scalar-schema-error)

**Example: trade_not_editable**

```json
{
  "error": {
    "code": "trade_not_editable",
    "message": "The deal is Cancelled or Executed."
  }
}
```

##### 422 The dates would be inconsistent

**Content type:** `application/json`

[Error](#scalar-schema-error)

**Example: maturity_before_trade_date**

```json
{
  "error": {
    "code": "maturity_before_trade_date",
    "message": "A trade of the deal would mature before the trade date. `details` names each one."
  }
}
```

##### 502 STAR failed

**Content type:** `application/json`

[Error](#scalar-schema-error)

**Example: star_error**

```json
{
  "error": {
    "code": "star_error",
    "message": "STAR answered with an error. The update may not have been saved; read the trade before retrying."
  }
}
```

**Example: star_unreachable**

```json
{
  "error": {
    "code": "star_unreachable",
    "message": "STAR could not be reached."
  }
}
```

##### 504 STAR timed out

**Content type:** `application/json`

[Error](#scalar-schema-error)

**Example: star_timeout**

```json
{
  "error": {
    "code": "star_timeout",
    "message": "STAR did not answer within 60 seconds. Read the trade before retrying."
  }
}
```

<a id="scalar-operation-get-health"></a>

### Health check

- **Method:** `GET`
- **Path:** `/health`
- **Operation ID:** `health`
- **Tags:** Service

**Servers:** [Inherited servers](#scalar-context-global-servers)

#### Authentication

No authentication required.

#### Responses

##### 200 The API is up

**Content type:** `text/plain`

`string`

**Example:**

```json
"ok"
```

## Schemas

<a id="scalar-schema-tradepatch"></a>

### TradePatch

**Type:** `object`, minProperties: `1`, no additional properties

The fields to change. Send at least one.

- **`contractRate`**: `number`, exclusiveMinimum: `0`

  Contract Rate.
- **`maturityDate`**: `string`, format: `date`

  Maturity. Also sets the settlement date, start date and hedge month.
- **`quantity`**: `number`, exclusiveMinimum: `0`

  Quantity Com.Ccy: the amount in `currency`, always positive.
- **`side`**: `string`, possible values: `"buy", "sell"`

  Buy / Sell of `currency`.
- **`tradeDate`**: `string`, format: `date`

  Trade Date. Belongs to the deal: changing it changes every trade of the deal.

<a id="scalar-example-1"></a>

**Generated example:**

```json
{
  "tradeDate": "2026-11-03",
  "maturityDate": "2026-11-20",
  "contractRate": 1.4,
  "quantity": 3000000,
  "side": "buy"
}
```

<a id="scalar-schema-trade"></a>

### Trade

**Type:** `object`

- **`contractRate` (required)**: `number`, exclusiveMinimum: `0`

  Contract Rate.
- **`counterAmount` (required)**: `number`

  The amount in `counterCurrency`, from quantity and rate.
- **`counterCurrency` (required)**: `string`

  The other currency of the pair.
- **`currency` (required)**: `string`

  The commodity currency, which `quantity` and `side` refer to.
- **`dealId` (required)**: `integer`

  The deal the trade belongs to.
- **`lastModified` (required)**: `string`

  When the deal was last saved, in STAR local time without an offset.
- **`lastUser` (required)**: `string`

  The STAR user who last saved the deal.
- **`maturityDate` (required)**: `string`, format: `date`

  Maturity. Also sets the settlement date, start date and hedge month.
- **`operationId` (required)**: `integer`

  The Trade Id shown in STAR.
- **`quantity` (required)**: `number`, exclusiveMinimum: `0`

  Quantity Com.Ccy: the amount in `currency`, always positive.
- **`side` (required)**: `string`, possible values: `"buy", "sell"`

  Buy / Sell of `currency`.
- **`status` (required)**: `string`, possible values: `"pending", "confirmed", "executed", "cancelled", "unknown"`

  Status of the deal.
- **`tradeDate` (required)**: `string`, format: `date`

  Trade Date. Belongs to the deal: changing it changes every trade of the deal.

**Example:**

```json
{
  "operationId": 118730,
  "dealId": 70869,
  "status": "confirmed",
  "tradeDate": "2026-11-03",
  "maturityDate": "2026-11-20",
  "contractRate": 1.4,
  "quantity": 3000000,
  "side": "buy",
  "currency": "CAD",
  "counterCurrency": "USD",
  "counterAmount": 2142857.14,
  "lastModified": "2026-11-10T14:02:11.480",
  "lastUser": "vega.hitti@finmetrix.com"
}
```

<a id="scalar-schema-change"></a>

### Change

**Type:** `object`

- **`field` (required)**: `string`, possible values: `"tradeDate", "maturityDate", "contractRate", "quantity", "side"`
- **`from` (required)**: `string | number`

  The value before.
- **`to` (required)**: `string | number`

  The value after.
- **`scope`**: `string`, possible values: `"deal"`

  Present when the change applies to every trade of the deal.

<a id="scalar-example-2"></a>

**Generated example:**

```json
{
  "field": "tradeDate",
  "from": "",
  "to": "",
  "scope": "deal"
}
```

<a id="scalar-schema-tradeupdate"></a>

### TradeUpdate

**All of:**

- [Trade](#scalar-schema-trade)
- `object`
  - **`changes` (required)**: array of [Change](#scalar-schema-change)

    What the update changed; empty when nothing differed.
  - **`dryRun` (required)**: `boolean`

    True when nothing was saved because `dryRun` was set.

**Example:**

```json
{
  "operationId": 118730,
  "dealId": 70869,
  "status": "confirmed",
  "tradeDate": "2026-11-03",
  "maturityDate": "2026-11-20",
  "contractRate": 1.4,
  "quantity": 3000000,
  "side": "buy",
  "currency": "CAD",
  "counterCurrency": "USD",
  "counterAmount": 2142857.14,
  "lastModified": "2026-11-10T14:02:11.480",
  "lastUser": "vega.hitti@finmetrix.com",
  "changes": [
    {
      "field": "contractRate",
      "from": 1.398,
      "to": 1.4
    },
    {
      "field": "maturityDate",
      "from": "2026-11-19",
      "to": "2026-11-20"
    }
  ],
  "dryRun": false
}
```

<a id="scalar-schema-error"></a>

### Error

**Type:** `object`

- **`error` (required)**: `object`
  - **`code` (required)**: `string`, possible values: `"invalid_body", "empty_patch", "invalid_trade_id", "unauthorized", "star_user_not_allowed", "trade_not_found", "not_found", "method_not_allowed", "trade_not_editable", "maturity_before_trade_date", "star_error", "star_unreachable", "star_timeout", "internal_error"`

    Stable identifier; handle errors by this.
  - **`message` (required)**: `string`

    For people; may change.
  - **`details`**: `array`

    Present when the error is about specific fields.

    **Items:**
    - **`field` (required)**: `string`
    - **`message` (required)**: `string`

<a id="scalar-example-3"></a>

**Generated example:**

```json
{
  "error": {
    "code": "invalid_body",
    "message": "",
    "details": [
      {
        "field": "",
        "message": ""
      }
    ]
  }
}
```
