API v1

Τεκμηρίωση API

Έκδοση 1 (v1), μόνο προσθήκες: νέα endpoints και προαιρετικά πεδία προστίθενται, ποτέ δεν αφαιρείται ή αλλάζει κάτι που υπάρχει.

Κρατήστε αντίγραφο των βίντεό σας

Δεν διαγράφουμε ποτέ βίντεο, όμως συνιστούμε να κατεβάζετε κάθε έτοιμο βίντεο μία φορά και να το φιλοξενείτε στον δικό σας server. Οι τιμές και το πορτοφόλι σας είναι ίδια με το mystage.film.

Η ροή από άκρη σε άκρη

Από μια κατηγορία μέχρι ένα βίντεο στον δικό σας server. Κάθε POST στέλνει νέο Idempotency-Key. Ο πλήρης οδηγός ως κείμενο: docs/api/guides/quickstart.md, και στο /llms.txt.

  1. 1.Πάρτε key: ζητήστε πρόσβαση στο /account/api· μετά την έγκρισή μας πατήστε Δημιουργία key. Το key φαίνεται μία φορά.
  2. 2.GET /v1/me: customer.id (το στέλνετε ως customer_id) και wallet.balance_cents. Οι παραγγελίες πληρώνονται από αυτό το πορτοφόλι.
  3. 3.GET /v1/video-categories: τα slugs των κατηγοριών.
  4. 4.GET /v1/videos?category=slug: κάθε βίντεο έχει poster_url, video_url (HLS), duration_seconds, category_slugs και product_id. product_id null σημαίνει μόνο παράδειγμα. Το GET /v1/videos/{id} δίνει ένα βίντεο.
  5. 5.GET /v1/products/{product_id} (δουλεύει και με το slug): prices (length_seconds, price_cents), duration_prices, aspect_ratios και has_storyboard. Το GET /v1/products φιλτράρει και με category, tag, q, aspect και length_seconds.
  6. 6.GET /v1/products/{id}/intake-form: τα πεδία που απαντάτε, ανά key. Παραλείψτε τα πεδία που κρύβει το show_if και όσα έχουν api_settable false.
  7. 7.POST /v1/uploads για κάθε αρχείο, μετά PUT τα bytes στο url με ακριβώς τα headers που επιστράφηκαν (το υπογεγραμμένο content-type) μέσα σε 10 λεπτά. Κρατήστε το upload_id.
  8. 8.Δείξτε την τιμή και πάρτε ένα ναι, μετά POST /v1/orders με customer_id, product_id, length_seconds, answers και uploads. 402 insufficient_credit: καμία χρέωση.
  9. 9.Παρακολουθήστε την παραγγελία: GET /v1/orders/{id} κάθε 30 με 60 δευτερόλεπτα, ή καταχωρήστε webhook για events order.*.
  10. 10.Μόνο όταν has_storyboard είναι true και το status είναι awaiting_approval: GET /v1/orders/{id}/storyboard, μετά έγκριση με version_id ή αιτήματα αλλαγών.
  11. 11.Με delivered: το GET /v1/orders/{id}/video δίνει link που ισχύει 10 λεπτά. Κατεβάστε το MP4 μία φορά και φιλοξενήστε το εσείς· για νέο link ζητήστε ξανά.

Posters και δείγματα βίντεο

Τα poster_url, video_url, example_poster_url και example_video_url είναι στο media CDN μας, που απαντά 403 σε αίτημα χωρίς Referer header. Σε browser μη χρησιμοποιείτε Referrer-Policy no-referrer (δουλεύει το strict-origin ή η προεπιλογή). Ένας server που κατεβάζει media πρέπει να στέλνει Referer, οποιοδήποτε https origin, π.χ. το δικό σας site. Το hotlinking στο CDN μας δεν υποστηρίζεται για sites σε παραγωγή: κατεβάστε το poster και το βίντεο μία φορά και σερβίρετέ τα από δικό σας storage ή CDN. Το link του έτοιμου βίντεο (GET /v1/orders/{id}/video) δεν χρειάζεται Referer αλλά λήγει στο expires_at.

Για AI agents και εργαλεία MCP

Σύντομος και ακριβής οδηγός για agents: η σειρά των κλήσεων, τα headers που απαιτούνται, οι κανόνες χρημάτων (πάντα δείχνετε την τιμή και παίρνετε επιβεβαίωση από τον άνθρωπο πριν το POST /v1/orders), πώς συμπληρώνονται οι φόρμες από το schema, κάθε πότε γίνεται polling και ο κανόνας Referer για τα media. Το OpenAPI είναι δημόσιο στο /api/v1/openapi.json.

