moda-design/agent-plugins · Archived

moda-api

Use Moda's public REST API at api.moda.app. Use when building scheduled jobs, CI pipelines, server-side integrations, webhook receivers, or any Moda automation that runs without a human in the loop and authenticates with a moda_live_ API key. Covers the canonical Task envelope, Moda-Version pinning, prefixed IDs, typed errors with request_id, idempotency_key, cursor pagination, Prefer:wait sync-feel, webhook HMAC verification, the 2-10 minute design task lifecycle, and the synchronous export en…

First seen Jul 7, 2026

Installation

$ npx skills add moda-design/agent-plugins --skill moda-api

Summary

  • Use Moda's public REST API at api.moda.app.
  • Use when building scheduled jobs, CI pipelines, server-side integrations, webhook receivers, or any Moda automation that runs without a human in the loop and authenticates with a moda_live_ API key.
  • Covers the canonical Task envelope, Moda-Version pinning, prefixed IDs, typed errors with request_id, idempotency_key, cursor pagination, Prefer:wait sync-feel, webhook HMAC verification, the 2-10 minute design task lifecycle, and the synchronous export endpoint.

Stronger alternatives

This repository is archived — consider an actively maintained alternative.

Similar popular skills

Related neighbors and high-traction skills in the same topics — useful to compare before installing.

Also in this package

Other skills from moda-design/agent-plugins.

npx skills add moda-design/agent-plugins

Browse all from moda-design/agent-plugins

More details

Agent compatibility

Declared targets from SKILL.md / docs. Unmarked agents are not listed — the skill may still install via the CLI.

Claude Code Not declared
Cursor Not declared
Codex Not declared
GitHub Copilot Not declared
Windsurf Not declared
Gemini CLI Not declared
Cline Not declared
OpenCode Not declared

Repository health

Stars 1
License LICENSE
Default branch main
Open issues 0
Status Archived

Package contents

Files included with this skill beyond the listing page.

  • skill md SKILL.md 11,750 B
  • docs SUMMARY.md 522 B

History

  1. First seen on skills.sh
  2. First recorded snapshot · 1 installs

SKILL.md

moda-api

Moda's public REST API at https://api.moda.app/v1 is the right surface when there is no human in the loop — scheduled jobs, CI pipelines, webhook-driven workers, server-side integrations, custom dashboards. Authentication is Bearer token with a modalive API key.

If a user is chatting with an AI agent (Claude Desktop, Cursor, etc.), use the [moda-mcp](../moda-mcp/SKILL.md) skill instead — it handles OAuth, session context, and chat-shaped flows. The REST API and MCP can both run against the same Moda account.

Prerequisites

  • Base URL: https://api.moda.app/v1
  • Auth: Authorization: Bearer modalive<suffix>. Create keys in Settings → Developer → REST API. Keys are shown once; store in a secret manager. See [references/authentication.md](./references/authentication.md) for the 13 scopes.
  • Version: pin Moda-Version: 2026-05-01 on every request. Omitting it resolves to the current default (2026-04-12 today, advancing on sunset dates). See [references/versioning.md](./references/versioning.md).

Every write in this skill's examples uses Moda-Version: 2026-05-01. Every body JSON uses prefixed IDs (cvs, task, bk, file).

Before you call POST /v1/tasks — HARD GATE

Design tasks take 2–10 minutes. Getting the input right up front saves a lot of wasted compute and retry cycles. Run this pre-flight checklist before every task start:

  1. Format. Set format_category explicitly for anything other than slides (slides / social / carousel / pdf / diagram / ui / other). Omitting it produces a slide deck regardless of intent. See [references/canvases-and-exports.md](./references/canvases-and-exports.md) and [references/task-envelope.md](./references/task-envelope.md) for allowed values and dimensions.
  2. Brand. Fetch GET /v1/brand-kits and pick brandkitid. The team default auto-applies when omitted. Set skipbrandkit: true to opt out explicitly.
  3. References. If the task needs a source doc or style reference, POST /v1/uploads first, then pass attachments: [{fileid, role}] on the task. For an existing canvas as inspiration, use referencecanvas_ids.
  4. Idempotency. For retry-safe scheduled jobs, pass an idempotency_key derived from a stable key (e.g. "weekly-deck:2026-W17"). Repeat calls with the same key return the same task, not a duplicate. See [references/idempotency.md](./references/idempotency.md).
  5. Delivery strategy. Choose one up front:

