API Getting Started

The Postcards API lets you connect your own apps, CRMs, CMSs, marketing automations, and AI agents to Postcards — pulling the email templates you've built in the editor and rendering them as ready-to-use HTML, all without ever opening the editor in a browser.

In this article, we'll walk you through everything you need to send your first authenticated request: creating an API key, picking the right endpoint, and exporting your first email project as HTML.

Important: The API is read-only for projects and folders — you can list, fetch, and export, but creating or editing projects is done in the editor. Exporting consumes from your team's monthly export quota.

Note: Only the account Owner can create, view, and delete API keys. If you don't see the API Keys section, check with your team owner.


What you can do with the API

  • List your team's projects — pull every email project in the workspace, optionally filtered by folder, paginated 50 at a time.
  • Inspect folder structure — list folders, get a folder with its projects.
  • Check usage — see your active plan and how many exports you've used this billing period.
  • Export a project to HTML or ZIP — render any project to production-ready HTML, optionally with images and fonts hosted on the Postcards CDN, optionally with {{variable}}   placeholders replaced.

Typical use cases:

  • Pulling rendered HTML into your transactional email service (Postmark, SendGrid, Mailgun, Customer.io).
  • Personalizing a single template per recipient via {{variables}}  .
  • Building automated workflows ("when a row is added in Airtable, render and send this Postcards project").
  • Backing up your email designs by exporting them as ZIPs.

1. Create your API key

API keys are managed per workspace, from Workspace Settings.

  • Open the workspace you want to connect, then click Workspace Settings in the left sidebar.
  • Go to the API tab.

  • Click Create API key, give it a descriptive name (e.g., "CRM integration"), and click Create.

  • Copy the key immediately and store it somewhere secure; it's shown only once, and you won't be able to view it again. Treat it like a password.

Who can manage API keys

  • Owners and Editors can create and revoke API keys.
  • Viewers cannot access the API tab.

Important: Each key is scoped to the workspace it was created in — it can only access that workspace's projects, folders, and usage data. Anyone who holds the key has the same read and export access as a member of that workspace, so treat it like a password: don't share it or commit it to public code.

Important: API keys stay active even after the person who created them leaves or is removed from the workspace. Review your keys regularly and revoke any that are no longer needed or may have been exposed.


2. Make your first request

The base URL of the API is:

https://api-postcards.designmodo.com/api/v1/

Every request must include an Authorization: Bearer <your-key>     header. Let's list the first page of projects in your team:

Mac / Linux (Terminal)

curl https://api-postcards.designmodo.com/api/v1/projects \   -H 'Authorization: Bearer sk-pcds-api03-XXXX'

Windows — Command Prompt (CMD)

curl https://api-postcards.designmodo.com/api/v1/projects -H "Authorization: Bearer sk-pcds-api03-XXXX"

Note for CMD users: Use double quotes "     instead of single quotes. Backslash line continuation (\     ) is not supported — keep the command on a single line.


Windows — PowerShell

Invoke-RestMethod -Uri "https://api-postcards.designmodo.com/api/v1/projects" `   -Headers @{ Authorization = "Bearer sk-pcds-api03-XXXX" }

Note for PowerShell users: PowerShell uses Invoke-RestMethod     instead of curl     (which is an alias that behaves differently). Use a backtick `     for line continuation, and pass headers as a hashtable @{ }    .

If everything is set up correctly, you'll get back JSON like this:

{
  "data": [
    {
      "id": 305876,
      "obfuscated_id": "32b3f40e",
      "name": "Spring Newsletter",
      "folder_id": null,
      "edited_at": "2026-05-18 11:19:49",
      "created_at": "2026-05-18T11:19:49.000000Z",
      "updated_at": "2026-05-19T02:59:36.000000Z"
    }
  ],
  "meta": {
    "page": 1,
    "per_page": 50,
    "total": 537,
    "total_pages": 11
  }
}

A few things to notice:

  • Each project has two identifiers — a numeric id     and a short hex obfuscated_id    . Both work everywhere the API takes an {id}  , so pick whichever you prefer.
  • The response is paginated — 50 projects per page. The meta   block tells you how to navigate.
  • The default sort order is most-recently edited first.

3. Pagination

All list endpoints return at most 50 items per page and include a meta   block describing the current position. This applies to:

  • GET /api/v1/projects        — full project list, optionally filtered by folder_id   .
  • GET /api/v1/folders/{id}        — a folder's nested projects   array is paginated too (folder metadata is not — there's only one folder per request).

Query parameters