Άνοιγμα /llms.txt

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. Κάθε απάντηση έχει 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). Παραγγελίες και usage έρχονται νεότερα πρώτα· τα βίντεο με τη σειρά του καταλόγου. Με φίλτρα μια σελίδα μπορεί να έχει λιγότερα στοιχεία ενώ has_more είναι true.

Idempotency (ασφαλή retries)

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

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

Σφάλματα

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

  • 400 invalid_input: διορθώστε τα πεδία της λίστας errors (τα paths των απαντήσεων ξεκινούν με answers.)
  • 400 unknown_category, invalid_aspect, invalid_length, invalid_cursor, invalid_limit: διορθώστε την query παράμετρο
  • 401 unauthenticated: λάθος, ληγμένο ή ανακλημένο API key
  • 402 insufficient_credit: δεν φτάνει το υπόλοιπο, δεν έγινε καμία χρέωση· φορτίστε και ξαναδοκιμάστε με νέο Idempotency-Key
  • 403 forbidden, pinned_customer, prices_not_available: το key δεν έχει το scope, στείλατε άλλο customer_id, ή ο λογαριασμός σας δεν μπορεί ακόμη να παραγγείλει
  • 404 not_found: άγνωστο id ή id άλλου λογαριασμού (ίδια απάντηση, χωρίς διαρροή πληροφορίας)· storyboard_not_ready: περιμένετε και ξαναρωτήστε
  • 409 idempotency_in_flight: περιμένετε και ξαναδοκιμάστε με το ίδιο key· not_delivered, not_awaiting_approval, version_not_current, cancellation_pending, not_cancellable: διαβάστε πρώτα την παραγγελία
  • 422 idempotency_mismatch: χρησιμοποιήστε νέο key· *_not_offered: παραγγείλτε μόνο ό,τι προσφέρει το προϊόν
  • 429: πάρα πολλές κλήσεις, ξαναδοκιμάστε με backoff
  • 5xx και 503 offer_unavailable: πρόβλημα από τη δική μας πλευρά· retry με το ίδιο Idempotency-Key είναι ασφαλές

Rate limits

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

Webhooks για τις παραγγελίες σας

Αντί να ρωτάτε συνέχεια το GET /v1/orders, καταχωρήστε ένα webhook και θα σας ειδοποιούμε όταν αλλάζει μια παραγγελία σας: order.created, order.paid, order.production_started, order.storyboard_ready, order.awaiting_approval, order.changes_requested, order.delivered, order.failed, order.cancelled. Λαμβάνετε μόνο events για τις δικές σας παραγγελίες, ποτέ για άλλους λογαριασμούς. Το σώμα περιέχει μόνο ids και status: διαβάστε τις λεπτομέρειες με GET /v1/orders/order_id.

Κάθε αίτημα ακολουθεί το Standard Webhooks: headers webhook-id, webhook-timestamp και webhook-signature, υπογεγραμμένα με το secret του endpoint. Ελέγξτε την υπογραφή πριν εμπιστευτείτε ένα event. Αν το endpoint δεν απαντά 2xx, ξαναδοκιμάζουμε για περίπου 27 ώρες· μετά από 5 ημέρες συνεχών αποτυχιών το απενεργοποιούμε.

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/video-categories

Οι κατηγορίες του καταλόγου βίντεο, με slug, όνομα στα αγγλικά και στα ελληνικά και πόσα βίντεο έχει η καθεμία.

response

  • data: [{ slug, name, name_el, video_count }]
curl -H "Authorization: Bearer $KEY" https://mystage.film/api/v1/video-categories
GET/videos

Τα βίντεο του καταλόγου, όλα ή μίας κατηγορίας. Κάθε στοιχείο έχει poster_url, video_url (HLS), duration_seconds, categories (ονόματα), category_slugs και product_id: το προϊόν που παραγγέλνει ένα τέτοιο βίντεο, ή null αν είναι μόνο παράδειγμα. Φίλτρα: category, tag, q, aspect. Τα media θέλουν Referer header.

body / query

  • category string?: a category slug
  • tag string?
  • q string?: text search
  • aspect string?: landscape | portrait | square
  • cursor string?
  • limit int?: 1..100

response

  • data: [{ id, title, title_el, subtitle, subtitle_el, categories, category_slugs, subcategory, hashtags, tags, aspect, duration, duration_seconds, video_url, poster_url, product_id, product_slug }]
  • product_id: null = an example only, not orderable
  • has_more, next_cursor
curl -H "Authorization: Bearer $KEY" "https://mystage.film/api/v1/videos?category=restaurants&limit=20"
GET/videos/{videoId}

Ένα βίντεο του καταλόγου, ίδιο σχήμα με στοιχείο της λίστας. 404 not_found για άγνωστο id.

response

  • ShowcaseVideo: the same shape as a list item
  • 404 not_found
