API v1

API Documentation

Version 1 (v1) — additive only: νεά endpoints και προαιρετικά πεδία προστίθενται, ποτέ δεν αφαιρείται ή αλλάζει κάτι που υπάρχει.

Γρήγορη εκκίνηση

Το πιο σύντομο δρόμος: βρείτε ένα προϊόν, δημιουργήστε παραγγελία, παρακολουθήστε μέχρι το delivered και κατεβάστε το βίντεο.

  1. 1.GET /v1/products — διαλέξτε product_id και length_seconds από τις τιμές.
  2. 2.GET /v1/products/'{id'}/intake-form — δείτε ποια πεδία (answers) ζητά το προϊόν.
  3. 3.POST /v1/uploads (αν χρειάζονται αρχεία) — ανεβάστε με το presigned URL και κρατήστε τα upload_id.
  4. 4.POST /v1/orders — στείλετε product_id, length_seconds, answers και uploads, με Idempotency-Key.
  5. 5.GET /v1/orders/'{id'} — το status εξελίσσεται: received → in_production → (awaiting_approval / changes_requested) → delivered.
  6. 6.GET /v1/orders/'{id'}/video — με delivered παίρνετε ασφαλές download link.

Authentication

Κάθε κλήση στέλνει το API key σας στο Authorization header, τύπου Bearer. Δεν υπάρχουν cookies, δεν υπάρχει CORS — το API καλείται από backend/server, όχι από browser του τελικού χρήστη. Το key δίνει πρόσβαση ΜΟΝΟ στον δικό σας λογαριασμό: κάθε παραγγελία, αρχείο και χρηματική κίνηση είναι δεμένη με τον λογαριασμό σας, ό,τι κι αν στείλετε. Το key φαίνεται μία φορά κατά τη δημιουργία — αν χαθεί, ανακαλέστε το από το portal και φτιάξτε άλλο.

Authorization: Bearer vapi_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Base URL

Όλα τα endpoints ζουν κάτω από https://mystage.film/api/v1. Οι απαντήσεις είναι JSON. Κάθε response έχει x-request-id header — αναφέρετέ το σε ερώτημα υποστήριξης.

https://mystage.film/api/v1

Pagination

Τα list endpoints επιστρέφουν { data, has_more, next_cursor }. Περάστε next_cursor ως query παράμετρο cursor στην επόμενη κλήση. limit: 1–100 (προεπιλογή 20). Η σειρά είναι πάντα νεότερο πρώτα.

Idempotency (ασφαλή retries)

Κάθε POST που δημιουργεί κάτι (orders, uploads, change requests, approvals, cancel) ΑΠΑΙΤΕΙ το header Idempotency-Key με μοναδική τιμή ανά λειτουργία (π.χ. ένα UUID ανά διαδικασία). Ίδιο key + ίδιο σώμα = ίδια απάντηση χωρίς δεύτερη εκτέλεση· ίδιο key + διαφορετικό σώμα = 422 idempotency_mismatch· key που τρέχει ήδη = 409 idempotency_in_flight. Οι απαντήσεις κρατούνται 48 ώρες. Έτσι ένα network retry δεν δημιουργεί ποτέ διπλή παραγγελία ή διπλή χρέωση.

Idempotency-Key: 8f3c1d2a-77b9-4c1e-9a01-5f6e8d9c0b1a

Σφάλματα

Όλα τα σφάλματα ακολουθούν RFC 9457 (application/problem+json) με πεδία type, title, status, code, request_id και προαιρετικά errors για προβλήματα πεδίων. Οι κωδικοί που θα συναντήσετε:

  • 400 invalid_input — μη έγκυρο σώμα ή πεδίο (βλ. errors list)
  • 401 unauthenticated — λάθος ή ανακλημένο API key
  • 402 insufficient_credit — δεν φτάνει το υπόλοιπο· δεν έγινε καμία χρέωση
  • 403 forbidden / pinned_customer — το key δεν έχει το scope ή δώσατε ξένο customer_id
  • 404 not_found — άγνωστο id ή id άλλου λογαριασμού (απάντηση ίδια, χωρίς διαρροή πληροφορίας)
  • 409 idempotency_in_flight / conflict — το αίτημα τρέχει ήδη ή δεν επιτρέπεται στην τρέχουσα κατάσταση
  • 422 idempotency_mismatch / *_not_offered — ίδιο key με διαφορετικό σώμα, ή επιλογή που το προϊόν δεν προσφέρει
  • 429 rate_limited — πάρα πολλές κλήσεις· δοκιμάστε ξανά με backoff
  • 5xx — πρόβλημα δικής μας πλευράς· retry με το ίδιο Idempotency-Key είναι ασφαλές