Parameter Type Default Description
page        integer 1        Page number, 1-based.
per_page        integer 50        Items per page. Maximum 100  .

The meta    block

Every paginated response includes a meta  object with four fields:

Field Description
page        The page you requested (echoed back).
per_page        Items per page after clamping (1..100    ).
total        Total number of items that match the filter.
total_pages        ceil(total / per_page)     . 0     when there are no items.

Walking through all the pages

A typical loop in pseudocode:

let page = 1;

while (true) {
  const res = await api(`/api/v1/projects?page=${page}&per_page=100`);
  for (const project of res.data) {
    // ...do something with each project
  }
  if (page >= res.meta.total_pages) break;
  page++;
}

Pick per_page=100     if you want fewer round-trips, or stick with the default 50 if your client struggles with larger payloads.

Important: A page request beyond total_pages     returns data: []     with an accurate meta.total     — not a 404. Use meta.total_pages     as the loop bound, not "until the response is empty".


4. Export a project to HTML

This is where the API earns its keep. Pick a project from the list above (we'll use 305876    ) and call the export endpoint:

Mac / Linux

curl -X POST https://api-postcards.designmodo.com/api/v1/projects/305876/export \   -H 'Authorization: Bearer sk-pcds-api03-XXXX' \   -H 'Content-Type: application/json' \   -d '{ "imageHosting": true, "minify": true }'

Windows — CMD

curl -X POST https://api-postcards.designmodo.com/api/v1/projects/305876/export -H "Authorization: Bearer sk-pcds-api03-XXXX" -H "Content-Type: application/json" -d "{\"imageHosting\": true, \"minify\": true}"

Note: In CMD, the JSON body must use double quotes, and each inner quote must be escaped with a backslash \"     .

Windows — PowerShell

$body = '{ "imageHosting": true, "minify": true }'  Invoke-RestMethod -Method Post `   -Uri "https://api-postcards.designmodo.com/api/v1/projects/305876/export" `   -Headers @{ Authorization = "Bearer sk-pcds-api03-XXXX" } `   -ContentType "application/json" `   -Body $body

You'll get back JSON with a single html   field containing the complete, ready-to-send HTML:

{   "html": "<!DOCTYPE html><html>...<img src=\"https://cdn.postcards.app/...\">...</html>" }
curl -X POST https://api-postcards.designmodo.com/api/v1/projects/305876/export \
  -H 'Authorization: Bearer sk-pcds-api03-XXXX' \
  -H 'Content-Type: application/json' \
  -d '{ "imageHosting": true, "minify": true }'

What the options do

Option Type Description
imageHosting        boolean When true  , images and fonts are referenced from the Postcards CDN, and the response is JSON. When false        (default), you get a binary ZIP with images bundled locally.
cdn        boolean When true  , host all assets (CSS, fonts, images) on the CDN. Available on Pro plans and higher.
minify        boolean Strip whitespace from the output HTML — smaller payloads, slightly harder to debug.
format        string Response format when imageHosting=true       . "json"        (default) wraps the HTML in { "html": "..." }       . "html"        returns raw HTML as the response body with Content-Type: text/html       . Ignored when imageHosting=false        (you always get a ZIP).
variables        object Map of {{placeholder}}        keys to scalar values. The placeholder text in the template is replaced before rendering.

Three response shapes

The combination of imageHosting     and format     decides what you get back:

imageHosting        format        Response
false        (default) Binary ZIP archive (Content-Type: application/zip       ) containing index.html     plus local images/      and fonts/        folders.
true        "json"        (default) JSON { "html": "..." }      with CDN-hosted assets. Easy to consume from any client.
true        "html"        Raw HTML body (Content-Type: text/html; charset=utf-8       ) with CDN-hosted assets. Pipe straight into an email service or save to a file.

Raw HTML example

Mac / Linux

curl -X POST https://api-postcards.designmodo.com/api/v1/projects/305876/export \   -H 'Authorization: Bearer sk-pcds-api03-XXXX' \   -H 'Content-Type: application/json' \   -d '{ "imageHosting": true, "format": "html", "minify": true }' \   -o newsletter.html

Windows — CMD

curl -X POST https://api-postcards.designmodo.com/api/v1/projects/305876/export -H "Authorization: Bearer sk-pcds-api03-XXXX" -H "Content-Type: application/json" -d "{\"imageHosting\": true, \"format\": \"html\", \"minify\": true}" -o newsletter.html

Windows — PowerShell

$body = '{ "imageHosting": true, "format": "html", "minify": true }'  Invoke-RestMethod -Method Post `   -Uri "https://api-postcards.designmodo.com/api/v1/projects/305876/export" `   -Headers @{ Authorization = "Bearer sk-pcds-api03-XXXX" } `   -ContentType "application/json" `   -Body $body `   -OutFile "newsletter.html"
curl -X POST https://api-postcards.designmodo.com/api/v1/projects/305876/export \
  -H 'Authorization: Bearer sk-pcds-api03-XXXX' \
  -H 'Content-Type: application/json' \
  -d '{ "imageHosting": true, "format": "html", "minify": true }' \
  -o newsletter.html

You now have newsletter.html  on disk — ready to send.

Personalizing with {{variables}}       

Anywhere in your email design where you've written {{username}}  or{{order_number}}  , the API can substitute real values:

Mac / Linux

curl -X POST https://api-postcards.designmodo.com/api/v1/projects/305876/export \   -H 'Authorization: Bearer sk-pcds-api03-XXXX' \   -H 'Content-Type: application/json' \   -d '{     "imageHosting": true,     "minify": true,     "variables": {       "username": "Alex",       "company": "Acme Inc",       "order_number": "A-22817"     }   }'

Windows — CMD

curl -X POST https://api-postcards.designmodo.com/api/v1/projects/305876/export -H "Authorization: Bearer sk-pcds-api03-XXXX" -H "Content-Type: application/json" -d "{\"imageHosting\": true, \"minify\": true, \"variables\": {\"username\": \"Alex\", \"company\": \"Acme Inc\", \"order_number\": \"A-22817\"}}"

Windows — PowerShell

$body = '{   "imageHosting": true,   "minify": true,   "variables": {     "username": "Alex",     "company": "Acme Inc",     "order_number": "A-22817"   } }'  Invoke-RestMethod -Method Post `   -Uri "https://api-postcards.designmodo.com/api/v1/projects/305876/export" `   -Headers @{ Authorization = "Bearer sk-pcds-api03-XXXX" } `   -ContentType "application/json" `   -Body $body

The resulting HTML will read "Hi Alex from Acme Inc!" instead of "Hi {{username}} from {{company}}!". Variables that you don't pass stay as-is in the output — handy for partial substitution.


5. Other useful endpoints

Once you're comfortable with the basics, these are the rest of the endpoints you'll likely use:

Endpoint What it does
GET /api/v1/projects?folder_id={id}        Filter projects by folder. Use the numeric id       , obfuscated_id       , or null   for root-level projects.
GET /api/v1/projects/{id}        Get metadata for a single project (no content payload).
GET /api/v1/folders        List all folders in your team.
GET /api/v1/folders/{id}        Get one folder together with a paginated list of its projects.
GET /api/v1/usage        Plan name, billing period, and exports used this period. Call this before bulk operations.

All list endpoints are paginated with the same page   / per_page  query parameters (default 50, max 100).


6. Error responses

Errors always come back as JSON with an error     field plus an HTTP status code:

Status Body Meaning
401        { "error": "unauthorized" }        Missing or invalid API key.
403        { "error": "plan_required", "required_plan": "pro", "feature": "cdn" }        The feature you requested isn't available on your team's plan.
404        { "error": "not_found" }        Resource doesn't exist or doesn't belong to your team.
422        { "error": "validation", "details": {...} }        Invalid request body.
429        { "error": "rate_limit_exceeded", "limit": 60, "window_seconds": 60 }        You're sending requests too fast. Wait and retry.
429        { "error": "export_limit_exceeded", "limit": 20 }        Monthly export quota exhausted.

7. Rate limits

The API enforces two rate-limit buckets per API key:

  • Read endpoints — 60 requests per minute. Includes GET /projects       , GET /folders       , and GET /usage       .
  • Export endpoint — 10 requests per minute.

If you hit a limit, the response sets a Retry-After   header telling you how many seconds to wait. The two buckets are independent — being throttled on exports doesn't slow down your list requests.


What we covered

  1. How to create an API key in your Postcards workspace.
  2. How to authenticate and make your first API request on Mac, Windows CMD, and PowerShell.
  3. How to paginate through your project list.
  4. How to export a project to HTML or ZIP, with options for CDN hosting, minification, and variable substitution.
  5. An overview of other useful endpoints for projects, folders, and usage.
  6. How to handle API errors and rate limits.
Did this answer your question? Thanks for the feedback There was a problem submitting your feedback. Please try again later.

Still need help? Contact Us Contact Us