Automation
Push live values and read widgets. Use for Zapier, n8n, and scripts.
live-value:read, live-value:write, widgets:read
Developer reference
Create and manage widgets, push ordered live values, and deliver dynamic images from a small HTTP API. The production base URL is https://api.dynamail.io. Routes are currently unversioned.
Open Dashboard → API Keys, create a named key, and store the full dm_sk_... value when shown. DynaMail stores its hash and cannot display it again. Send the key in the X-API-Key header.
GET /me is the smallest connection test and requires a valid key but no scope. Use the returned data.id as the userId when creating a widget.
Connection test
curl https://api.dynamail.io/me \
-H "X-API-Key: dm_sk_your_key_here"200 response
{
"success": true,
"data": { "id": "YOUR_ACCOUNT_ID" }
}In Dashboard → API Keys, create a Full access key for the widget creation step. For recurring pushes, create a separate Automation key and keep each key in your credential store.
Call GET /me. The returned data.id is YOUR_ACCOUNT_ID in the next request.
Request
curl https://api.dynamail.io/me \
-H "X-API-Key: dm_sk_your_key_here"200 response
{
"success": true,
"data": { "id": "YOUR_ACCOUNT_ID" }
}POST /widgets requires widgets:write, which is included in the Full access preset.
Request
curl --request POST 'https://api.dynamail.io/widgets' \
--header 'Content-Type: application/json' \
--header 'X-API-Key: dm_sk_your_key_here' \
--data '{"name":"Signups","type":"stat_counter","userId":"YOUR_ACCOUNT_ID","config":{"value":1250,"label":"Signups this week"}}'201 response (trimmed)
{
"success": true,
"data": {
"id": "WIDGET_ID",
"userId": "YOUR_ACCOUNT_ID",
"type": "stat_counter",
"name": "Signups",
"config": {
"value": 1250,
"label": "Signups this week",
"dslLayout": "…",
"tokens": "…"
},
"createdAt": "2026-09-12T12:34:56.789Z",
"updatedAt": "2026-09-12T12:34:56.789Z"
}
}The API stamped the default layout and tokens, so the minimal configuration is ready to render. Save data.id as WIDGET_ID.
API calls use api.dynamail.io; delivered images use img.dynamail.io. The stamped Stat Counter is 340×184.
HTML embed
<img src="https://img.dynamail.io/widgets/WIDGET_ID/render.png" width="340" height="184" alt="Signups this week">Use the Automation key, which includes live-value:write. Increase sourceVersion with each newer upstream event.
Request
curl --request PUT 'https://api.dynamail.io/widgets/WIDGET_ID/live-value' \
--header 'Content-Type: application/json' \
--header 'X-API-Key: dm_sk_your_key_here' \
--data '{"value":1250,"sourceVersion":1}'200 receipt
{
"success": true,
"data": {
"widgetId": "WIDGET_ID",
"revision": 1,
"active": true,
"value": 1250,
"sourceVersion": 1,
"updatedAt": "2026-09-12T12:34:56.789Z",
"applied": true,
"reason": "applied"
}
}Check data.applied and data.reason; a successful HTTP response can report a stale or unchanged event.
Add the same recipient identifier to the update and the image URL.
Recipient request
curl --request PUT 'https://api.dynamail.io/widgets/WIDGET_ID/live-value' \
--header 'Content-Type: application/json' \
--header 'X-API-Key: dm_sk_your_key_here' \
--data '{"value":1250,"sourceVersion":1,"recipient":"person@example.com"}'Recipient HTML embed
<img src="https://img.dynamail.io/widgets/WIDGET_ID/render.png?recipient=person%40example.com" width="340" height="184" alt="Signups this week">For many recipients, PUT /widgets/WIDGET_ID/live-value/bulk accepts 1–500 unique recipient entries in one request.
Choose the narrowest preset that supports the integration. When an endpoint lists more than one acceptable scope, the rule is ANY-of: the key needs one, not all. Keys created before scoped keys were introduced retain full access.
| Scope | Access |
|---|---|
widgets:read | List owned widgets and widget summaries. |
widgets:write | Create, update, and delete owned widgets. |
live-value:read | Read an owned widget's pushed live value. |
live-value:write | Set, increment, bulk-set, or clear pushed live values; also list widget summaries. |
Push live values and read widgets. Use for Zapier, n8n, and scripts.
live-value:read, live-value:write, widgets:read
List and read widgets and live values.
widgets:read, live-value:read
Create, edit, and delete widgets too.
widgets:read, widgets:write, live-value:read, live-value:write
403 missing-scope response
{
"error": {
"code": "FORBIDDEN",
"message": "API key lacks the required scope",
"context": {
"required": ["widgets:write"],
"granted": ["widgets:read", "live-value:read", "live-value:write"]
}
}
}Schema names identify each operation's request and response shapes in the downloadable OpenAPI document. An empty scopes field means the endpoint is public or only requires a valid key, as described in its summary.
/widgets/{id}/gifts201Add a gift to a fundraising campaign
/me200Get the authenticated API account identity
/latest-content/{id}200Get the current published latest content item
/latest-content/{id}/image.png200Get the generation-bound latest content image
/widgets201Create widget
/widgets200List widgets
/widgets/summaries200List owned widget metadata without configuration
/widgets/{id}200Get widget by ID
/widgets/{id}200Update widget
/widgets/{id}200Delete widget
/widgets/{id}/live-value200Get the live value (Stat Counter, Progress Bar, Radial Progress, Gauge Progress, Vertical Progress)
/widgets/{id}/live-value/bulk200Set recipient live values in bulk (Stat Counter, Progress Bar, Radial Progress, Gauge Progress, Vertical Progress)
/widgets/{id}/live-value200Set the live value (Stat Counter, Progress Bar, Radial Progress, Gauge Progress, Vertical Progress)
/widgets/{id}/live-value/increment200Increment the live value (Stat Counter, Progress Bar, Radial Progress, Gauge Progress, Vertical Progress)
/widgets/{id}/live-value/recipients200Clear all recipient live values (Stat Counter, Progress Bar, Radial Progress, Gauge Progress, Vertical Progress)
/widgets/{id}/live-value200Clear the live value (Stat Counter, Progress Bar, Radial Progress, Gauge Progress, Vertical Progress)
/widgets/{id}/render.png200Render widget as PNG
/widgets/{id}/render.gif200Render widget as GIF
/widgets/{id}/render.webp200Render widget as WebP
/widgets/{id}/render.apng200Render widget as animated PNG
/widgets/{id}/render.jxl200Render widget as JPEG XL
/widgets/{id}/render.jpg200Render widget as JPEG
/widgets/{id}/render.jpeg200Render widget as JPEG (alias)
/openapi.json200Get the public OpenAPI document
/health/live200Liveness check
/health200Health check
/assets/{userId}/{hashDotExt}200Get a user image by content hash
/backgrounds/{category}/{filename}200Get a background image by category and filename
/backgrounds/id/{imageId}200Get a background image by ID
Render precedence is an explicit URL ?value= or ?current= override, then a non-tombstoned recipient row, the global pushed value, a pull source, and finally authored configuration or 0.
value must be an integer from -1,000,000,000,000 through 1,000,000,000,000. sourceVersion is a positive safe integer that increases with the upstream system’s ordering. Prefer its revision or change sequence; retries of one event must reuse the same version. Do not derive it from request arrival time. Older writes return stale, identical retries return unchanged, and an equal version with a different value returns 409.
Count events. Post a non-zero integer delta when each trigger should add to or subtract from the current value. Increments are atomic. Send a unique eventId so retries count once per widget within 7 days; without one, every request counts.
Request
curl --request POST 'https://api.dynamail.io/widgets/WIDGET_ID/live-value/increment' \
--header 'Content-Type: application/json' \
--header 'X-API-Key: dm_sk_your_key_here' \
--data '{"delta":1,"eventId":"order-123"}'Recipient keys are URL-decoded once, trimmed, normalized to Unicode NFC, and compared case-insensitively. DynaMail stores only a salted, widget-specific hash. A bulk request accepts 1–500 unique normalized recipients, processes them in input order, and retains at most 10,000 recipient rows per widget.
For Progress Bar, Radial Progress, Gauge, and Vertical Progress widgets, the pushed value fills current. Keep target and styling in the embed URL. Negative values render as 0, and a value past the target fills the widget while its text shows the real total. An explicit ?current= always wins. Step Progress, Wave, and Timeline do not support pushed live values.
Clearing preserves the widget design and writes a tombstone with the last accepted source version, so a delayed retry cannot restore the value. A later update needs a strictly greater version. Recipient clears fall through to the global value.
| Status | Meaning |
|---|---|
| 400 Bad Request | The request body, query, or widget type is invalid. |
| 401 Unauthorized | The API key is missing, malformed, unknown, or revoked. |
| 403 Forbidden | The key lacks every acceptable scope for the operation. |
| 404 Not Found | The resource is missing or belongs to another account. |
| 409 Conflict | A revision, source-version, source, or data-source conflict occurred. |
| 429 Too Many Requests | The authenticated account quota is exhausted. |
| 503 Service Unavailable | Shared quota storage is unavailable. |
| Status | Meaning |
|---|---|
| 400 Bad Request | The request body, query, or widget type is invalid. |
| 401 Unauthorized | The API key is missing, malformed, unknown, or revoked. |
| 403 Forbidden | The key lacks every acceptable scope for the operation. |
| 404 Not Found | The resource is missing or belongs to another account. |
| 409 Conflict | A revision, source-version, source, or data-source conflict occurred. |
| 429 Too Many Requests | The authenticated account quota is exhausted. |
| 503 Service Unavailable | Shared quota storage is unavailable. |
| Tier | Requests per hour |
|---|---|
| Indie | 100 |
| Growth | 500 |
| Business | 2,500 |
| Enterprise | 10,000 |
Authenticated widget calls share one hourly allowance across all of an owner’s keys and client IPs. Unresolved key attempts also have a 300-per-minute, per-IP protection. For both 429 quota exhaustion and 503 quota-storage outages, honor Retry-Afterand retry unchanged input; a 503 currently reports a five-second delay. For most widget types, PATCH /widgets/:id replaces the complete stored config.
Use manual HTTP connections for Make, n8n, Zapier, or your own worker. DynaMail is not in the Zapier directory. Keep one named key per integration so it can be revoked independently.
Creating a widget requires widgets:write, which is included in the Full access preset. Recurring live-value pushes only need the Automation preset.
No. The production base URL is https://api.dynamail.io and routes are currently unversioned.
When an endpoint lists multiple acceptable scopes, the rule is ANY-of: the key needs one, not all, of those scopes.
Yes. Reuse the same sourceVersion for the same upstream event and follow Retry-After for 429 or 503 responses.
For most widget types, PATCH replaces the stored config as a whole. Read and preserve the complete current config before editing it.
No. DynaMail is not in the Zapier directory; use API by Zapier with an X-API-Key header.