# Update analysis

Change a pending chart analysis's title, scenarios, expiry, timeframe, direction with new levels, or its drawings.

- Endpoint: `PATCH https://api.returning.ai/v1/analysis/{analysisId}`
- Section: Community / Chart analysis
- Authentication: `Authorization: Bearer <API_KEY>` (Community API key)
- Permission: `sendMessage` (Shown in the dashboard as "Send Messages")
- Retries: No Idempotency-Key; read back before retrying
- Guide: hand-written
- Verified: code, 2026-09-27
- Last updated: 26 Sep 2026
- Web page: https://docs.returning.ai/api-reference/chart-analysis/update-analysis

## When to use this

- Correct a typo in an analysis title or scenario after it was posted.
- Extend or shorten how long an idea stands by changing its expiry.
- Flip an idea from bullish to bearish, with a new set of levels.

**Instead:** Use [Append drawings](https://docs.returning.ai/api-reference/chart-analysis/append-drawings.md) instead to add drawings without resending the others.

**Posts in the channel:** Any update with `metadata` or `levels` rewrites the channel post and fires your message webhooks. A new title, scenario, type or levels also adds a bot message about the edit to the card's discussion.

## Authentication

- Header: `Authorization: Bearer <API_KEY>`
- Permission: `sendMessage`

Use a Community API key with `sendMessage`, shown in the dashboard as Send Messages, and keep it on your server. Name the member who created the analysis in `sender`; only they can update it. Only send IDs from your own community's analyses. Personal API keys are not accepted here and return `401`.

## Behaviour

Only a `Pending` analysis can be updated. Send at least one of `metadata`, `levels` or `drawings`. Fields you leave out, or send empty, keep their values, and `metadata.symbol` is ignored.

The card type comes from `direction` and `entryPlacement`. Levels move with the type: if a change to either field gives a new type, send a full `levels` set in the same request, and `levels` is refused in any other request. Changing `direction` or `entryPlacement` without changing the type, for example sending the current value again, counts as no type change.

`drawings` replaces the whole drawing set, and is saved only alongside a `metadata` or `levels` change. To add drawings on their own, use Append drawings.

Any request with `metadata` or `levels` rewrites the channel post and fires your message webhooks. When the title, a scenario, the type or the levels change, a bot message about the edit is also added to the card's discussion. Members with the channel open see the change straight away.

## Request

### Path parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `analysisId` | `string` | Yes | The analysis ID from Create analysis or List analyses. (24 hex characters) |

### Headers

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `Authorization` | `string` | Yes | Community API key with `sendMessage`. (`Bearer <API_KEY>`) |
| `Content-Type` | `string` | Yes | Request body format. (`application/json`) |

### Body

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `sender` | `string` | Yes | Username or email of the member who created the analysis. Required with a Community API key; any other member is refused. |
| `metadata` | `object` | No | The fields to change. Leave a field out, or send it empty, to keep its value. The symbol can't be changed. |
| `metadata.title` | `string` | No | New card title. (1-200 characters) |
| `metadata.preferenceText` | `string` | No | New main scenario. `[[clientObjectId:text]]` links must name a level or drawing on the card. (1-5000 characters) |
| `metadata.alternativeText` | `string` | No | New alternative scenario. Same links. (Up to 5000 characters) |
| `metadata.comments` | `string` | No | New comments. Same links. (Up to 5000 characters) |
| `metadata.expiry` | `object` | No | New expiry: `{"type": "GTC"}`, or `{"type": "HOURS", "hours": 1-8760}`. |
| `metadata.expiry.type` | `string` | Yes | Good Till Cancelled (`GTC`) |
| `metadata.expiry.hours` | `integer` | No | Hours the idea stands. Required when `type` is `HOURS`. (1-8760) |
| `metadata.timeframe` | `string` | No | New chart timeframe. (`M1`, `M5`, `M15`, `M30`, `H1`, `H2`, `H4`, `D`, `W` or `M`) |
| `metadata.direction` | `string` | No | New market bias. If this changes the card type, `levels` is required in the same request. (`BULLISH` or `BEARISH`) |
| `metadata.entryPlacement` | `string` | No | New entry placement. If this changes the card type, `levels` is required in the same request. (`ABOVE_CURRENT` or `BELOW_CURRENT`) |
| `levels` | `object` | No | A full new set of levels, in the same shape as Create analysis (pivot, resistance and support required, in price order). Accepted only in a request whose `direction` or `entryPlacement` changes the card type; otherwise it returns `400` `Pivot value can not be changed`. |
| `levels.pivot` | `object` | Yes | The centre level. |
| `levels.pivot.price` | `number` | Yes | Price of the level. |
| `levels.pivot.clientObjectId` | `string` | Yes | Your own name for the level, unique among the levels. Use it in text links. (Min 1 chars) |
| `levels.pivot.time` | `string` | No | Optional chart time for the level. (ISO 8601 date-time) |
| `levels.resistance` | `object` | Yes | Main resistance, above the pivot. |
| `levels.resistance.price` | `number` | Yes | Price of the level. |
| `levels.resistance.clientObjectId` | `string` | Yes | Your own name for the level, unique among the levels. Use it in text links. (Min 1 chars) |
| `levels.resistance.time` | `string` | No | Optional chart time for the level. (ISO 8601 date-time) |
| `levels.support` | `object` | Yes | Main support, below the pivot. |
| `levels.support.price` | `number` | Yes | Price of the level. |
| `levels.support.clientObjectId` | `string` | Yes | Your own name for the level, unique among the levels. Use it in text links. (Min 1 chars) |
| `levels.support.time` | `string` | No | Optional chart time for the level. (ISO 8601 date-time) |
| `levels.intermediateResistance` | `object` | No | Optional level between the pivot and resistance. |
| `levels.intermediateResistance.price` | `number` | Yes | Price of the level. |
| `levels.intermediateResistance.clientObjectId` | `string` | Yes | Your own name for the level, unique among the levels. Use it in text links. (Min 1 chars) |
| `levels.intermediateResistance.time` | `string` | No | Optional chart time for the level. (ISO 8601 date-time) |
| `levels.intermediateSupport` | `object` | No | Optional level between support and the pivot. |
| `levels.intermediateSupport.price` | `number` | Yes | Price of the level. |
| `levels.intermediateSupport.clientObjectId` | `string` | Yes | Your own name for the level, unique among the levels. Use it in text links. (Min 1 chars) |
| `levels.intermediateSupport.time` | `string` | No | Optional chart time for the level. (ISO 8601 date-time) |
| `drawings` | `object[]` | No | Replaces every drawing on the card; `[]` removes them all. Saved only when the same request also sends `metadata` or `levels`: a request with only `drawings` returns `200` and changes nothing. (0-20 items) |
| `drawings.type` | `string` | Yes | Drawing type. `PARALLEL` needs exactly 3 points; `FIB_RETRACEMENT` needs 2 or 3. (`HORIZONTAL_LINE`, `LINE`, `RECTANGLE`, `PARALLEL` or `FIB_RETRACEMENT`) |
| `drawings.clientObjectId` | `string` | Yes | Your own name for the drawing, unique in the request. Use it to find the drawing again in the response. (Min 1 chars) |
| `drawings.points` | `object[]` | Yes | Chart points for the drawing. (3 for `PARALLEL`, 2-3 for `FIB_RETRACEMENT`) |
| `drawings.points.time` | `string \| integer` | Yes | Point time. (ISO 8601 date-time in UTC ending in `Z`, or epoch milliseconds) |
| `drawings.points.price` | `number` | Yes | Point price. |
| `drawings.style` | `object` | No | Optional colours and line style. |
| `drawings.style.color` | `string` | No | Line colour. (`#RRGGBB`) |
| `drawings.style.lineWidth` | `integer` | No | Line width. (1-10) |
| `drawings.style.lineStyle` | `string` | No | Line style. (`SOLID`, `DASHED` or `DOTTED`) |
| `drawings.style.fillColor` | `string` | No | Fill colour. An alpha channel such as `#00FF0033` is rejected. (`#RRGGBB`) |
| `drawings.style.borderColor` | `string` | No | Border colour. (`#RRGGBB`) |
| `drawings.style.opacity` | `number` | No | Opacity. (0-1) |
| `drawings.startHead` | `string` | No | `LINE` only: the start decoration. (`NONE`, `ARROW` or `EXTEND`; default `NONE`) |
| `drawings.endHead` | `string` | No | `LINE` only: the end decoration. (`NONE`, `ARROW` or `EXTEND`; default `NONE`) |
| `drawings.levels` | `number[]` | No | `FIB_RETRACEMENT` only: the ratios to draw. Omit for the standard set from -0.618 to 1.618. |

### Example request

```bash
curl --request PATCH \
  --url https://api.returning.ai/v1/analysis/66f000000000000000000b01 \
  --header 'Authorization: Bearer <API_KEY>' \
  --header 'Content-Type: application/json' \
  --data '{
    "sender": "trader@example.com",
    "metadata": {
      "title": "EURUSD bounce holds above support"
    }
  }'
```

## Response

A `200` returns the updated analysis in `data.analysis`, not a list of changed fields. Replaced drawings get new saved IDs, so match them on your `clientObjectId`.

### Response fields

| Field | Type | Presence | Description |
| --- | --- | --- | --- |
| `status` | `string` | always | Result of the request. (`success`) |
| `statusCode` | `integer` | always | The HTTP status, repeated. |
| `message` | `string` | always | Human-readable summary. Do not branch on it. |
| `data` | `object` | always | Holds the updated analysis. |
| `data.analysis` | `object` | always | The analysis after the update, with the same fields as `data.analysis` in Get analysis, without `message`. |

### Example response (200)

```json
{
  "status": "success",
  "statusCode": 200,
  "message": "Analysis updated successfully",
  "data": {
    "analysis": {
      "_id": "66f000000000000000000b01",
      "owner": {
        "id": "3247779"
      },
      "tempId": null,
      "serverId": "66f000000000000000000010",
      "topicId": "66f000000000000000000b11",
      "commentId": "66f000000000000000000b21",
      "timeframe": "H1",
      "users": {
        "up": [],
        "down": []
      },
      "shapes": [
        {
          "key": "analysis",
          "levels": {
            "pivot": {
              "price": 1.1,
              "clientObjectId": "pivot_main",
              "time": "2026-09-27T08:00:00.000Z"
            },
            "resistance": {
              "price": 1.12,
              "clientObjectId": "resistance_main",
              "time": "2026-09-27T08:00:00.000Z"
            },
            "support": {
              "price": 1.08,
              "clientObjectId": "support_main",
              "time": "2026-09-27T08:00:00.000Z"
            }
          },
          "metadata": {
            "direction": "BULLISH",
            "entryPlacement": "BELOW_CURRENT",
            "expiry": {
              "type": "HOURS",
              "hours": 24
            },
            "title": "EURUSD bounce holds above support",
            "preferenceText": "Holding above [[support_main:support]], targeting [[resistance_main:resistance]].",
            "timeframe": "H1",
            "symbol": "EURUSD"
          },
          "id": "66f000000000000000000b41",
          "isRaw": true,
          "timeRange": [1790496000000, 1790496312431]
        },
        {
          "type": "HORIZONTAL_LINE",
          "clientObjectId": "entry_line",
          "points": [
            {
              "time": "2026-09-27T08:00:00.000Z",
              "price": 1.095
            }
          ],
          "style": {
            "color": "#2E7D32"
          },
          "key": "h-ray",
          "id": "66f000000000000000000b31",
          "isRaw": true,
          "timeRange": [1790035200000, 1790956800000]
        }
      ],
      "category": "analysis",
      "extra": {
        "happened": "[{\"type\":\"p\",\"children\":[{\"text\":\"Holding above \"},{\"type\":\"linked-text\",\"hoverIds\":[\"66f000000000000000000b41:0\"],\"children\":[{\"text\":\"support\",\"type\":\"text\",\"id\":\"7c1e4f9a-2b6d-4e0a-9f3b-1a2c3d4e5f61\"}],\"id\":\"7c1e4f9a-2b6d-4e0a-9f3b-1a2c3d4e5f71\"},{\"text\":\", targeting \"},{\"type\":\"linked-text\",\"hoverIds\":[\"66f000000000000000000b41:7\"],\"children\":[{\"text\":\"resistance\",\"type\":\"text\",\"id\":\"7c1e4f9a-2b6d-4e0a-9f3b-1a2c3d4e5f62\"}],\"id\":\"7c1e4f9a-2b6d-4e0a-9f3b-1a2c3d4e5f72\"},{\"text\":\".\"}],\"id\":\"7c1e4f9a-2b6d-4e0a-9f3b-1a2c3d4e5f60\"}]",
        "expected": "[{\"type\":\"p\",\"children\":[{\"text\":\"\"}],\"id\":\"7c1e4f9a-2b6d-4e0a-9f3b-1a2c3d4e5f80\"}]",
        "comments": "[{\"type\":\"p\",\"children\":[{\"text\":\"\"}],\"id\":\"7c1e4f9a-2b6d-4e0a-9f3b-1a2c3d4e5f80\"}]",
        "direction": "bullish",
        "currency_pair": "EURUSD",
        "horizon": "SHORT",
        "userId": 3247779,
        "level": [
          {
            "title": "1st Support",
            "value": 1.08
          },
          {
            "title": "2nd Support",
            "value": 0
          },
          {
            "title": "Intermediate Support",
            "value": 0
          },
          {
            "title": "Downside Confirmation",
            "value": 0
          },
          {
            "title": "Upside Confirmation",
            "value": 0
          },
          {
            "title": "Intermediate Resistance",
            "value": 0
          },
          {
            "title": "2nd Resistance",
            "value": 0
          },
          {
            "title": "1st Resistance",
            "value": 1.12
          },
          {
            "title": "Pivot",
            "value": 1.1
          }
        ],
        "timeframe": "H1",
        "analysisType": {
          "expiry": 1,
          "title": "EURUSD bounce holds above support",
          "type": "bullish_bounce"
        },
        "status": "Pending",
        "intermediate": false
      },
      "roles": [],
      "private": false,
      "timeRange": {
        "x": [1790035200000, 1790956800000],
        "y": [1.078, 1.122]
      },
      "snapshot": null,
      "translationSource": "en",
      "translations": {},
      "createdAt": "2026-09-27T08:05:12.431Z",
      "updatedAt": "2026-09-27T09:10:00.000Z",
      "__v": 0
    }
  }
}
```

## Errors

These errors have no machine-readable code; branch on the HTTP status and read `message`. A `sender` who isn't the creator gets `500`, not `403`. The `401` for a missing or unknown key has `detail` and a `meta` object; the `401` for a missing permission has only `message`.

### Fix the request

| Status | Code | What to do |
| --- | --- | --- |
| 400 | - | Nothing was changed. Read `message`: - `Update Analysis Request Validation validation error.`: a field breaks its rule, such as an unknown `timeframe` or levels out of order. `detail` names the field. - `Invalid analysis ID format`: send the 24-character analysis ID. - `Sender field is required`: send `sender`. - `The Analsys is not possible to update as status changed!` (spelled this way): the analysis is no longer `Pending` and can't be changed. - `When updating direction or entryPlacement, levels must be provided`: the card type changes, so send the new `levels` too. - `Pivot value can not be changed`: `levels` was sent without a change of card type. Leave `levels` out, or change `direction` or `entryPlacement` as well. - `Text links reference non-existent objectIds`: a link names an ID that isn't on the card; `invalidObjectIds` lists them. |
| 401 | - | The key is missing (`Token is require.`) or unknown (`Token is wrong.`), it's a personal key, or it lacks `sendMessage` (`Your API key does not have permission to access this action`). Send `Authorization: Bearer <API_KEY>` with a Community API key, and add Send Messages in Settings > Integration > API Keys. |
| 500 | - | Read the body: - `The analysis was not created by the user`: `sender` isn't the member who created the analysis. Send the creator's username or email; nothing was changed. - An HTML error page: the body has none of `metadata`, `levels` or `drawings`, or it sends `levels` or `drawings` together with a text link to an ID that isn't in them. Fix the body; nothing was changed. - `Failed to update analysis`: the update failed, and `detail` has the reason. Read the analysis back with Get analysis before you retry. |

### Fix the data

| Status | Code | What to do |
| --- | --- | --- |
| 404 | - | `Analysis not found`: no analysis has this ID, or it was deleted. `User not found`: no member of your community has the `sender` username or email. Check the value and send again. |

**Retries:** This endpoint doesn't accept an `Idempotency-Key`. Sending the same change twice saves the same values, but your webhooks fire again, a resent scenario adds another edit message, and replaced drawings get new IDs. After a timeout or a `500 Failed to update analysis`, read the analysis with Get analysis and send the update again only if the change isn't there.

## Next step

- [Check the result with Get analysis](https://docs.returning.ai/api-reference/chart-analysis/get-analysis.md): `GET /v1/analysis/{analysisId}`. Read the analysis back to confirm the change, or after a timeout before you retry.
- [Append drawings](https://docs.returning.ai/api-reference/chart-analysis/append-drawings.md): `POST /v1/analysis/{analysisId}/drawings`. Add drawings on their own, without replacing the existing ones.