GET/model-library

Έτοιμα AI μοντέλα για να απαντήσετε σε πεδίο model-picker (το id του μοντέλου) ή σε πεδίο person-spec σε library mode. Φίλτρο: gender.

body / query

  • gender string?: female | male

response

  • data: [{ id, number, collection, gender, attributes, thumb_url }]
GET/model-library/{modelId}

Ένα μοντέλο της βιβλιοθήκης με τις εικόνες του από μπροστά, πίσω, πλάι και πορτρέτο.

response

  • Model with signed front, back, side and portrait images
GET/products

Ο κατάλογός σας: τίτλος (EN, EL), υπότιτλος, τιμές ανά διάρκεια, duration_prices, aspect_ratios, has_storyboard, κατηγορίες, tags, ένα βίντεο και poster παράδειγμα. Φίλτρα: category, tag, q, aspect, length_seconds. Cursor pagination.

body / query

  • category string?: a category slug
  • tag string?
  • q string?: text search
  • aspect string?: landscape | portrait | square (or 16:9, 9:16, 1:1)
  • length_seconds int?
  • cursor string?
  • limit int?: 1..100

response

  • data: [{ id, slug, title { en, el }, subtitle { en, el }, description, native_aspect, aspect, aspect_ratios, resolution_choice, duration_options, video_seconds, prices: [{ length_seconds, price_cents, price_1080_cents, subtitles_price_cents, revision_price_cents }], duration_prices, has_storyboard, categories, subcategories, tags, hashtags, example_video_url, example_poster_url, example_duration_seconds, currency, created_at }]
  • has_more, next_cursor
curl -H "Authorization: Bearer $KEY" "https://mystage.film/api/v1/products?category=restaurants"
GET/products/{productId}

Ένα προϊόν με id ή slug, με όλες τις λεπτομέρειες. has_storyboard true σημαίνει ότι η παραγγελία σταματά μία φορά για storyboard που εγκρίνει ο πελάτης· false σημαίνει ότι πηγαίνει κατευθείαν στο τελικό βίντεο.

body / query

  • productId: the product id or its slug

response

  • Product: one product, all details
  • has_storyboard: true = a storyboard to approve, false = straight to the video
GET/products/{productId}/intake-form

Τα πεδία που ζητά το προϊόν: τύπος, ετικέτα, υποχρεωτικότητα, επιλογές, show_if, θέσεις για αρχεία, model_library και api_settable. Χτίστε τη φόρμα σας από εδώ· παραλείψτε κρυφά πεδία και πεδία με api_settable false (ισχύει η προεπιλογή).

response

  • product_id, languages, aspect_ratios
  • fields: [{ key, type, required, label, help, options, min, max, show_if, require_one_of, upload, groups, person_attributes, model_library, api_settable }]
POST/uploadsIdempotency-Key required