Rate limits

Μέχρι 120 κλήσεις το λεπτό ανά key (ανάγνωση και γραφή συνολικά) και μέχρι 30 POST/PATCH/DELETE το λεπτό ανά key. Στο 429 η απάντηση έχει Retry-After. Δεν χρεώνεστε για απορριφθέντες κλήσεις.

Endpoints

Όλες οι κλήσεις είναι σχετικές με το base URL. Τα πεδία του σώματος είναι ακριβή (strict): άγνωστα πεδία απορρίπτονται.

GET/ping

Έλεγχος ότι το key είναι έγκυρο. Χωρίς πεδία.

response

  • ok: true
GET/me

Το key σας, ο λογαριασμός σας, το υπόλοιπο του πορτοφολιού σας και σύνοψη χρήσης 30 ημερών (κλήσεις, παραγγελίες, χρεώσεις σε cents).

response

  • key { id, name, last4, scopes, created_at, last_used_at }
  • customer { id, email, name }
  • wallet { currency, balance_cents }
  • usage { window_days, requests, orders, credits_cents }
curl -H "Authorization: Bearer $KEY" https://mystage.film/api/v1/me
GET/usage

Το ιστορικό κλήσεων του key σας: route, method, status, order_id αν η κλήση δημιούργησε παραγγελία και credits_cents όσα χρεώθηκαν. Cursor pagination.

body / query

  • cursor string? — next_cursor of the previous page
  • limit int? — 1..100 (default 20)

response

  • data: [{ id, created_at, route, method, status_code, order_id, credits_cents, product_name, order_status, delivered_at, currency }]
  • has_more: boolean
  • next_cursor: string | null
curl -H "Authorization: Bearer $KEY" "https://mystage.film/api/v1/usage?limit=50"
GET/products

Ο κατάλογος με τιμές ανά διάρκεια, aspect ratios, add-ons και το πεδίο intake_schema_id. Cursor pagination.

body / query

  • cursor string?
  • limit int? — 1..100

response

  • data: [{ id, slug, name, lengths: [{ seconds, price_cents }], aspect_ratios, add_ons, intake_schema }]
  • has_more
  • next_cursor
curl -H "Authorization: Bearer $KEY" https://mystage.film/api/v1/products
GET/products/{productId}

Ένα προϊόν με όλες τις λεπτομέρειές του.

response

  • Product — one product, all details
GET/products/{productId}/intake-form

Τα πεδία (answers) που ζητά το προϊόν: τύπος, ετικέτα, υποχρεωτικότητα, επιλογές, αν δέχεται upload. Χτίστε τη φόρμα σας από εδώ.

response

  • fields: [{ key, type, label, required, options?, upload? }]
POST/uploadsIdempotency-Key required

