REST API reference
All paths are relative to https://shopify.workflow-transactional-email.app. Every endpoint except the index needs Authorization: Bearer fak_... - see Authentication and API keys.
Responses are JSON. Errors use the same shape throughout:
{ "error": "Invalid or missing API key" }Index and identity
| Method | Path | Level | Purpose |
|---|---|---|---|
| GET | /api/v1 |
none | API index. Confirms the API is up and reports the current release |
| GET | /api/v1/me |
read | Check a key works and see its access level |
The index needs no key, so it is a safe health check to point a monitor at.
Email layouts
| Method | Path | Level | Purpose |
|---|---|---|---|
| GET | /api/v1/email-templates |
read | List email layouts |
| GET | /api/v1/email-templates/:id |
read | Get one layout, including its subject, preview text and rendered body |
Accepts page (default 1), limit (default 25, max 100) and q to search by name.
Read-only. Create and edit layouts in the app.
Email senders
| Method | Path | Level | Purpose |
|---|---|---|---|
| GET | /api/v1/smtp-configs |
read | List SMTP senders |
| GET | /api/v1/senders |
read | List connected mailboxes (Microsoft 365, Google) |
SMTP senders report hasUsername and hasPassword rather than the credential itself - both halves are withheld, since a username is as much a part of the credential as the password. A sender stays identifiable by its name, host and from-address. Connected mailboxes report a partially masked address such as so***@example.com and never their sign-in tokens.
Read-only. Connect and edit senders in the app.
Secrets
| Method | Path | Level | Purpose |
|---|---|---|---|
| GET | /api/v1/secrets |
read | List secret names and descriptions |
Returns hasValue, never the value. There is no endpoint that reads a secret back. Create and update secrets in the app.
Uploaded files
| Method | Path | Level | Purpose |
|---|---|---|---|
| GET | /api/v1/files |
read | List uploaded logos, images and attachments |
| DELETE | /api/v1/files |
write | Delete one, by key in the body |
List accepts page, limit (max 100) and q to search by filename.
Add ?usage=true and each file also reports inUse plus a usedBy array naming the layouts and presets referencing it. That is how you find everything safe to clear out in a single request. It scans your layouts and presets, so it is opt-in rather than the default.
Uploading is not exposed here - add files through the media picker in the app.
HTTP requests
| Method | Path | Level | Purpose |
|---|---|---|---|
| GET | /api/v1/http-requests |
read | List HTTP requests |
| POST | /api/v1/http-requests |
write | Create one |
| GET | /api/v1/http-requests/:id |
read | Get one |
| PUT / PATCH | /api/v1/http-requests/:id |
write | Update one |
| DELETE | /api/v1/http-requests/:id |
write | Delete one |
| POST | /api/v1/http-requests/:id/test |
execute | Run it against the real target |
List accepts the same page, limit and q parameters as email layouts.
The target url must be http:// or https://. Other schemes are rejected when you save.
Literal credentials written into a header or body are masked in every response. Values referenced as {{ secrets.KEY }} are returned as written, because the reference itself is not sensitive.
History and statistics
Covers both email sends and HTTP requests.
| Method | Path | Level | Purpose |
|---|---|---|---|
| GET | /api/v1/history |
read | List executions |
| GET | /api/v1/history/:id |
read | Get one execution with request and response data |
| GET | /api/v1/stats |
read | Totals and success rate |
History accepts page, limit (max 100), actionType, status (PENDING, SUCCESS, FAILED), and actionConfigId to filter to a single layout or request.
actionType must be exactly EMAIL or HTTP_REQUEST. An unrecognised value is ignored rather than rejected, so a typo returns everything instead of erroring.
Stats accepts days (default 30, max 365).
Status codes
| Code | Meaning |
|---|---|
| 200 | Success |
| 201 | Created |
| 400 | Malformed request or failed validation |
| 401 | Missing, malformed or revoked API key |
| 403 | Key is valid but its level is too low for this endpoint |
| 404 | Not found, or belongs to a different shop |
| 409 | Refused because the record is still in use (file deletion) |
| 429 | Rate limited - see below |
| 502 | The request ran but the third-party target failed |
A record belonging to another shop returns 404 rather than 403, so the API never confirms that an id exists elsewhere.
Rate limits
Two independent budgets, both per key:
- 300 requests per 60 seconds across all endpoints.
- 60 execute calls per hour on top of that, covering
POST /api/v1/http-requests/:id/test.
Exceeding either returns 429 with an explanatory error. The execute budget is deliberately tight: a runaway loop firing live requests at a payment provider is a much worse failure than a slow script.
Budgets are per key, not per store, so one integration cannot exhaust another's allowance.

