Guides

    Mapping the API surface by route group

    A working map of Versely REST by route group: generate, status, social, workflows, analytics, and where each capability actually lives.

    Versely Team9 min read

    There is no public API reference site on versely.studio. The OpenAPI spec exists internally and is not served in production, so "go read /docs" is not a plan. What you can hold in your head is smaller and more useful: the REST surface is a set of mounted route groups under /api/v1, each group owns one kind of work, and a scoped key is allowed to call the prefixes it was minted for.

    Once you know which group a capability lives in, you stop hunting through agent tool names. This is a map of the groups you actually automate against, in the order a working client hits them.

    How a call is identified

    Base URL: https://api.versely.studio/api/v1.

    Two auth paths land on the same handlers. A Supabase JWT from the app, or a Versely API key in the Authorization header. Keys are vsk_ plus forty hex characters (160 bits), stored as a SHA-256 hash, with the raw value returned once at creation. You mint them at POST /auth/api-keys. Those management routes accept a JWT or an existing key; the raw secret is still shown only at creation. GET /auth/api-keys/scopes returns the catalog. DELETE /auth/api-keys/:keyId soft-revokes.

    Every POST, PUT, and PATCH body is run through content moderation on text fields before the handler sees it. If a prompt is rejected, that is the moderation layer, not a model failure.

    There is no customer-facing webhook. A generation returns a request_id. You poll GET /status/:requestId until the payload says generating, completed, or failed. Completed responses carry result_url, plus type and model when they are present. Failed jobs surface a reason, classified as transient or permanent. Retry the first. Do not retry the second as-is.

    The developer page is the front door. CLI versus MCP versus the API is the decision about whether you should be on this surface at all. The rest of this post assumes you should.

    Generate, then status

    /generate is the media producer. It is the group a generate-scoped key is for. All of these sit behind auth and a cost-sensitive limiter (30 requests per minute). Generation POSTs consume credits from the same balance as the app. Listing models is not a generation.

    Method Path Job
    POST /generate/image Text-to-image, image-to-image, edits. model may be a string or an array; one request can fan out across named models.
    POST /generate/video Text-to-video, image-to-video, frame-to-frame.
    POST /generate/lipsync Image plus audio in, talking clip out.
    POST /generate/audio Text-to-speech.
    POST /generate/story Multi-scene storyboard generation.
    POST /generate/background-removal Still cutout.
    POST /generate/image-upscale Image upscale.
    POST /generate/video-upscale Video upscale.
    POST /generate/expand-scene Expand a scene description.
    GET /generate/models Models this group will accept.
    GET /generate/models/background-removal Cutout model list.
    GET /generate/models/image-upscale Image-upscale model list.
    GET /generate/models/video-upscale Video-upscale model list.
    GET /generate/story-models Storyboard model list.
    GET /generate/provider-check Provider health check.

    GET /status/:requestId is the other half of every call in that table. It is a different group on purpose: generate starts work, status reads it. A client that only POSTs and never polls will look broken while the job is fine.

    Related producers live next door, under the same generate scope: /movie, /dubbing, /ltx-batch (including hook packs), /broll, /avatar, /suno, /audio. If the job is "make new media," it is in this family. If the job is "stitch media I already have," it is /features.

    The web equivalent of /generate/video is the AI video generator. Use the tool when a person is picking a model. Use the route when a worker is.

    Features, editor, media

    /features is post-production on URLs you already host. It sits under the read scope, which surprises people who expect "read" to mean "only GET." The group is named for the product surface it backs, not for HTTP verbs.

    Method Path Job
    POST /features/editor-render One EDL (clips, transitions, texts, music, voiceover, overlay, captions) in, one video out.
    POST /features/extract-frames Pull stills from a clip.
    POST /features/merge-videos Concatenate clips.
    POST /features/attach-audio Mux audio onto a video.
    POST /features/add-captions Burn captions in.
    POST /features/audio-isolation Isolate vocals.
    POST /features/colorize-photo Colorize a still.
    POST /features/generate-prompt Prompt helper.
    POST /features/generate-caption Caption helper.
    POST /features/preview-rendition Small feed-preview derivative of a master.

    editor-render is the one that needs a careful reading. Pass preview: true and you get a 480p pass at no credit charge, with a short per-user cooldown (five seconds; a cooldown hit returns retry_after_ms). Preview is also capped on clip count and on duration (180 seconds). The final export, preview omitted or false, is charged once regardless of how many clips are on the EDL. That is the only free path on the whole platform, and it is a preview, not a plan. Iterate against it, wait out the cooldown, then export once. The same pipeline is what the video editor runs in the app.

    /media is the library and /fonts is the caption font list; both are read. /editor-projects is saved editor documents. That prefix is not in the scope catalog, so scope matching does not apply to it — any authenticated key currently reaches it. Do not treat that as a designed read grant. There is no cursor-based pagination documented on the library reads; do not invent one. If you need a file the generate group will accept, it has to be an HTTPS URL. file:// is rejected.

    Social, analytics, workflows

    These three groups are how a finished asset leaves the building, how you see whether it worked, and how you repeat the recipe.

    /social splits across two scopes on purpose. Account connection (GET /social/auth-url, GET /social/accounts, POST /social/accounts/refresh, DELETE /social/accounts/:accountId) is manage_accounts. Publishing (POST /social/posts, POST /social/preview, GET /social/posts, GET /social/posts/:postId, DELETE /social/posts/:postId) is post. A posting key should not be allowed to connect or disconnect accounts. Posting is 10 requests per minute per user; account refresh is 5. GET /social/posts takes limit and offset, default 20.

    /social-analytics is analytics. Four routes: GET /social-analytics/overview, GET /social-analytics/:postId, POST /social-analytics/:postId/collect, GET /social-analytics/:postId/history. Live pulls cover TikTok, Instagram, YouTube, X, Facebook, and LinkedIn. Adjacent groups in the same scope: /trend-analysis and /trending-feed.

    /workflows and /video-workflows are workflows. Saved recipes live under /workflows (GET /, GET /:id, POST /:id/run, PATCH /:id, mode and schedule patches, assets, duplicate, export, delete). Run documents and scene-level retry live under /video-workflows (GET /runs, GET /runs/:id, POST /runs/:id/cancel, POST /runs/:id/combine, POST /runs/:id/scenes/:order/retry). Public templates are /public-workflows. /templates is the one-tap template runner. The app-side version of this group is the workflows library and the agent workflow tools.

    /slideshow and /ugc (plus /captions) are their own scopes. If the job is a carousel or a UGC overlay, do not stuff it through /generate/video just because you already have a generate key.

    /agentic is the chat runtime: /chat, /chat/stream, /chat/answer-now, /quick-generate, conversations, tasks, plan execute/reject, brand-kit, memories. It is a read prefix. It is also 60 requests per minute per user. Use it when a person or an MCP client is driving. Do not use it as a hidden generate endpoint for a worker; the worker belongs on /generate.

    /billing is the credit ledger the pricing page describes on the human side. API access draws on the same balance as the app. There is no API-only plan and no free API allowance.

    What this map is not

    A few things people bring from other vendors that are not route groups here:

    • Outbound webhooks. /webhook/* is inbound from providers. You poll /status.
    • A published SDK. @versely/cli installs and authenticates. It is not a client library, and there is no versely generate.
    • A general queue API. /ltx-batch/queue/stats is LTX-batch-specific.
    • Cursor pagination on library reads. List endpoints that take limit and offset say so. The others do not.
    • Per-key spend caps. Keys can store a credit_limit field. It is not enforced. Cap spend in your dispatcher if you need a ceiling.

    Admin, gamification, email, referrals, and inbound webhooks are not part of a content-worker client. Hold the groups; they are what the key already matches on.

    FAQ

    Where do I look up which models a generate call will accept?

    GET /api/v1/generate/models, plus the specialist lists for background removal, image upscale, video upscale, and story. /ai-models is the broader registry under read. Do not scrape the website catalog and hope the slugs match; ask the group you are about to POST to.

    Is /features/editor-render a generate call?

    No. It composites URLs you already have. The 480p preview pass with preview: true is free and carries a short per-user cooldown of five seconds. The final export is a single credit charge. New clips still come from /generate (or /movie, /ugc, and the rest of the producer family).

    Why is /agentic not under the generate scope?

    Because it is the chat runtime, not the media producer. A worker that only needs images should never see /agentic. A chat integration that needs to talk to the agent should be granted read, and should still not be the thing that publishes. Split those jobs across keys the same way the prefixes are split.

    Can I register a URL and get a POST when a job finishes?

    No. Poll GET /status/:requestId. That is the completion surface. Designing a client around a webhook you supply will not work, because that callback does not exist.