The Components API lets you create, update, list, and delete game pieces. Components are the building blocks of your game - tiles, stacks (decks), tokens, boards, and more.
All component creation uses upsert semantics: if a component with the same kind and externalKey already exists, it is updated in place. This means re-exporting is always safe -- you never create duplicates, and existing components retain their identity across updates so they keep their positions on tables.
When Your Changes Are Live
Every write and delete is live as soon as it returns: a component you push is on the game's Import Pieces page and can be added to tables at once, and one you delete is gone from them. There is no draft and nothing to publish. A table set up from the game picks up your changes on its own.
Two answers ask you to wait and send the same request again. Both are 503 with the code RETRY_LATER and a Retry-After header, and sending the same request again is always safe. A request that writes or deletes one component applies nothing when it is refused. A batch is written entry by entry, so a batch refused part way keeps the entries written before the refusal; sending the whole batch again writes those again in place (every entry is an upsert) and finishes the rest. A batch delete refused part way keeps what it deleted, and sending it again deletes the rest:
- "Imports are being upgraded." while Playtest Parlor upgrades how imported pieces are stored.
Retry-Afteris 120 seconds. Lists never ask you to wait. - "Your app's pieces in this game are being updated by an import." while an import or a copy is being applied to your app's pieces, which takes about a minute for the largest game.
Retry-Afteris 30 seconds.
A client that already retries RETRY_LATER needs no change; see Error Handling.
Limits
- A deck or bag holds at most 8,192 different components. Pushing one more answers
400VALIDATION_ERROR: "A deck can hold at most 8,192 different cards. "{deck}" already has 8,192. Split it into two decks." - Your app's pieces in one game number at most 8,192 (deleted ones not counted). Deleting a deck or bag frees a place for it and for everything it holds, all the way down; pushing it again takes those places back. Pushing one more answers
400VALIDATION_ERROR: "This game's {app} pieces have reached the limit of 8,192. Remove some before adding more." - A card, tile or die face names its deck, bag or die with
parentExternalKey, which must be pushed first. In a batch, order does not matter: entries that name no parent are written first. A card whose deck was never pushed answers400VALIDATION_ERRORnaming the key. - Decks, bags, stacks, dice and containers may not share a key with one another, because their cards name them by key alone.
- Two pieces in one deck or bag may not share a key, even of different kinds (a card and a tile, say), because a deck lists its pieces by key. Pushing one, or moving one into a deck that already holds that key, answers
400VALIDATION_ERROR: "Two pieces in "{deck}" share the key "{key}" (card and tile). A deck or bag lists its pieces by key, so each needs a key of its own. Use another key for one of them." The same key in two different decks is fine. - Images go in a component's
assets, never inpayload(payload.imageFacesanswers400), and a book's pages inpages.
Component Kinds
Playtest Parlor supports these component kinds:
| Kind | Description |
|---|---|
tile | A single game piece (card, token, etc.) |
deck (alias: stack) | A collection of tiles, typically for drawing |
board | A play surface with positions and zones |
tokenSprite | A visual representation for player pieces |
tokenModel | A 3D or complex token definition |
die | A die with numbered faces |
dieFace | A single face of a die |
container | A holding area for game pieces |
counter | A numerical tracker |
bundle | A group of related components |
book | A readable book with ordered pages and an optional cover |
Books use book-specific request fields (pages and cover, as R2 keys) instead of assets; see the Books API for the full contract. All the other kinds follow the asset flow documented on this page.
Parent-Child Relationships
Components can be nested using parentExternalKey. Only certain kinds are allowed as children:
| Parent Kind | Valid Child Kinds | Notes |
|---|---|---|
deck | card, tile | Both are treated as deck cards. tile is accepted as a synonym for card when nested under a deck. |
bundle | tile | Tiles grouped into a two-sided set. |
die | dieFace | Individual faces of a die. |
Providing parentExternalKey on any other kind (e.g., deck, board, tokenSprite) will return a validation error.
Create or Update Single Component
Create or update one component at a time. If a component with the same kind and externalKey already exists, it is updated in place. Assets are upserted by sideKey -- uploading a new image for the same side replaces the previous one. Pushing a component you deleted brings it back with the same id, and pushing a card with another parentExternalKey moves it to that deck. Live as soon as it returns; see When Your Changes Are Live.
Request:
POST /api/v1/games/:gameId/components
Authorization: Bearer pp_your_token
Content-Type: application/json
{
"kind": "tile",
"name": "Fighter Card",
"externalKey": "fighter-card",
"count": 12,
"widthMm": 88.9,
"heightMm": 127,
"shape": "box",
"assets": [
{
"sideKey": "front",
"key": "uploads/fighter-front.png",
"sourceUrl": "https://assets.playtestparlor.com/uploads/fighter-front.png",
"contentType": "image/png",
"widthPx": 750,
"heightPx": 1050,
"sourcePixelsPerInch": 300
},
{
"sideKey": "back",
"key": "uploads/fighter-back.png",
"sourceUrl": "https://assets.playtestparlor.com/uploads/fighter-back.png",
"contentType": "image/png",
"widthPx": 750,
"heightPx": 1050,
"sourcePixelsPerInch": 300
}
]
}
Request Body:
| Field | Type | Required | Description |
|---|---|---|---|
kind | string | yes | Component type (see table above) |
name | string | yes | Display name (max 256 characters) |
externalKey | string | yes | Unique identifier for this component within the game (used for deduplication and parent references) |
count | number | no | Number of instances (min 1) |
widthMm | number | no | Width in millimeters |
heightMm | number | no | Height in millimeters |
shape | string | no | Tile/card outline: box (rectangle, the default), hex, or orb (circle). Applies to tile/card components and is inherited by the parent deck/stack/bundle. |
sides | number | no | Number of die faces (2, 4, 6, 8, 10, 12, or 20). Only applies to die components. |
parentExternalKey | string | no | External key of the parent component (e.g., for cards belonging to a deck) |
assets | array | no | Asset objects to attach to this component (see Asset Object below) |
payload | object | no | Extra component metadata. Attach images with assets, not here: if the payload includes imageFaces or bookPageFaces, every assetId and derivedAssetId in them must be an image this game already has from the API. A face naming any other image is rejected with a VALIDATION_ERROR that names the field. |
If shape is omitted, Playtest Parlor infers the outline from the component's payload and otherwise defaults to box (rectangle). Set shape explicitly to import hex or circular tiles reliably.
Asset Object
Each entry in the assets array maps an uploaded image to a specific face of the component using the sideKey field.
| Field | Type | Required | Description |
|---|---|---|---|
sideKey | string | yes | Which face this image belongs to (see sideKey values below) |
key | string | yes | The key from the presign response (also accepts r2ObjectKey) |
sourceUrl | string | yes | The publicUrl from the presign response (also accepts publicUrl) |
contentType | string | yes | MIME type of an image (e.g., image/png, image/jpeg) |
widthPx | number | yes | Image width in pixels (a positive number) |
heightPx | number | yes | Image height in pixels (a positive number) |
sourcePixelsPerInch | number | no | DPI of the source image (default: 300) |
bleedMode | string | no | "bleed" (default) or "noBleed" |
sideKey Values by Component Kind
The sideKey identifies which face of the component an asset belongs to. The valid values depend on the component kind:
| Component Kind | Valid sideKey Values | Example |
|---|---|---|
tile, card, board | "front", "back" | "front" for the card face, "back" for the card back |
dieFace | "face-1", "face-2", ... "face-N" | "face-3" for the third face of a die |
Using the wrong sideKey pattern (e.g., "front" on a dieFace) will cause the asset to not render on the component.
A tile or card must have a "front" image. These kinds render their front face with no color fallback, so one created without a "front" asset would show up blank on the table. The API rejects such a push with a VALIDATION_ERROR that names the offending components. The "front" can come from the current request, or already exist on the component from a previous push (so metadata-only re-exports that omit assets still succeed). Decks, stacks, and bundles are exempt: their front art lives on their child tiles/cards, and they may carry only a shared "back".
Image derivation is handled automatically: Playtest Parlor makes each image's display version from its widthPx and heightPx. There is no separate derivation step. An asset without a positive widthPx and heightPx, or whose contentType is not an image type, is refused with VALIDATION_ERROR before anything is written, and the message names the fields.
Response:
{
"id": "j57a..."
}
Errors:
VALIDATION_ERROR: Invalid component dataGAME_NOT_FOUND: The game does not existPERMISSION_DENIED: You do not own this gameRETRY_LATER(503): Imports are being upgraded, or an import is being applied to your app's pieces. Retry after theRetry-Afterseconds; see When Your Changes Are Live
Batch Create or Update Components
Create or update up to 200 components in a single request. Same upsert semantics as the single endpoint. Every entry is checked before any is written; entries that name no parent are written first, so a deck may come after its cards, and the response lists the components in the order you sent them. Each is live as soon as it is written: a request refused part way (a wait, or a deck that fills) keeps the entries written before it, and sending it again finishes it.
Request:
POST /api/v1/games/:gameId/components/batch
Authorization: Bearer pp_your_token
Content-Type: application/json
{
"components": [
{
"kind": "deck",
"name": "Main Deck",
"externalKey": "main-deck",
"count": 1,
"widthMm": 88.9,
"heightMm": 127
},
{
"kind": "tile",
"name": "Basic Card",
"externalKey": "basic-card",
"count": 50,
"widthMm": 88.9,
"heightMm": 127,
"parentExternalKey": "main-deck",
"assets": [
{
"sideKey": "front",
"key": "uploads/basic-front.png",
"sourceUrl": "https://assets.playtestparlor.com/uploads/basic-front.png",
"contentType": "image/png"
}
]
},
{
"kind": "tile",
"name": "Special Card",
"externalKey": "special-card",
"count": 10,
"widthMm": 88.9,
"heightMm": 127,
"parentExternalKey": "main-deck",
"assets": [
{
"sideKey": "front",
"key": "uploads/special-front.png",
"sourceUrl": "https://assets.playtestparlor.com/uploads/special-front.png",
"contentType": "image/png"
},
{
"sideKey": "back",
"key": "uploads/special-back.png",
"sourceUrl": "https://assets.playtestparlor.com/uploads/special-back.png",
"contentType": "image/png"
}
]
}
]
}
Request Body:
| Field | Type | Required | Description |
|---|---|---|---|
components | array | yes | Array of component objects (max 200) |
Each component object has the same fields as the single create endpoint.
Response:
{
"components": [
{ "id": "j57a..." },
{ "id": "k82b..." },
{ "id": "m93c..." }
]
}
Errors:
VALIDATION_ERROR: Invalid component data or too many components (max 200)GAME_NOT_FOUND: The game does not existPERMISSION_DENIED: You do not own this gameRETRY_LATER(503): Imports are being upgraded, or an import is being applied to your app's pieces. Retry after theRetry-Afterseconds; see When Your Changes Are Live
List Components
Retrieve the components your app has created for a game and not deleted, a page at a time. Only returns components from your app's import source. A card of a deck you deleted is still listed: it is off the table with its deck, and comes back if you push the deck again; delete it too if you want it gone.
Request:
GET /api/v1/games/:gameId/components?limit=100
Authorization: Bearer pp_your_token
Query Parameters:
| Parameter | Description |
|---|---|
limit | How many components to return, 1 to 200. Default 100. A page can hold fewer when its components are large, so keep going until nextCursor is null. |
cursor | Where to continue. Pass the previous response's nextCursor unchanged to get the next page. A component written or deleted while you page is found or left out where the listing has not reached yet; none is listed twice. |
Response:
{
"components": [
{
"id": "j57a...",
"kind": "deck",
"name": "Main Deck",
"externalKey": "main-deck",
"count": 1,
"widthMm": 88.9,
"heightMm": 127,
"parentExternalKey": null,
"payload": {}
},
{
"id": "k82b...",
"kind": "tile",
"name": "Basic Card",
"externalKey": "basic-card",
"count": 50,
"widthMm": 88.9,
"heightMm": 127,
"shape": "hex",
"parentExternalKey": "main-deck",
"payload": {
"imageFaces": [
{ "sideKey": "front", "assetId": "a1b2...", "derivedAssetId": "c3d4..." }
]
}
}
],
"nextCursor": "eyJyIjoi..."
}
nextCursor is null on the last page.
Response Fields:
| Field | Type | Description |
|---|---|---|
id | string | Unique component identifier |
kind | string | Component type |
name | string | Display name |
externalKey | string | Your external identifier for this component |
count | number | Number of instances |
widthMm | number | Width in millimeters |
heightMm | number | Height in millimeters |
shape | string or undefined | The explicitly set outline (box, hex, or orb). Absent if you never sent shape -- the importer then infers it, defaulting to box. |
parentExternalKey | string or null | Parent component's external key |
payload | object | Component metadata including imageFaces for attached assets |
nextCursor | string or null | Pass as cursor to get the next page; null when there are no more |
Errors:
GAME_NOT_FOUND: The game does not existPERMISSION_DENIED: You do not own this gameVALIDATION_ERROR(400):limitis not a whole number from 1 to 200, orcursoris not anextCursorthis API gave you
Reading every page:
async function listAllComponents(gameId, token) {
const components = [];
let cursor = null;
do {
const url = new URL(`https://playtestparlor.com/api/v1/games/${gameId}/components`);
url.searchParams.set("limit", "200");
if (cursor) url.searchParams.set("cursor", cursor);
const page = await fetch(url, { headers: { "Authorization": `Bearer ${token}` } }).then(r => r.json());
components.push(...page.components);
cursor = page.nextCursor;
} while (cursor);
return components;
}
Delete Component
Delete a single component and its assets, live as soon as it returns. Deleting a deck or bag takes what it holds off the table with it; those stay yours, and come back if you push the deck again. Deleting a component already deleted answers 204 again. Table configurations take its copies off a couple of seconds later, and so does a game in progress while someone who can edit the game is at it; a finished game keeps them. See when the game's imported pieces change.
Request:
DELETE /api/v1/games/:gameId/components/:componentId
Authorization: Bearer pp_your_token
Response: 204 No Content
Errors:
COMPONENT_NOT_FOUND: The component does not exist or belongs to a different appGAME_NOT_FOUND: The game does not existPERMISSION_DENIED: You do not own this gameRETRY_LATER(503): Imports are being upgraded, or an import is being applied to your app's pieces. Retry after theRetry-Afterseconds; see When Your Changes Are Live
Batch Delete Components
Delete all components (and their assets) your app has created for a game, providing a clean slate for re-export. Live as each part returns: the request deletes them 100 at a time, however many there are. If it is answered with a wait part way, what was deleted stays deleted, and sending it again finishes the rest.
Request:
DELETE /api/v1/games/:gameId/components/batch
Authorization: Bearer pp_your_token
Response:
{
"deleted": 15
}
Response Fields:
| Field | Type | Description |
|---|---|---|
deleted | number | Number of components that were deleted |
Errors:
GAME_NOT_FOUND: The game does not existPERMISSION_DENIED: You do not own this gameRETRY_LATER(503): Imports are being upgraded, or an import is being applied to your app's pieces. Retry after theRetry-Afterseconds; see When Your Changes Are Live
Units
All dimensions (widthMm, heightMm) are in millimeters. This is the canonical storage format in Playtest Parlor. When you retrieve components from the app or other APIs, they will always be in millimeters.
Typical Deck Workflow
Here is a typical workflow for creating a deck with multiple card types:
# 1. Upload card images (front and back) via the Assets API
# For each image: presign -> PUT to the signed URL with every header the presign
# returned (412 means the file is already stored; carry on) -> confirm upload
# You'll get key and publicUrl from the presign response; keep each image's
# width and height in pixels for its asset entry below
# 2. Create the deck and its cards in one batch request
# Cards reference the deck via parentExternalKey
# Each asset's sideKey ("front" or "back") maps the image to a tile face
POST /api/v1/games/:gameId/components/batch
{
"components": [
{
"kind": "deck",
"name": "Main Deck",
"externalKey": "main-deck",
"widthMm": 88.9,
"heightMm": 127
},
{
"kind": "tile",
"name": "Basic Card",
"externalKey": "basic-card",
"count": 40,
"widthMm": 88.9,
"heightMm": 127,
"parentExternalKey": "main-deck",
"assets": [
{
"sideKey": "front",
"key": "uploads/basic-front.png",
"sourceUrl": "https://assets.playtestparlor.com/uploads/basic-front.png",
"contentType": "image/png",
"widthPx": 750,
"heightPx": 1050,
"sourcePixelsPerInch": 300
},
{
"sideKey": "back",
"key": "uploads/card-back.png",
"sourceUrl": "https://assets.playtestparlor.com/uploads/card-back.png",
"contentType": "image/png",
"widthPx": 750,
"heightPx": 1050,
"sourcePixelsPerInch": 300
}
]
},
{
"kind": "tile",
"name": "Special Card",
"externalKey": "special-card",
"count": 10,
"widthMm": 88.9,
"heightMm": 127,
"parentExternalKey": "main-deck",
"assets": [
{
"sideKey": "front",
"key": "uploads/special-front.png",
"sourceUrl": "https://assets.playtestparlor.com/uploads/special-front.png",
"contentType": "image/png",
"widthPx": 750,
"heightPx": 1050,
"sourcePixelsPerInch": 300
},
{
"sideKey": "back",
"key": "uploads/card-back.png",
"sourceUrl": "https://assets.playtestparlor.com/uploads/card-back.png",
"contentType": "image/png",
"widthPx": 750,
"heightPx": 1050,
"sourcePixelsPerInch": 300
}
]
}
]
}
Typical Die Workflow
Dice use a parent-child model, just like decks and cards. You must create separate dieFace child components for each face -- you cannot attach assets directly to the die parent.
| Concept | Deck analogy | Die equivalent |
|---|---|---|
| Parent component | deck | die |
| Child component | tile (card) | dieFace |
| Child links to parent via | parentExternalKey | parentExternalKey |
| Asset sideKey on child | "front" / "back" | "face-1", "face-2", ... "face-N" |
The die parent component carries no assets. All face images go on individual dieFace children.
Set sides on the die parent to specify the die type (e.g., "sides": 8 for a d8). You do not need to provide a dieFace child for every face -- faces without images will display default numbered labels. Dice do not require widthMm/heightMm -- dimensions are automatically derived from the sides value (e.g., a d6 defaults to 25mm).
# 1. Upload face images via the Assets API
# For each face image: presign -> PUT to the signed URL with every header the presign
# returned (412 means the file is already stored; carry on) -> confirm upload
# 2. Create the die and its faces in one batch request
POST /api/v1/games/:gameId/components/batch
{
"components": [
{
"kind": "die",
"name": "Attack Die",
"externalKey": "attack-die",
"sides": 6
},
{
"kind": "dieFace",
"name": "Face 1",
"externalKey": "attack-die-face-1",
"parentExternalKey": "attack-die",
"assets": [
{
"sideKey": "face-1",
"key": "uploads/attack-face-1.png",
"sourceUrl": "https://assets.playtestparlor.com/uploads/attack-face-1.png",
"contentType": "image/png",
"widthPx": 256,
"heightPx": 256
}
]
},
{
"kind": "dieFace",
"name": "Face 2",
"externalKey": "attack-die-face-2",
"parentExternalKey": "attack-die",
"assets": [
{
"sideKey": "face-2",
"key": "uploads/attack-face-2.png",
"sourceUrl": "https://assets.playtestparlor.com/uploads/attack-face-2.png",
"contentType": "image/png",
"widthPx": 256,
"heightPx": 256
}
]
},
{
"kind": "dieFace",
"name": "Face 3",
"externalKey": "attack-die-face-3",
"parentExternalKey": "attack-die",
"assets": [
{
"sideKey": "face-3",
"key": "uploads/attack-face-3.png",
"sourceUrl": "https://assets.playtestparlor.com/uploads/attack-face-3.png",
"contentType": "image/png",
"widthPx": 256,
"heightPx": 256
}
]
},
{
"kind": "dieFace",
"name": "Face 4",
"externalKey": "attack-die-face-4",
"parentExternalKey": "attack-die",
"assets": [
{
"sideKey": "face-4",
"key": "uploads/attack-face-4.png",
"sourceUrl": "https://assets.playtestparlor.com/uploads/attack-face-4.png",
"contentType": "image/png",
"widthPx": 256,
"heightPx": 256
}
]
},
{
"kind": "dieFace",
"name": "Face 5",
"externalKey": "attack-die-face-5",
"parentExternalKey": "attack-die",
"assets": [
{
"sideKey": "face-5",
"key": "uploads/attack-face-5.png",
"sourceUrl": "https://assets.playtestparlor.com/uploads/attack-face-5.png",
"contentType": "image/png",
"widthPx": 256,
"heightPx": 256
}
]
},
{
"kind": "dieFace",
"name": "Face 6",
"externalKey": "attack-die-face-6",
"parentExternalKey": "attack-die",
"assets": [
{
"sideKey": "face-6",
"key": "uploads/attack-face-6.png",
"sourceUrl": "https://assets.playtestparlor.com/uploads/attack-face-6.png",
"contentType": "image/png",
"widthPx": 256,
"heightPx": 256
}
]
}
]
}
Set sides on the die parent to control the die type. Supported values: 2 (coin), 4, 6, 8, 10, 12, 20. You only need to create dieFace children for faces that have custom images -- faces without a corresponding dieFace child will display default numbered labels.
Re-export Workflow
Component creation uses upsert semantics, so re-exporting is straightforward. There are two common patterns:
Partial re-export (only changed tiles)
If only some tiles in a deck changed, just upload the new assets and upsert those tiles. Unchanged tiles are left alone.
// 1. Upload new/changed assets via the Assets API
// 2. Upsert only the changed tiles
await fetch(`https://playtestparlor.com/api/v1/games/${gameId}/components/batch`, {
method: "POST",
headers: { "Authorization": `Bearer ${token}`, "Content-Type": "application/json" },
body: JSON.stringify({ components: changedTiles })
});
// Existing tiles with the same externalKey are updated in place.
// Other tiles in the deck are untouched.
Full re-export (complete deck sync)
If tiles may have been added or removed, compare the current state in PP with the full tile list from your design tool and delete any tiles that no longer exist.
// 1. Get what's currently in PP, every page (listAllComponents, under List Components)
const components = await listAllComponents(gameId, token);
// 2. Find tiles that no longer exist in the design
const deckKey = "deck-my-deck-id";
const currentTileKeys = new Set(myDesignTiles.map(t => t.externalKey));
const orphanedTiles = components.filter(c =>
c.parentExternalKey === deckKey && !currentTileKeys.has(c.externalKey)
);
// 3. Delete orphaned tiles
for (const tile of orphanedTiles) {
await fetch(
`https://playtestparlor.com/api/v1/games/${gameId}/components/${tile.id}`,
{ method: "DELETE", headers: { "Authorization": `Bearer ${token}` } }
);
}
// 4. Upload assets and upsert all current tiles
await fetch(`https://playtestparlor.com/api/v1/games/${gameId}/components/batch`, {
method: "POST",
headers: { "Authorization": `Bearer ${token}`, "Content-Type": "application/json" },
body: JSON.stringify({ components: allCurrentTiles })
});
Troubleshooting
Die faces not rendering
If your die component is created successfully but face images do not appear, check:
- Are you creating
dieFacechildren? The most common mistake is attaching assets directly to thedieparent component. Dice require separatedieFacechild components, each with its own asset. The die parent must have"assets": []or omit the field entirely. - Is the sideKey correct? Each dieFace asset must use
"face-N"(e.g.,"face-1","face-2"). Using"front","side1", or any other value will silently fail to bind. - Does each dieFace reference the die via parentExternalKey? Without this link, the face is created as an orphan component and will not attach to the die.
- Are extra fields being sent on the die? Fields like
sides,dieKind, andfaceLabelsat the top level of the component body are ignored. The API only reads:kind,name,externalKey,count,widthMm,heightMm,shape,parentExternalKey,payload, andassets. The die type is determined by counting itsdieFacechildren.
Incorrect example (will not work)
{
"kind": "die",
"name": "D6",
"externalKey": "my-die",
"sides": 6,
"dieKind": "polyhedral",
"assets": [
{ "sideKey": "front", "key": "...", "sourceUrl": "...", "contentType": "image/png" }
]
}
This fails because: assets are on the die parent (should be on dieFace children), sideKey is "front" (should be "face-N"), and sides/dieKind are unrecognized fields.
Correct pattern
{
"components": [
{ "kind": "die", "name": "D6", "externalKey": "my-die", "sides": 6 },
{
"kind": "dieFace", "name": "Face 1", "externalKey": "my-die-f1",
"parentExternalKey": "my-die",
"assets": [{ "sideKey": "face-1", "key": "...", "sourceUrl": "...", "contentType": "image/png", "widthPx": 256, "heightPx": 256 }]
},
{
"kind": "dieFace", "name": "Face 2", "externalKey": "my-die-f2",
"parentExternalKey": "my-die",
"assets": [{ "sideKey": "face-2", "key": "...", "sourceUrl": "...", "contentType": "image/png", "widthPx": 256, "heightPx": 256 }]
}
]
}
Rate Limits
Component requests are not rate limited. Uploading the images they use is: see Errors.