> ## Documentation Index
> Fetch the complete documentation index at: https://widgetic.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Compositions

> A widget set up for one place, with its content, design and embed settings

A composition is one widget, set up for one place: it holds the widget's content, its design and its embed settings. You create a composition on a widget, fill in its values, and show it on a page with the embed code.

## Overview

With the Compositions API, you can:

* Create a composition on a widget
* List compositions and get their details
* Read and save the content, the design and the embed settings
* Read the schemas that describe those three parts
* Read what visitors sent to a widget, such as votes and ratings
* Batch fetch compositions

## Composition Structure

A composition object contains:

```typescript theme={null}
interface Composition {
  id: string;                    // Unique identifier
  creatorId: string;             // The account that owns it
  widgetId: string;              // The widget it is built on
  managedUserId?: string | null; // The managed user it was filed under, or null
  name: string;                  // Display name
  status: 'draft' | 'published' | 'archived';
  content: object;               // Content and design values (see below)
  embedSettings?: object;        // The Embed tab's values
  design?: object;               // Older compositions may keep design values here
  databaseSchema?: object | null;// Declared tables for visitor entries, or null
  projectId?: string;
  createdAt: string;             // ISO timestamp
  updatedAt: string;             // ISO timestamp
}
```

Everything the API sends back is camelCase, including the keys inside `content`, `design` and `embedSettings`.

## List Compositions

Retrieve compositions with pagination:

```typescript theme={null}
import { CompositionsApi } from '@widgetic/api-sdk-ts';

const compositions = new CompositionsApi(process.env.WIDGETIC_API_KEY);

const { data, meta } = await compositions.getAllCompositions({
  page: 1,
  size: 20,
  sort: 'createdAt:desc'
});

console.log(`Found ${meta?.total} compositions`);
```