Ζητά presigned URL για ένα αρχείο (intake). Σώμα: purpose="intake", content_type (image/jpeg|png|webp, audio/mpeg|mp4), size_bytes (μέχρι 50 MB, ακριβές). Πάντα PUT στο url με το σωστό Content-Type πριν λήξει (10'). Κρατήστε το upload_id για τις απαντήσεις της παραγγελίας.

body / query

  • customer_id uuid — your own customer id (GET /me)
  • purpose "intake"
  • content_type enum — image/jpeg | image/png | image/webp | audio/mpeg | audio/mp4
  • size_bytes int — 1..52428800, exact

response

  • upload_id — reference it in the order answers
  • url — PUT the bytes here
  • headers { content-type }
  • expires_at (10 minutes)
curl -X POST -H "Authorization: Bearer $KEY" -H "Idempotency-Key: $IDEM" \
  -H "Content-Type: application/json" -d '{"customer_id":"…","purpose":"intake","content_type":"image/jpeg","size_bytes":48123}' \
  https://mystage.film/api/v1/uploads
POST/ordersIdempotency-Key required

Δημιουργεί παραγγελία και χρεώνει το πορτοφόλι σας σε ένα transaction. Σώμα: customer_id, product_id, length_seconds (από τις τιμές), aspect (προαιρετικά, από aspect_ratios), add_ons { subtitles, extra_formats, resolution, duration_seconds } (προαιρετικά, όσα προσφέρει το προϊόν), answers (τα πεδία του intake form· τα αρχεία ως upload_id), uploads (λίστα upload_id). complimentary δεν είναι διαθέσιμο μέσω API. 402 αν δεν φτάνει το υπόλοιπο — τότε δεν δημιουργείται τίποτα.

body / query

  • customer_id uuid — your own customer id
  • product_id uuid — from GET /products
  • length_seconds int — one of the product price lengths
  • aspect enum? — one of the product aspect_ratios (default: native)
  • add_ons object? — { subtitles?, extra_formats?, resolution?, duration_seconds? } only what the product offers
  • answers object — the intake form fields; files as upload_id
  • uploads string[] — the upload_ids used in answers

response

  • Order — 201 with status "received", total_cents, add_ons
  • 402 insufficient_credit — nothing created, nothing charged
curl -X POST -H "Authorization: Bearer $KEY" -H "Idempotency-Key: $IDEM" \
  -H "Content-Type: application/json" -d '{"customer_id":"…","product_id":"…","length_seconds":30,"answers":{}}' \
  https://mystage.film/api/v1/orders
GET/orders

Οι παραγγελίες σας, νεότερες πρώτα. Φίλτρα: status, created_after (ISO), customer_id. Cursor pagination.

body / query

  • cursor string?
  • limit int? — 1..100
  • status enum? — received | awaiting_approval | changes_requested | in_production | delivered | failed | cancelled | awaiting_balance
  • created_after string? — ISO timestamp

response

  • data: [Order]
  • has_more
  • next_cursor
curl -H "Authorization: Bearer $KEY" "https://mystage.film/api/v1/orders?status=delivered"
GET/orders/{orderId}

Μια παραγγελία: status (received, awaiting_approval, changes_requested, in_production, delivered, failed, cancelled, awaiting_balance), στοιχεία, add-ons, total_cents.

response

  • Order
POST/orders/{orderId}/cancelIdempotency-Key required

Αίτημα ακύρωσης για παραγγελία σε κατάσταση paid. Σώμα: reason (προαιρετικό). Η ακύρωση ολοκληρώνεται από την ομάδα μας· αν έχει ξεκινήσει παραγωγή, η χρέωση επιστρέφεται στο πορτοφόλι σας.

body / query

  • reason string? — why you cancel

response

  • Cancellation — status "pending" until our team completes it
GET/orders/{orderId}/storyboard

Το τρέχον storyboard μιας παραγγελίας: version, status, scenes με εικόνες (presigned URLs που λήγουν σε ~15').

response

  • version_id, version_number, status, revisions_allowed
  • scenes: [{ scene_index, label, caption, image_url }] (image URLs expire in ~15 minutes)
POST/orders/{orderId}/storyboard/approveIdempotency-Key required

Εγκρίνει το storyboard και ξεκινά την παραγωγή. Χωρίς σώμα.

response

  • StoryboardDecision — production starts
POST/orders/{orderId}/change-requestsIdempotency-Key required

Ζητά αλλαγές σε σκηνές. Σώμα: requests array με scene_index και comment (το ίδιο σχήμα με το site).

body / query

  • requests array — [{ scene_index int, comment string }]

response

  • StoryboardDecision — the order goes to changes_requested
GET/orders/{orderId}/video

Μόνο με delivered: ασφαλές download URL για το τελικό βίντεο (MP4), με λήξη.

response

  • url — secure MP4 download, expiring
  • 409 not_delivered — the video is not ready yet