Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.moda.app/llms.txt

Use this file to discover all available pages before exploring further.

Uploads

Two endpoints. Both require uploads:write.

Entrypoints

POST /v1/uploads — multipart

curl -X POST https://api.moda.app/v1/uploads \
  -H "Authorization: Bearer moda_live_..." \
  -H "Moda-Version: 2026-05-01" \
  -F "file=@/path/to/brief.pdf"
Response:
{
  "id": "file_01HT9WK8N3M2J4A5Z6P7Q8R9TV",
  "url": "https://api.moda.app/api/v2/images/ref/550e8400-...?h=abc123",
  "filename": "brief.pdf",
  "mime_type": "application/pdf",
  "size_bytes": 245760,
  "was_duplicate": false
}
Use when you have the bytes locally.

POST /v1/uploads/from-url

curl -X POST https://api.moda.app/v1/uploads/from-url \
  -H "Authorization: Bearer moda_live_..." \
  -H "Moda-Version: 2026-05-01" \
  -H "Content-Type: application/json" \
  -d '{"source_url": "https://example.com/mockup.png"}'
Server fetches the URL, validates MIME, stores. Returns the same FileUploadResponse shape. Use when the file is already hosted publicly and you don’t want to proxy it through your own machine. SSRF-validated server-side — internal / localhost / metadata URLs are rejected with 422.

Supported types

  • Images: PNG, JPEG, WebP
  • Documents: PDF, PPTX
The server enforces a max file size (multiple MB, varies by plan). Oversize uploads return 422 unprocessable.

Deduplication

Content-hash dedupe. Uploading the same bytes twice returns:
{ "id": "file_01HT9...", "was_duplicate": true, ... }
id / url / filename / mime_type / size_bytes point at the existing record. Safe to call repeatedly — no duplicate storage, no duplicate billing.

Using in a task

curl -X POST https://api.moda.app/v1/tasks \
  -H "Authorization: Bearer moda_live_..." \
  -H "Moda-Version: 2026-05-01" \
  -H "Content-Type: application/json" \
  -d '{
    "prompt": "Build a pitch deck from the attached brief",
    "format": { "category": "slides", "width": 1920, "height": 1080 },
    "attachments": [
      { "file_id": "file_01HT9WK8...", "role": "source", "label": "Q2 strategy brief" }
    ]
  }'

Roles

RoleMeaning
sourceExtract content from this file (brief, notes, CSV)
referenceEmulate this file’s style (mood board, screenshot)
assetUse verbatim (logo, hero image)
Pick the right role — it determines what the agent does with the file. See moda-mcp/references/attachments.md for details; the attachment shape is identical between MCP and REST.

URL-form attachment (legacy, less preferred)

{
  "attachments": [
    { "url": "https://example.com/mockup.png", "type": "image" }
  ]
}
No role metadata. Still works for hosted public URLs when you want to avoid the upload roundtrip. Mix with file-id-form in the same list freely.

Worked example

import httpx, os

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=60) as c:
    with open("brief.pdf", "rb") as f:
        upload = c.post("/uploads", files={"file": ("brief.pdf", f, "application/pdf")}).json()

    task = c.post("/tasks", json={
        "prompt": "Build a 10-slide pitch deck from the brief",
        "format": {"category": "slides", "width": 1920, "height": 1080},
        "number_of_slides": 10,
        "attachments": [
            {"file_id": upload["id"], "role": "source", "label": "Q2 strategy"},
        ],
        "idempotency_key": "q2-strategy-deck-v1",
    }).json()

Common wrong guesses

  • Posting files as JSON-encoded base64 into POST /v1/uploads. Use multipart (multipart/form-data).
  • Using the proxy URL (/api/v2/images/ref/…) as if it were stable public content. It includes an auth hash; it’s a stable reference for use in Moda-side operations, not a CDN URL.
  • Re-uploading on every run when content hasn’t changed. Dedupe handles it server-side, but you still pay a roundtrip. Cache file_id in your app.
  • Skipping role in the attachment. It drops back to a generic “reference” semantic, which is usually not what you want. Always set role.
  • Mixing file_id and url in one attachment item. Pick one per item. Mix items within the array is fine.

Upstream