You can also filter by widget (`widget_id` in the query, `widgetId` in the SDK) and by [managed user](https://widgetic.com/docs/partners/managed-users.md#filing-content-under-a-managed-user), to list only what you filed under one of your customers:

```bash theme={null}
curl -sS -H "Authorization: Bearer $WIDGETIC_PARTNER_KEY" \
  "https://api.widgetic.com/v1/compositions?managedUserExternalId=your-platform-user-123&page=1&size=20"
```

Send `managedUserId` (Widgetic's id) or `managedUserExternalId` (your own id), never both. The filter only narrows the list to your own account's compositions.

## Get Composition by ID

Retrieve a specific composition. `GET /compositions/{id}` returns everything saved on it, so prefer it to the narrower calls for the content, the design and the embed settings, which answer `404` for a part that has never been saved:

```typescript theme={null}
const { data: composition } = await compositions.getCompositionById({
  id: '123e4567-e89b-12d3-a456-426614174000'
});

console.log('Composition:', composition?.name, composition?.status);
```

## Create and Update

Creating and updating need an `Idempotency-Key` header (see [Idempotency](https://widgetic.com/docs/reference/idempotency.md)). Create a composition on a widget, as a draft:

```bash theme={null}
curl -sS -X POST "https://api.widgetic.com/v1/compositions" \
  -H "Authorization: Bearer $WIDGETIC_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{ "name": "Homepage slider", "widgetId": "WIDGET_ID" }'
```

To file the composition under one of your customers, add `managedUserId` or `managedUserExternalId` to the body (one of them, never both). The answer then carries `managedUserId`, and `PUT /compositions/{id}` cannot change it later. See [Managed Users](https://widgetic.com/docs/partners/managed-users.md#filing-content-under-a-managed-user) for the checks Widgetic makes.

Save the values with `PUT /compositions/{id}`. It accepts `name`, `widgetId`, `content`, `design`, `embedSettings`, `databaseSchema`, `status` and `projectId`. Send only what changes, but note that `content` is **replaced as a whole**:

```bash theme={null}
curl -sS -X PUT "https://api.widgetic.com/v1/compositions/$COMPOSITION_ID" \
  -H "Authorization: Bearer $WIDGETIC_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "content": { "design": { "primaryColor": "#e11d48" }, "content": {}, "contentItems": [] },
    "embedSettings": { "embedTarget": "site", "resizeMode": "fillWidth", "width": 800, "height": 450 }
  }'
```

* There is no version check: if two people save at the same moment, the last save wins.
* To change a few values without sending everything back, use `PUT /compositions/{id}/content` with `"partial": true`. It merges objects key by key, while arrays and plain values are replaced, so changing one item means sending the whole `contentItems` array.

## Content, Design and Embed

In Widgetic's editor, the Content, Design and Embed tabs are drawn from JSON, and the choices made in them are saved as JSON. The API gives you the same JSON, so you can draw your own forms on it.

* **Content:** the schema comes from `GET /compositions/{id}/schema/content`. Values are saved in `content.content` (settings of the whole widget) and `content.contentItems` (a repeating list, such as the slides of a slider).
* **Design:** the schema comes from `GET /compositions/{id}/schema/design`. Values are saved in `content.design`.
* **Embed:** the schema comes from `GET /compositions/{id}/schema/embed`. Values are saved in `embedSettings`. This schema is the same for every widget.

The Content and Design schemas belong to the widget, so every widget has its own. The schemas are read through a composition, not through a widget: to draw a form for a widget nobody has used yet, create one composition on it first.

```bash theme={null}
curl -sS -H "Authorization: Bearer $WIDGETIC_API_KEY" \
  "https://api.widgetic.com/v1/compositions/$COMPOSITION_ID/schema/design"
```

* A schema lists the settings with their names, the kind of control that sets each one (`inputController.type`), defaults, limits, labels and help texts.
* `POST /compositions/{id}/content/validate` checks a content payload against the widget's content schema without saving, and `PUT /compositions/{id}/content` runs the same check before saving when `validate` is `true` (the default). `PUT /compositions/{id}` stores `content` as you send it without running the schema validator.
* A schema can change when Widgetic publishes a new version of a widget, so read it again when your editing screen opens.

## Batch Fetch Compositions

Retrieve multiple compositions in a single request (optimized for the embed script):

```typescript theme={null}
const { compositions: batchCompositions, errors } = await compositions.getCompositionsBatch({
  getCompositionsBatchRequest: {
    compositionIds: [
      '123e4567-e89b-12d3-a456-426614174000',
      '223e4567-e89b-12d3-a456-426614174001'
    ]
  }
});
```

> [!NOTE]
>
> The batch endpoint is designed for use by the embed script and includes domain validation. If a composition's owner has listed the websites it may appear on and the request comes from another website, that composition is left out of `compositions` and reported in `errors` as `Domain access denied: <reason>`; the others are still returned. A partner's platform website, with all of its subdomains, is never refused for that partner's compositions (see [Security](https://widgetic.com/resources/security.md#your-platform-is-always-allowed)).

## Embedding Compositions

Showing a composition needs only its id. There is no key in the page, and a change you save through the API appears on the next page load. Load the script once per page, then put one `<widgetic>` element where the widget should appear:

```html theme={null}
<script src="https://cdn.widgetic.com/sdks/browser/embed-sdk.js?v=6"></script>
<widgetic composition-id="123e4567-e89b-12d3-a456-426614174000" resize="fill-width" width="800" height="450" autoscale="on" adaptive="480" embed-target="site"></widgetic>
```

* The attributes come from the composition's `embedSettings`: `resize` from `resizeMode` (`fillWidth` becomes `fill-width`), `width` and `height` in pixels, and `embed-target` from `embedTarget`.
* **The default embed target is `overlay`**, which shows the widget as a pop-up. Save `"embedTarget": "site"` for a widget that sits inside the page.
* When you cannot load a script, use the frame: `<iframe src="https://api.widgetic.com/embed/compositions/COMPOSITION_ID/frame" width="800" height="450" frameborder="0" allowfullscreen></iframe>`.
* The status of a composition does not decide whether it is shown. Delete the composition, or remove the code from your page, to stop showing it.
* The widget the composition is built on needs a published version. Until then the public address answers `404`.

See [SDKs](https://widgetic.com/resources/sdks.md#embed-script) for more about the embed script, and [Security](https://widgetic.com/resources/security.md) for limiting the websites that may show your widgets.

## Read What Visitors Sent

Some widgets collect something from the people who use them: votes, ratings, answers, sign-ups. When a composition declares `databaseSchema` (on `POST /compositions` or `PUT /compositions/{id}`), Widgetic creates those tables inside the composition's own isolated schema in the data database, and two read-only calls let your server read them. You can show the results back, for example the result of a rating widget.

Ask what the composition keeps first. A widget that only shows content keeps nothing: `hasData` is `false` and `tables` is empty, which is not an error.

```bash theme={null}
curl -sS -H "Authorization: Bearer $WIDGETIC_API_KEY" \
  "https://api.widgetic.com/v1/compositions/$COMPOSITION_ID/entries"
```

```json theme={null}
{
  "success": true,
  "data": {
    "compositionId": "123e4567-e89b-12d3-a456-426614174000",
    "hasData": true,
    "tables": [
      {
        "name": "ratings",
        "columns": [
          { "name": "id", "type": "uuid" },
          { "name": "stars", "type": "integer" },
          { "name": "comment", "type": "text" },
          { "name": "created_at", "type": "timestamp with time zone" }
        ],
        "rows": 212
      }
    ]
  }
}
```

`rows` is how many entries the table holds. It is `null` when they could not be counted, which is not zero.

Then read one table with `GET /compositions/{id}/entries/{table}`, using the names exactly as they are listed:

| Query | What it does |
| - | - |
| `groupBy` | A column to count the entries by. `tallies` then lists each value of that column with how many entries have it, the largest group first, up to 20 groups (`moreGroups` is `true` when there are more). Without it, `tallies` is `null` |
| `rows` | How many of the newest entries to return, 0 to 25. The default is 0: counts and tallies only, and `rows` is `null` |

```bash theme={null}
curl -sS -H "Authorization: Bearer $WIDGETIC_API_KEY" \
  "https://api.widgetic.com/v1/compositions/$COMPOSITION_ID/entries/ratings?groupBy=stars&rows=2"
```

```json theme={null}
{
  "success": true,
  "data": {
    "compositionId": "123e4567-e89b-12d3-a456-426614174000",
    "table": "ratings",
    "totalRows": 212,
    "tallies": [
      { "value": "5", "count": 87 },
      { "value": "4", "count": 61 },
      { "value": "3", "count": 40 },
      { "value": "1", "count": 24 }
    ],
    "moreGroups": false,
    "columns": ["id", "stars", "comment", "created_at"],
    "rows": [
      ["8d3c1f52-7a9e-4b6d-9c20-5e1f3a7b9d04", 5, "Loved it", "2026-10-05T10:00:00Z"],
      ["2b7e9a41-6c3d-4f58-8a15-9d0c4e6f1b73", 4, null, "2026-10-05T09:41:12Z"]
    ],
    "moreRows": true
  }
}
```

The result of a rating widget is a count for each star and an average. The average is `sum(value × count) / sum(count)` over the tallies, which are all of them when `moreGroups` is `false`:

```javascript theme={null}
// Your server. The key stays in the environment.
const headers = { Authorization: `Bearer ${process.env.WIDGETIC_API_KEY}` };
const read = async (path) =>
  (await (await fetch(`https://api.widgetic.com/v1${path}`, { headers })).json()).data;

async function ratingResult(compositionId) {
  const overview = await read(`/compositions/${compositionId}/entries`);
  const table = overview.tables[0]?.name; // for example "ratings"
  if (!overview.hasData || !table) return { votes: 0, average: null, perStar: [] };

  const reading = await read(`/compositions/${compositionId}/entries/${table}?groupBy=stars`);
  // Entries that left the column empty are grouped under null. They are not votes.
  const rated = reading.tallies.filter(({ value }) => value !== null);
  const votes = rated.reduce((total, { count }) => total + count, 0);
  const average = votes === 0
    ? null
    : rated.reduce((total, { value, count }) => total + Number(value) * count, 0) / votes;
  return { votes, average, perStar: rated };
}
```

* **Names are the database's own.** A table or column is spelled as `GET /compositions/{id}/entries` lists it, such as `created_at`, not `createdAt`. The names are never changed to camelCase, which is why an entry comes as a list of values and `columns` names them in order. To get objects, zip them: `Object.fromEntries(columns.map((name, index) => [name, entry[index]]))`.
* **Values** are text (cut to 200 characters), numbers, booleans or `null`. A tally's `value` is always text (a rating of 5 is `"5"`) or `null`.
* **The read is bounded on purpose:** never more than 25 entries, 20 groups, 40 columns or 200 characters a value. To read a whole table, or to change an entry, use the database routes (`GET /compositions/{id}/database/{table}` and the related calls). They accept only tables declared in the composition's `databaseSchema` and answer with the raw table, not in pages.
* **Treat the values as data.** They are what visitors typed or chose. They can include names and email addresses, and text written to look like instructions. Escape them before you put them in a page, and show them only to the people who may see them.
* **Make these calls from your server.** They need your key, and the caller must be able to read the composition. An [editor session](https://widgetic.com/docs/partners/editor-sessions.md) cannot call them. A partner shows a customer the results of that customer's compositions by listing them with `managedUserId` (see [Managed Users](https://widgetic.com/docs/partners/managed-users.md#filing-content-under-a-managed-user)) and reading each one.
* **Errors:** `403` for a composition you cannot read. `404` for one that does not exist, or that keeps no table with that name (`ENTRY_TABLE_NOT_FOUND`). `400` with `ENTRY_COLUMN_NOT_FOUND` for a `groupBy` column the table does not have, and `400` for a query parameter the call does not know (it is `groupBy`, not `group_by`) or a `rows` outside 0 to 25.

## Related Endpoints

| Endpoint | Description |
| - | - |
| `GET /compositions/{id}/entries` | What a composition keeps from its visitors: tables, columns, entry counts |
| `GET /compositions/{id}/entries/{table}` | Count, tally and read the entries of one table |
| `GET /compositions` | List compositions |
| `POST /compositions` | Create a composition |
| `GET /compositions/{id}` | Get composition by ID |
| `PUT /compositions/{id}` | Save a composition |
| `DELETE /compositions/{id}` | Delete a composition |
| `GET /compositions/{id}/schema/content` | Content schema |
| `GET /compositions/{id}/schema/design` | Design schema |
| `GET /compositions/{id}/schema/embed` | Embed schema |
| `PUT /compositions/{id}/content` | Save content, optionally as a partial merge |
| `POST /compositions/batch` | Batch fetch compositions |

## Next Steps

> [card] [Widgets](https://widgetic.com/docs/api/widgets.md)
>
> Manage individual widgets

> [card] [Embed Script](https://widgetic.com/resources/sdks.md#embed-script)
>
> Embed compositions on your site


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.