MeyktAPI Documentation
OpenAPI specllms-full.txtDashboard
  • Overview
  • Quickstart
  • Authentication
  • Orders
  • Files
  • Webhooks
  • Errors

Files

Artwork, engraving files and print data are attached to order items through a two-step upload. You first request an upload slot (POST /v1/uploads), then send the file content directly to the returned address with a single HTTP PUT, and finally reference the returned upload_id inside items[].files when you create the order. File content is validated when the order is accepted, not when the file is uploaded.

#Two-step upload

The file never passes through the Meykt application server. POST /v1/uploads only hands out a short-lived, signed storage address; the bytes go straight to storage.

This has three practical consequences:

  • No request size or timeout problems. You are not limited by JSON body limits, and you never base64-encode a file into a request.
  • Retrying is cheap. If the PUT fails, repeat only the PUT. If the order call fails, repeat only the order call with the same upload_id values.
  • Validation happens later. The upload slot accepts whatever you send. Type checking, SVG sanitizing and the move into permanent storage happen when POST /v1/orders accepts the order that references the upload.

POST /v1/uploads requires the scope files:write. Creating the order requires orders:write. Scopes are not hierarchical — a key that only has orders:write cannot request upload slots.

#Step 1 request an upload slot

Send the file name you want the file to be stored under. The name is sanitized (directory parts removed, unusual characters replaced with _, truncated to 120 characters) and the sanitized value is echoed back as filename.

bash
curl -X POST https://api.meykt.com/v1/uploads \
  -H "Authorization: Bearer $MEYKT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"filename": "engraving-logo.svg"}'

Response 201:

json
{
  "data": {
    "upload_id": "3f0a9c1e-2b77-4c1a-9f1c-0d2a4e6b8c10",
    "filename": "engraving-logo.svg",
    "upload_url": "https://…signed…",
    "method": "PUT",
    "expires_in_seconds": 7200,
    "max_bytes": 52428800
  }
}

One upload_id belongs to exactly one file. Request a separate slot for every file you want to attach.

#Step 2 send the file

Send the raw file content as the request body of a single PUT to upload_url. No authorization header is needed — the address is already signed.

bash
response=$(curl -s -X POST https://api.meykt.com/v1/uploads \
  -H "Authorization: Bearer $MEYKT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"filename": "engraving-logo.svg"}')

upload_url=$(echo "$response" | jq -r '.data.upload_url')
upload_id=$(echo "$response" | jq -r '.data.upload_id')

curl -X PUT "$upload_url" --data-binary @engraving-logo.svg

Check the HTTP status of the PUT. Only a 2xx means the file arrived; on any other status the file is not stored and the upload_id will later be rejected with file_not_found.

#Step 3 reference the upload in an order

Add the upload_id to the files array of the item it belongs to. role is optional free text (max 40 characters) and is display information only.

bash
curl -X POST https://api.meykt.com/v1/orders \
  -H "Authorization: Bearer $MEYKT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "external_order_id": "ORDER-2026-1044",
    "customer": { "name": "Anna Beispiel" },
    "items": [{
      "sku": "BECHER-GROSS",
      "name": "Mug large",
      "quantity": 2,
      "personalization": { "engraving": "All the best" },
      "files": [{ "upload_id": "3f0a9c1e-2b77-4c1a-9f1c-0d2a4e6b8c10", "role": "engraving" }]
    }]
  }'

Files are accepted before anything is written. A single unusable file rejects the whole order with 422 and nothing is created — this is the same all-or-nothing rule that applies to field validation.

In responses, each item reports its files as name and role only:

json
{
  "files": [{ "name": "engraving-logo.svg", "role": "engraving" }]
}

The API does not return download URLs for order files. The files are visible on the order in the dashboard.

#Allowed file types

The actual content is checked, not the file extension and not the Content-Type you sent. Detection reads the first bytes of the stored file. Renaming a .mp4 to .svg does not help — it is rejected with file_type_not_allowed, and the error message names the type that was detected.