- Webhook (callbackurl) — fires once on terminal state; best for cron / backend workers. API-key auth only (OAuth callers get 400). - Poll GET /v1/tasks/{id} at the retryafter_ms cadence (~3s) — best when the caller is short-lived. - Prefer: wait=<s> (max 30s) — best for fast operations (brand-kit creation, remix-without-prompt). Design tasks almost always exceed 30s; wait will time out and return the non-terminal envelope. See [references/task-envelope.md#prefer-wait](./references/task-envelope.md).

  1. IDs. Every ID in a JSON body must be prefixed (cvs, task, bk, file, conv). Path parameters tolerate bare UUIDs for convenience. Sending a bare UUID in a body returns 400 invalidrequest. See [references/ids.md](./references/ids.md).

The canonical flow

(1) create API key + pick scopes      → Settings → Developer → REST API
(2) ensure a brand kit                 → GET /v1/brand-kits (create if missing)
(3) upload references (if any)         → POST /v1/uploads or POST /v1/uploads/from-url
(4) start the design task              → POST /v1/tasks with format_category + callback_url or Prefer: wait
(5) receive terminal state             → webhook (task.succeeded / failed / canceled) OR GET /v1/tasks/{id} poll
(6) export the canvas                  → POST /v1/canvases/{id}/export (synchronous; returns signed URL)

Minimum viable example

TypeScript (Node 20+, fetch):

const HEADERS = {
  Authorization: `Bearer ${process.env.MODA_API_KEY!}`,
  "Moda-Version": "2026-05-01",
  "Content-Type": "application/json",
};

// 4. start the task
const taskRes = await fetch("https://api.moda.app/v1/tasks", {
  method: "POST",
  headers: HEADERS,
  body: JSON.stringify({
    prompt: "Create a 10-slide pitch deck for FocusTime...",
    format: { category: "slides", width: 1920, height: 1080 },
    number_of_slides: 10,
    callback_url: "https://your-server.com/webhooks/moda",
    idempotency_key: "focustime-v1",
  }),
});
const task = await taskRes.json();
// { id: "task_01HT9...", kind: "design", status: "queued", retry_after_ms: 3000, ... }

// 5a. poll (alternative to webhook)
let status = task;
while (!["succeeded", "failed", "canceled", "expired"].includes(status.status)) {
  await new Promise(r => setTimeout(r, status.retry_after_ms ?? 3000));
  status = await fetch(`https://api.moda.app/v1/tasks/${task.id}`, { headers: HEADERS }).then(r => r.json());
}

// 6. export
if (status.status === "succeeded") {
  const exportRes = await fetch(
    `https://api.moda.app/v1/canvases/${status.result.canvas_id}/export?format=pptx`,
    { method: "POST", headers: HEADERS },
  );
  const { url } = await exportRes.json();
}

Python (3.11+, httpx):

import os, time, httpx

HEADERS = {
    "Authorization": f"Bearer {os.environ['MODA_API_KEY']}",
    "Moda-Version": "2026-05-01",
}

with httpx.Client(base_url="https://api.moda.app/v1", headers=HEADERS, timeout=30) as c:
    # 4. start
    task = c.post("/tasks", json={
        "prompt": "Create a 10-slide pitch deck for FocusTime...",
        "format": {"category": "slides", "width": 1920, "height": 1080},
        "number_of_slides": 10,
        "callback_url": "https://your-server.com/webhooks/moda",
        "idempotency_key": "focustime-v1",
    }).json()

    # 5. poll
    while task["status"] not in {"succeeded", "failed", "canceled", "expired"}:
        time.sleep((task.get("retry_after_ms") or 3000) / 1000)
        task = c.get(f"/tasks/{task['id']}").json()

    # 6. export
    if task["status"] == "succeeded":
        exp = c.post(f"/canvases/{task['result']['canvas_id']}/export",
                     params={"format": "pptx"}).json()

Core concepts

  • 13 scopes, minimum-privilege principle. Pick only what the integration needs. tasks:write lets you start design tasks; designs:export lets you export; brand_kits:write lets you create kits. Full list: [references/authentication.md](./references/authentication.md).
  • Moda-Version: 2026-05-01 is canonical. Pin explicitly on every request. Unpinned traffic resolves to the current default and changes shape when the default advances on sunset dates. [references/versioning.md](./references/versioning.md).
  • Task envelope. Every task-shaped operation (design, export task, remix, brandkitextract) returns {id, kind, status, result, error, progress, input, credits, links, retryafterms}. Branch your code on status (queued / running / succeeded / failed / canceled / expired). [references/task-envelope.md](./references/task-envelope.md).
  • Prefixed IDs. Response id fields always come back prefixed (cvs01HT9…, task01HT9…, bk01HT9…, file01HT9…, conv01HT9…, evt01HT9…, org, team). Request body fields require prefixed form — bare UUIDs get 400 invalid_request. Path parameters tolerate bare UUIDs as a convenience. [references/ids.md](./references/ids.md).
  • Typed error envelope. {error: {type, code, message, docurl, requestid, details?, retryafterms?, causes?}}. Branch on type, not HTTP status code. Log requestid on every error. Retry upstreamerror and rate_limited; fix everything else. [references/errors.md](./references/errors.md).
  • Cursor pagination. List endpoints return {data: [...], nextcursor}. Iterate until nextcursor === null. Cursors are opaque + signed — don't hand-craft. Sort is (created_at DESC, id DESC) on immutable columns. [references/pagination.md](./references/pagination.md).
  • idempotency_key on POST /v1/tasks. Retry-safe across network timeouts and worker restarts. Reusing the same key returns the existing task, not a duplicate. [references/idempotency.md](./references/idempotency.md).
  • callbackurl is API-key-auth only. OAuth callers get 400 "callbackurl is only supported for API-key authenticated callers". Use polling from OAuth clients. Webhooks fire terminal-only (task.succeeded / task.failed / task.canceled / export.succeeded / export.failed) — sign verification is HMAC-SHA256 over {timestamp}.{body}. [references/webhooks.md](./references/webhooks.md).

Common tasks

Job-to-be-done Recipe
Weekly cron: generate a status deck, post URL to Slack [recipes/scheduled-generation.md](./recipes/scheduled-generation.md)
CSV of 50 prospects → 50 personalized decks [recipes/bulk-personalization.md](./recipes/bulk-personalization.md)
Export every team canvas as PDF to S3/Drive [recipes/export-pipeline.md](./recipes/export-pipeline.md)
Receive + verify + process webhooks [recipes/webhook-receiver.md](./recipes/webhook-receiver.md)
Upload a brief PDF → branded deck → PPTX [recipes/brief-to-deck-pdf-intake.md](./recipes/brief-to-deck-pdf-intake.md)
GitHub Action: regenerate theme tokens from a canonical canvas [recipes/design-to-code-ci.md](./recipes/design-to-code-ci.md)

Errors & retries — quick table

error.type Retry? Action
invalid_request No Fix the request (missing / malformed field)
authentication No Check the API key
permission No Add the missing scope, or access a resource the key's team owns
not_found No Check the ID; confirm team access
conflict No Resolve state conflict (usually a name collision)
idempotency_conflict No Reusing an idempotency_key with a different body — fix the key or the body
rate_limited Yes Respect Retry-After (seconds)
upstream_error Yes Transient upstream failure — back off and retry
internal_error Yes Server error — retry with backoff; include request_id when reporting
unprocessable No Validation failed on well-formed input

Full catalog + code examples: [references/errors.md](./references/errors.md).

Further reading