Ζητά presigned URL για ένα αρχείο της φόρμας. Σώμα: purpose "intake", content_type (image/jpeg, image/png, image/webp, audio/mpeg, audio/mp4), size_bytes (μέχρι 50 MB, ακριβές). PUT στο url με ακριβώς τα headers που επιστράφηκαν πριν λήξει (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
  • method "PUT"
  • headers { content-type }: send exactly these
  • 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

curl -X PUT -H "content-type: image/jpeg" --data-binary @photo.jpg "$UPLOAD_URL"
POST/ordersIdempotency-Key required

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

body / query

  • customer_id uuid: your own customer id
  • product_id uuid: from GET /products or a video product_id
  • length_seconds int: one of the product prices length_seconds
  • 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
  • review enum?: required | default

response

  • 201 Order: status "received", total_cents, add_ons
  • 402 insufficient_credit: nothing created, nothing charged
  • 422 *_not_offered: a choice the product does not sell
curl -X POST -H "Authorization: Bearer $KEY" -H "Idempotency-Key: $IDEM" \
  -H "Content-Type: application/json" -d '{"customer_id":"…","product_id":"…","length_seconds":15,"answers":{},"uploads":[]}' \
  https://mystage.film/api/v1/orders
GET/orders

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

body / query

  • cursor string?
  • limit int?: 1..100
  • customer_id uuid?
  • 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, cancellation_requested.

response

  • Order: id, status, customer_id, product_id, length_seconds, aspect, add_ons, total_cents, currency, complimentary, cancellation_requested, created_at
POST/orders/{orderId}/cancelIdempotency-Key required

Καταχωρεί ΑΙΤΗΜΑ ακύρωσης για την ομάδα μας (201, status pending). Σώμα: reason (υποχρεωτικό, τουλάχιστον 10 χαρακτήρες). Η παραγωγή δεν σταματά και δεν γίνεται αυτόματη επιστροφή χρημάτων: η ομάδα μας αποφασίζει για το αίτημα και για κάθε επιστροφή. Στο μεταξύ η παραγγελία μπορεί να παραδοθεί κανονικά.

body / query

  • reason string: why you cancel, 10 to 1000 characters

response

  • 201 { order_id, status: "pending" }: a request for our staff; production is not stopped, nothing is refunded automatically
  • 409 cancellation_pending | not_cancellable
GET/orders/{orderId}/storyboard

Μόνο για προϊόντα με has_storyboard true: το τρέχον storyboard, το version_id, το status και οι σκηνές με εικόνες (links που ισχύουν 10 λεπτά). 404 storyboard_not_ready πριν είναι έτοιμο.

response

  • order_id, version_id, version_number, status, revisions_allowed
  • scenes: [{ scene_index, label, caption, image_url }]
  • images_expire_at (10 minutes)
  • 404 storyboard_not_ready
POST/orders/{orderId}/storyboard/approveIdempotency-Key required

Εγκρίνει το storyboard εκ μέρους του πελάτη· η παραγωγή συνεχίζει. Σώμα: version_id από το GET storyboard.

body / query

  • version_id uuid: from GET storyboard

response

  • { order_id, version_id, status: "approved" }: production continues
POST/orders/{orderId}/change-requestsIdempotency-Key required

Ζητά αλλαγές σε σκηνές. Σώμα: version_id και changes, λίστα με scene_index και comment (10 έως 2000 χαρακτήρες). Ένας γύρος μπορεί να χρεωθεί από το πορτοφόλι (402 revision_payment_failed)· τα βίντεο 15 δευτερολέπτων μόνο εγκρίνονται.

body / query

  • version_id uuid: from GET storyboard
  • changes array: [{ scene_index int, comment string (10..2000) }]

response

  • 201 { order_id, version_id, status: "changes_requested" }
  • 402 revision_payment_failed | 409 revisions_unavailable
GET/orders/{orderId}/video

Μόνο με delivered: download URL για το τελικό MP4 και expires_at (10 λεπτά). Μην αποθηκεύετε το link, ζητήστε ξανά. Κατεβάστε το αρχείο μία φορά και φιλοξενήστε το στον δικό σας server.

response

  • url: MP4 download link
  • expires_at (10 minutes): ask again for a fresh link
  • filename
  • 409 not_delivered | order_cancelled
POST/webhook-endpoints

Καταχωρεί ένα webhook για τις δικές σας παραγγελίες. Σώμα: url (δημόσιο https, θύρα 443), events (μόνο order.*), description (προαιρετικό). Η απάντηση έχει το signing secret, που φαίνεται μόνο αυτή τη φορά. Μέχρι 5 endpoints ανά λογαριασμό.

body / query

  • url string: public https, port 443
  • events string[]: order.created | order.paid | order.production_started | order.storyboard_ready | order.awaiting_approval | order.changes_requested | order.delivered | order.failed | order.cancelled
  • description string?

response

  • WebhookEndpoint + secret (whsec_…, shown only once)
curl -X POST -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
  -d '{"url":"https://example.com/hooks/in","events":["order.delivered","order.storyboard_ready"]}' \
  https://mystage.film/api/v1/webhook-endpoints
GET/webhook-endpoints

Τα webhook endpoints σας, νεότερα πρώτα. Το secret δεν εμφανίζεται ποτέ ξανά.

response

  • data: [{ id, url, description, events, status, disabled_reason, created_at, updated_at }]
GET/webhook-endpoints/{endpointId}

Ένα webhook endpoint σας.

response

  • WebhookEndpoint
PATCH/webhook-endpoints/{endpointId}

Αλλάζει url, events ή description, ή το απενεργοποιεί και το ενεργοποιεί ξανά (status active ή disabled).

body / query

  • url string?
  • events string[]?
  • description string?
  • status enum?: active | disabled

response

  • WebhookEndpoint
DELETE/webhook-endpoints/{endpointId}

Διαγράφει ένα endpoint που δεν έχει λάβει ποτέ αποστολή. Αν έχει ιστορικό αποστολών, απενεργοποιήστε το.

response

  • { id, deleted: true }
  • 409 endpoint_has_deliveries: disable it instead
POST/webhook-endpoints/{endpointId}/test

Στέλνει τώρα ένα υπογεγραμμένο δοκιμαστικό event webhook.ping και δείχνει αν το endpoint απάντησε 2xx.

response

  • { delivered, http_status, error_code }
POST/webhook-endpoints/{endpointId}/rotate-secret

Νέο signing secret, που φαίνεται μόνο σε αυτή την απάντηση. Τα επόμενα events υπογράφονται με αυτό.

response

  • WebhookEndpoint + new secret (shown only once)