FormatDetected type
PNGimage/png
JPEGimage/jpeg
WebPimage/webp
GIFimage/gif
SVGimage/svg+xml
TIFFimage/tiff
BMPimage/bmp
HEIC / HEIF / AVIFimage/heic, image/heif, image/avif
PDFapplication/pdf
DXFapplication/dxf
EPS / PSapplication/postscript
ZIP (including ZIP-based Office formats)application/zip
Plain text / CSVtext/plain, text/csv

Notes:

  • Adobe Illustrator files are accepted through the format they are actually written in: older PostScript-based .ai files are detected as application/postscript, current PDF-based .ai files as application/pdf.
  • A file must start with a recognizable header. A truncated or empty upload is reported as file_not_found, not as a type error.
  • SVG is sanitized automatically. Scripts, inline event handlers, external references and active URL schemes are stripped, and the file is stored with Content-Disposition: attachment. The stored SVG can therefore differ from the bytes you uploaded.

#Limits

  • 50 MB per file (max_bytes: 52428800).
  • Up to 10 files per order item. More than 10 entries in items[].files is a schema violation and fails with validation_failed.
  • One file per upload_id.
  • upload_id must be a UUID — the exact value returned by POST /v1/uploads.

#Lifetime

  • The upload_url is valid for 2 hours (expires_in_seconds: 7200). After that, request a new upload slot; the old upload_id is unusable.
  • An upload that is never referenced by an order expires after 14 days. Nothing to clean up on your side, and there is no endpoint to delete an upload.
  • Once an order referencing the upload is accepted, the file is stored permanently with that order and is no longer subject to the 14-day expiry.

#Reusing uploads on retry

Duplicate detection for orders is computed over the upload_id values, not over storage locations. That makes retries safe:

  • Resending the identical order body, including the same upload_id values, returns 200 with the existing order — never a duplicate.
  • Using new upload_id values with the same external_order_id counts as different content and returns 409 order_already_exists_with_different_content.
  • If an order is rejected (file problem or validation error), the uploads stay usable. Fix the request and resend it with the same upload_id values.

#Errors

Both file errors are reported with HTTP 422 and the top-level code validation_failed; the specific code sits in errors[] together with the exact position in field, using bracket notation.

json
{
  "error": {
    "code": "validation_failed",
    "message": "The order was rejected because of file problems. Nothing was created.",
    "doc_url": "https://www.meykt.com/en/developers/errors/validation_failed",
    "errors": [
      {
        "code": "file_type_not_allowed",
        "message": "The file for upload_id 3f0a9c1e-2b77-4c1a-9f1c-0d2a4e6b8c10 has an unsupported type (detected: video/mp4).",
        "field": "items[0].files[0].upload_id"
      }
    ]
  }
}

file_not_found — the upload_id is unknown, the PUT in step 2 never succeeded, or the upload already expired after 14 days. Fix: run POST /v1/uploads, PUT the content to upload_url, verify the PUT returned 2xx, then reference the upload_id. Details: /developers/errors/file_not_found.

file_type_not_allowed — the detected content is not in the allowed list. The message names the detected type. Fix: export the file in one of the formats listed under Allowed file types. Renaming the extension changes nothing, because the content is what is checked. Details: /developers/errors/file_type_not_allowed.

Both errors mean that no part of the order was created. Correct the file, then resend the complete order.

Full list of codes: /developers/errors. Order semantics, limits and warnings: /developers/orders.

Machine-readable version of this page: https://www.meykt.com/developers/files.md

On this page

  • Two-step upload
  • Step 1 request an upload slot
  • Step 2 send the file
  • Step 3 reference the upload in an order
  • Allowed file types
  • Limits
  • Lifetime
  • Reusing uploads on retry
  • Errors

Base URL https://api.meykt.com/v1 · Machine-readable: openapi.json · llms-full.txt

Imprint · Privacy · Terms