Skip to main content

REST chart storage adapter

createRestChartStorageAdapter maps the public storage contract to a fixed HTTP convention. Use it when your backend can expose these routes. Use a custom chart storage adapter for different routes, GraphQL, RPC, or pushed drawing updates.

This adapter persists chart workspaces and related chart-owned state. It does not persist the outer widget panel arrangement used by WidgetLayoutContainer or Trading Terminal. Use the separate Widget layout storage guide for that WidgetLayoutState integration.

Configure the client

storage.ts
import { createRestChartStorageAdapter } from '@tradescript/pro/sdk';

declare function getAccessToken(): Promise<string>;

export const storage = createRestChartStorageAdapter({
baseUrl: 'https://api.example.com/chart-storage',
apiVersion: 'v1',
clientId: 'pro-terminal',
userId: 'user-123',
workspaceId: 'primary',
headers: async () => ({
Authorization: `Bearer ${await getAccessToken()}`,
}),
});

The factory makes no request during construction. It resolves headers() for every operation, so short-lived credentials can be refreshed without replacing the adapter.

Configured identity becomes query context:

client=pro-terminal&user=user-123&workspace=primary

Per-request userId and workspaceId override configured defaults. The adapter may also send chartId, symbol, and interval. Treat all browser query values as routing context; derive authorization from the credential on the server.

Response rules

Successful JSON may be returned directly or inside this envelope:

{ "status": "ok", "data": {} }

The adapter also accepts an empty successful response, including 204, for writes and deletes. A chart workspace layout create is the exception: it must return a server-assigned id.

Load routes map 404 to null. Other non-success responses reject. Drawing routes map 409 to DrawingRevisionConflictError and 401 or 403 to DrawingPermissionError; non-drawing HTTP failures are generic storage errors.

The adapter runtime-parses JSON syntax but does not replace backend schema validation. Validate incoming payloads and preserve unknown documented metadata on the server.

Chart layout routes

OperationRequest
ListGET /v1/charts
CreatePOST /v1/charts without chart
UpdatePOST /v1/charts?chart={storageId}
LoadGET /v1/charts?chart={storageId}
DeleteDELETE /v1/charts?chart={storageId}

Create and update use the same body:

{
"name": "Opening drive",
"symbol": "AAPL",
"resolution": "1D",
"content": {
"id": "opening-drive",
"version": 1,
"name": "Opening drive",
"activeChartId": "chart-1",
"charts": [
{
"id": "chart-1",
"symbol": { "ticker": "AAPL" },
"interval": "1D"
}
]
}
}

Create must return an object with id, either directly or inside the standard response envelope:

{ "status": "ok", "data": { "id": "layout-record-481" } }

The SDK retains opening-drive → layout-record-481 and sends the backend id as chart on update, load, and delete. The widget-level chart layout controller keeps this mapping in memory; hosts that drive it directly must persist getStorageIds() and restore it with restoreStorageIds() across sessions. Chart layout tabs persist the mapping through user settings. Do not treat the name as identity.

List returns summary records whose id is the backend storage id. Use Unix milliseconds for timestamp:

{
"status": "ok",
"data": [
{
"id": "layout-record-481",
"name": "Opening drive",
"symbol": "AAPL",
"resolution": "1D",
"timestamp": 1720000000123
}
]
}

Chart workspace layout load may return the ChartLayout directly or as content or layout. content may be an object or a JSON string.

Separate drawing routes

OperationRequest
SavePOST /v1/drawings plus drawing bucket query
LoadGET /v1/drawings plus drawing bucket query
PermissionsGET /v1/drawings/permissions

Drawing calls carry the applicable general context and these wire keys:

chart={chartId}&layout={layoutId}&sharing={sharingMode}

The general query may also include chartId, symbol, and interval. Loads for indicator-owned drawings add requestType, ownerSource, and seriesSourceId when supplied.

Save body:

{
"state": {
"chartId": "chart-1",
"layoutId": "opening-drive",
"sharingMode": "not-shared",
"drawings": [],
"removed": [],
"removedGroups": []
},
"baseRevision": "revision-8",
"force": false
}

A production save returns the new revision and millisecond timestamp:

{ "revision": "revision-9", "savedAt": 1720000000123 }

On stale baseRevision, return 409 with the current server state:

{
"status": "error",
"error": {
"remoteRevision": "revision-9",
"remoteState": {
"sharingMode": "not-shared",
"drawings": [],
"revision": "revision-9"
}
}
}

Load returns DrawingState directly or under state; 404 means no saved bucket. Permissions return DrawingSharingPermissions directly or under permissions.

The REST adapter does not implement subscribeDrawings. Use a custom adapter when remote drawing changes must be pushed to a mounted chart.

Template routes

KindRouteIdentity query
Indicator/v1/indicator_templatestemplate={templateId}
Chart/v1/chart_templatestemplate={templateId}
Drawing/v1/drawing_templatesname={templateId}&template={templateId}&tool={toolId}

indicator_templates is the wire route even though the public SDK calls the kind indicator. List omits the template id; drawing-template list retains tool. Save uses POST, load and list use GET, and delete uses DELETE.

Template save body:

{
"name": "Momentum stack",
"content": {
"id": "momentum-stack",
"kind": "indicator",
"version": 1,
"name": "Momentum stack"
}
}

Load returns a ChartTemplate directly or as content or template; 404 maps to null. Template writes are last-write-wins in this adapter.

User-setting routes

OperationRequest and body
LoadGET /v1/settings
SavePOST /v1/settings with { "settings": UserSettingsState }
Delete keys or allDELETE /v1/settings with { "keys": string[] } or no keys

Load returns UserSettingsState directly or under settings; 404 maps to null. Serialize overlapping writes on the backend if request order matters. The adapter has no user-setting revision contract.

Replay-state routes

OperationRequest and body
SavePOST /v1/replay?layout={layoutId} with { "state": ReplayState }
LoadGET /v1/replay?layout={layoutId}

The layout query is omitted when the request has no layoutId. Replay-state load returns ReplayState directly or under state; 404 maps to null. There is no replay-state delete method in ChartStorageAdapter.

What the REST adapter does not cover

  • widget panel arrangements (WidgetLayoutState);
  • pushed drawing subscriptions;
  • retries, timeouts, cancellation, or offline queues;
  • image file upload;
  • chart workspace layout, template, user-setting, or replay-state compare-and-swap revisions.

Use a custom adapter or wrapped fetch where those requirements apply. Pass a separate image storage adapter for inline image drawings.

Production requirements

  • Configure CORS for the application origin, methods, and authorization headers.
  • Authenticate and authorize every operation; never authorize from user or workspace query values alone.
  • Keep tenant keys for chart workspace layouts, drawing buckets, template kinds, settings, and replay state explicit in the database.
  • Return a stable backend id from chart workspace layout create and preserve it on update.
  • Version the HTTP path independently from ChartLayout.version and ChartTemplate.version. Drawing, settings, and replay records have no built-in schema-version field.
  • Treat uncertain writes as unknown until reconciled; the adapter does not automatically retry them.
  • Redact credentials and stored payloads from logs.

Next steps