tovid.app

tovid API

Post videos to YouTube, TikTok, Facebook, Instagram, Threads, LinkedIn, Telegram and Discord from your own code: a video tovid makes from a link, an idea or a text, or your own MP4. REST, JSON, one API key.

Working with an AI assistant? Give it the Markdown version of this page:tovid.app/api/docs.mdUpdated: 2026-11-12

The tovid API does what the dashboard does: you send a link (an article, a product page), an idea or a text; tovid reads it, writes the script, records the narration and renders an MP4. You then download the file or post it to the channels connected to your account.

You can also skip the making: send your own MP4 (a link to the file, or an upload) and tovid posts it as it is. One call publishes a video to several channels at once, right away, at a time you choose, or in the next free posting time of the project; every post reports its status and the link of the published post.

Everything is asynchronous. Creating a video answers at once with an id; you poll that video until its status is ready (usually 2 to 6 minutes), then read the download link or create posts. Posts are queued and go out in the background; you poll them the same way.

Quick start

Make a video from a link, wait for it, download it:

curl
# 1. Create the video (renders as soon as the script is ready)
curl -X POST https://tovid.app/api/v1/videos \
  -H "Authorization: Bearer $TOVID_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://example.com/blog/my-article"}'
# → 201 {"video": {"id": "cmh2x8k3v0001l5089qjz7w1a", "status": "processing", ...}}

# 2. Poll every 10-15 s until status is "ready" (or "failed")
curl https://tovid.app/api/v1/videos/cmh2x8k3v0001l5089qjz7w1a \
  -H "Authorization: Bearer $TOVID_API_KEY"
# → 200 {"video": {"status": "ready", "renders": [{"downloadUrl": "https://…", ...}], ...}}

# 3. Download the MP4, or post it to the project's channels
curl -X POST https://tovid.app/api/v1/posts \
  -H "Authorization: Bearer $TOVID_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"videoId": "cmh2x8k3v0001l5089qjz7w1a", "when": "now"}'
# → 201 {"posts": [{"id": "cmh2y3tu0007l508y3z4a5b6", "platform": "youtube", "status": "pending", ...}]}

Authentication

Create a key in the dashboard under Account → API (https://tovid.app/dashboard/api). The API is included in the Growth, Scale and Pro plans. A key acts as the account owner on every project of the account, so keep it on your server and never ship it in a browser or an app.

Send the key in the Authorization header of every request. The full key is shown once, when it is created; deleting a key in the dashboard revokes it immediately. An account can hold 5 keys.

HTTP
Authorization: Bearer tovid_sk_your_key_here

Requests and answers

Base URL: https://tovid.app/api/v1. Request bodies are JSON (Content-Type: application/json) and so are all answers. Ids are opaque strings. Times are ISO 8601 in UTC.

A failed request answers with a 4xx or 5xx status and a body of the form { "error": "CODE", "message": "what went wrong" }. Branch on the code; the message is for people. The codes are listed under Errors.

Rate limits per account: 120 requests a minute overall, 10 new videos a minute, 10 renders a minute, 20 calls that create posts a minute, 30 uploaded videos an hour. Over the limit the API answers 429 RATE_LIMITED with a Retry-After header. When polling a video, every 10 to 15 seconds is plenty.

Credits

The API spends the account's credits at the same prices as the dashboard. A render costs 25,000 credits for 60s, 31,500 credits for 90s, 37,500 credits for 120s (a script that runs much longer than the length asked is billed at the next length). The credits are held when the render starts and returned if it fails. Writing a script without rendering is free. Adding your own video is free. Each post to a channel costs 500 credits, taken when the post goes out and returned if it fails.

Creating a video is refused with 402 INSUFFICIENT_CREDITS when the balance cannot cover its render. GET /account returns the balance.

Life of a video

status is one of: processing (the script is being written, or a render is running or about to start), draft (the script is ready and no render was asked: you created it with render: false), ready (the newest render has an MP4), failed (see error).

While the script is written, stage says how far it is: research, read, media, brand, screenshot, script, finish. A render starts within a minute of the script being ready, and its progress goes from 0 to 100.

Finished MP4s are kept for 3 days (renders[].expiresAt). downloadUrl is a signed link valid until then; fetch the video again for a fresh one. A video made through the API also appears in the dashboard, where its script and look can be edited in the studio and rendered again.

Posting to social channels

Channels (a YouTube channel, a TikTok account, a Facebook Page, an Instagram account, a Threads or LinkedIn profile, a Telegram channel, a Discord channel) are connected once in the dashboard under Connections, where each platform asks for its own sign-in; the API cannot connect them. GET /channels lists them with the ids you post to. A project holds a default set of channels, used when a request names none.

POST /posts takes a video that is ready and creates one post per channel. when says the time: "now", "at" a scheduledAt you give, or "slot": the project's next free posting time (set in the project's settings), which fills a calendar without computing times yourself.

A post goes through: pending (queued for now) or scheduled, then processing (being sent to the platform), then posted (url is the link of the published post) or failed (error says why; the credits are returned). canceled is a post you canceled before it went out. Most posts finish within a minute or two of their time; TikTok confirms later and can stay processing for a while.

The video file must still exist when the post goes out: schedule within 3 days of the render for a video tovid made, within 7 days for your own video (MEDIA_EXPIRES_FIRST otherwise). The title is the YouTube title and the first line elsewhere; a link (see link) goes in the first comment on Facebook and Threads and at the end of the caption on the other platforms.

Post your own video

Your own MP4 or MOV (H.264 or HEVC, 3 seconds to 10 minutes) is posted as it is: no script, no render, no credits for adding it. The file is kept 7 days. Give a direct link to the file (up to 100 MB), or upload it (up to 200 MB) with POST /uploads.

curl
# 1. Hand tovid the file by link (or upload it first: see Create an upload)
curl -X POST https://tovid.app/api/v1/videos/upload \
  -H "Authorization: Bearer $TOVID_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"videoUrl": "https://cdn.example.com/clips/store-opening.mp4", "title": "Our new store is open", "caption": "Come say hi this weekend."}'
# → 201 {"video": {"id": "cmh3a1b2c0009l508d4e5f6g7", "status": "ready", ...}}

# 2. Post it to two channels tomorrow morning
curl -X POST https://tovid.app/api/v1/posts \
  -H "Authorization: Bearer $TOVID_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"videoId": "cmh3a1b2c0009l508d4e5f6g7", "channelIds": ["cmg9u4d5e0002l408k1m2n3p4", "cmg9u7f8g0004l408q5r6s7t8"], "when": "at", "scheduledAt": "2026-11-13T09:00:00+07:00"}'
# → 201 {"posts": [{"id": "cmh2y3tu0007l508y3z4a5b6", "status": "scheduled", ...}, ...]}

# 3. Read the result of a post
curl https://tovid.app/api/v1/posts/cmh2y3tu0007l508y3z4a5b6 \
  -H "Authorization: Bearer $TOVID_API_KEY"
# → 200 {"post": {"status": "posted", "url": "https://www.youtube.com/watch?v=abc123", ...}}

Endpoints

Get the account

GEThttps://tovid.app/api/v1/account

The plan and credit balance of the account the key belongs to. A cheap call to check that a key works.

Request
curl https://tovid.app/api/v1/account \
  -H "Authorization: Bearer $TOVID_API_KEY"
Response · 200 OK
{
  "plan": "growth",
  "planExpiresAt": "2026-12-01T00:00:00.000Z",
  "credits": 2140500,
  "limits": { "maxVideoSeconds": 120, "concurrentRenders": 3 }
}

Response fields

FieldTypeDescription
creditsnumberCredits left.
limits.concurrentRendersnumberRenders the account may run at once; further videos wait their turn.

List projects

GEThttps://tovid.app/api/v1/projects

Every video belongs to a project, which holds the brand, the default language, ratio, length and voice, the channels to post to and the posting times. Projects are created and set up in the dashboard; the API reads them.

Request
curl https://tovid.app/api/v1/projects \
  -H "Authorization: Bearer $TOVID_API_KEY"
Response · 200 OK
{
  "projects": [
    {
      "id": "cmg9t1b2c0000l408h3x9y7zq",
      "name": "My project",
      "language": "en",
      "ratio": "9:16",
      "length": "60s",
      "timeZone": "America/New_York",
      "postingTimes": ["09:00", "18:30"],
      "channels": [
        { "id": "cmg9u4d5e0002l408k1m2n3p4", "platform": "youtube", "name": "My channel" },
        { "id": "cmg9u7f8g0004l408q5r6s7t8", "platform": "facebook", "name": "My page" }
      ]
    }
  ]
}

Response fields

FieldTypeDescription
language, ratio, lengthstringWhat a new video of the project gets when the request does not say.
postingTimesstring[]The project's posting times (HH:MM in timeZone), used by publish with when: "slot".
channelsobject[]The connected channels the project posts to: the default of Create posts. GET /channels lists every channel of the account.

List channels

GEThttps://tovid.app/api/v1/channels

The social channels connected to the account. Their ids are what Create posts takes as channelIds; a channel does not have to belong to the video's project to be posted to.

Channels are connected (and disconnected) in the dashboard under Connections. When a post fails because a sign-in expired or was revoked, lastError carries the platform's answer; connect the channel again in the dashboard.

Request
curl https://tovid.app/api/v1/channels \
  -H "Authorization: Bearer $TOVID_API_KEY"
Response · 200 OK
{
  "channels": [
    {
      "id": "cmg9u4d5e0002l408k1m2n3p4",
      "platform": "youtube",
      "name": "My channel",
      "username": "mychannel",
      "projectIds": ["cmg9t1b2c0000l408h3x9y7zq"],
      "totalPosts": 42,
      "lastPostedAt": "2026-11-11T11:30:12.000Z",
      "lastError": null
    }
  ]
}

Response fields

FieldTypeDescription
platformstringyoutube | tiktok | facebook | instagram | threads | linkedin | telegram | discord.
projectIdsstring[]The projects that post to this channel by default.
lastErrorstring | nullWhat the platform answered when the last post failed (or a warning about the last post); null when all is well.

Create a video

POSThttps://tovid.app/api/v1/videos

Starts a video from exactly one of url, idea or text. The answer comes back at once with status processing; the script is written in the background and, unless render is false, the video is rendered as soon as the script is ready.

url: a public page tovid can read, such as a news or blog article, or a product page (Shopify, WooCommerce, Tiki, Shopee, Lazada, App Store, Google Play...). Links to videos or posts on social networks are refused (SOCIAL_VIDEO_LINK). A page that cannot be read fails the video later, with error SOURCE_UNREADABLE or SHOP_UNREADABLE.

idea: a question or topic; tovid researches it on the web first. text: your own content, written into scenes without research.

JSON body

FieldTypeDescription
urlstringhttp(s) link of the page to make the video from. One of url / idea / text is required.
ideastringA topic or question, 3 to 200 characters.
notesstringWith idea: what to stress or where to look, up to 300 characters.
textstringYour own text, 40 to 20,000 characters. Its first line is the working title.
projectIdstringThe project the video belongs to. Default: the account's first project.
languagestringLanguage of the script and voice as a two-letter code ("en", "vi", "es"...). Default: the project's.
ratiostring"9:16" | "16:9" | "1:1". Default: the project's.
lengthstring"60s" | "90s" | "120s". Default: the project's.
templateIdstringA look, by the id in its URL on https://tovid.app/templates (e.g. "news_breaking"). Default: one of the project's looks, else picked to suit the content.
affiliateUrlstringA link to carry in the posts of this video. Default: the project's affiliate link.
renderbooleanDefault true. false stops at the script (status draft, no credits): render it later with POST /videos/{id}/render, or edit it in the dashboard first.
Request
curl -X POST https://tovid.app/api/v1/videos \
  -H "Authorization: Bearer $TOVID_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://example.com/blog/my-article",
    "language": "en",
    "ratio": "9:16",
    "length": "60s"
  }'
Response · 201 Created
{
  "video": {
    "id": "cmh2x8k3v0001l5089qjz7w1a",
    "projectId": "cmg9t1b2c0000l408h3x9y7zq",
    "title": "example.com",
    "status": "processing",
    "stage": "read",
    "error": null,
    "sourceUrl": "https://example.com/blog/my-article",
    "language": "en",
    "length": "60s",
    "createdAt": "2026-11-12T08:30:00.000Z",
    "renders": []
  }
}

Add your own video

POSThttps://tovid.app/api/v1/videos/upload

Saves your own MP4 or MOV as a video that is ready at once, to post as it is. Give exactly one of videoUrl (a direct public link to the file, up to 100 MB; it is downloaded during the call, which can take up to a minute) or uploadId (a file you uploaded with Create an upload, up to 200 MB).

The file must be H.264 or HEVC and last 3 seconds to 10 minutes. It is kept 7 days (renders[0].expiresAt). No credits. The ratio is read from the file. A page of a social network or a video site is not a file: pass the address of the MP4 itself.

JSON body

FieldTypeDescription
videoUrlstringDirect http(s) link to the MP4 / MOV, up to 100 MB. One of videoUrl / uploadId is required.
uploadIdstringThe uploadId of Create an upload, after the file was PUT to its uploadUrl.
titlestringRequired. Name of the video and default title of its posts, up to 500 characters.
captionstringDefault caption of its posts, up to 5,000 characters.
projectIdstringThe project the video belongs to. Default: the account's first project.
coverSecnumberSecond of the frame used as cover (YouTube thumbnail, TikTok and Instagram cover). Default: 3, or the middle of a shorter video.
Request
curl -X POST https://tovid.app/api/v1/videos/upload \
  -H "Authorization: Bearer $TOVID_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "videoUrl": "https://cdn.example.com/clips/store-opening.mp4",
    "title": "Our new store is open",
    "caption": "Come say hi this weekend."
  }'
Response · 201 Created
{
  "video": {
    "id": "cmh3a1b2c0009l508d4e5f6g7",
    "projectId": "cmg9t1b2c0000l408h3x9y7zq",
    "title": "Our new store is open",
    "status": "ready",
    "stage": null,
    "error": null,
    "sourceUrl": null,
    "language": "en",
    "length": "42s",
    "createdAt": "2026-11-12T09:00:00.000Z",
    "renders": [
      {
        "id": "cmh3a1b2d000al508h8i9j0k1",
        "ratio": "9:16",
        "status": "completed",
        "progress": 100,
        "durationSec": 41.8,
        "error": null,
        "createdAt": "2026-11-12T09:00:00.000Z",
        "expiresAt": "2026-11-19T09:00:00.000Z",
        "downloadUrl": "https://…/video.mp4?X-Amz-Signature=…"
      }
    ]
  }
}

Create an upload

POSThttps://tovid.app/api/v1/uploads

For a file that is not on a public link, or larger than a link allows: returns an address to PUT the file to. Send the file as the raw body of a PUT to uploadUrl with the Content-Type you declared, within the hour, then pass uploadId to Add your own video.

JSON body

FieldTypeDescription
sizenumberRequired. Size of the file in bytes, up to 200 MB.
typestring"video/mp4" (default) | "video/quicktime".
Request
curl -X POST https://tovid.app/api/v1/uploads \
  -H "Authorization: Bearer $TOVID_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"size": 18734112, "type": "video/mp4"}'

# then send the file itself
curl -X PUT "<uploadUrl>" -H "Content-Type: video/mp4" --data-binary @clip.mp4
Response · 201 Created
{
  "uploadId": "7days/tovid/videos/…/3f9c2a7b1d4e5f6a7b8c9d0e.mp4",
  "uploadUrl": "https://…/3f9c2a7b1d4e5f6a7b8c9d0e.mp4?X-Amz-Signature=…",
  "method": "PUT",
  "headers": { "Content-Type": "video/mp4" },
  "expiresIn": 3600
}

Get a video

GEThttps://tovid.app/api/v1/videos/{id}

The video with its renders and posts. Poll this until status is ready or failed.

Request
curl https://tovid.app/api/v1/videos/cmh2x8k3v0001l5089qjz7w1a \
  -H "Authorization: Bearer $TOVID_API_KEY"
Response · 200 OK
{
  "video": {
    "id": "cmh2x8k3v0001l5089qjz7w1a",
    "projectId": "cmg9t1b2c0000l408h3x9y7zq",
    "title": "5 things to know about my article",
    "status": "ready",
    "stage": null,
    "error": null,
    "sourceUrl": "https://example.com/blog/my-article",
    "language": "en",
    "length": "60s",
    "createdAt": "2026-11-12T08:30:00.000Z",
    "renders": [
      {
        "id": "cmh2xb7pd0003l508m2n4k6s8",
        "ratio": "9:16",
        "status": "completed",
        "progress": 100,
        "durationSec": 58.4,
        "error": null,
        "createdAt": "2026-11-12T08:31:10.000Z",
        "expiresAt": "2026-11-15T08:36:02.000Z",
        "downloadUrl": "https://…/video.mp4?X-Amz-Signature=…"
      }
    ],
    "posts": []
  }
}

Response fields

FieldTypeDescription
statusstringprocessing | draft | ready | failed (see Life of a video).
stagestring | nullStep of the script while it is written, else null.
errorstring | nullA code when the script or the newest render failed (SOURCE_UNREADABLE, EXTRACT_FAILED, INSUFFICIENT_CREDITS...).
titlestringThe site's host name at first, the video's headline once the script is written.
rendersobject[]Newest first (up to 5). status: queued | voicing | rendering | completed | failed.
renders[].downloadUrlstring | nullSigned MP4 link for a completed render, valid until expiresAt; null otherwise.
postsobject[]Posts of this video that were not canceled, in the shape of Get a post.

List videos

GEThttps://tovid.app/api/v1/videos

The account's videos, newest first. Same shape as Get a video, without downloadUrl and posts.

Query parameters

FieldTypeDescription
projectIdstringOnly the videos of this project.
limitnumber1 to 100, default 20.
Request
curl "https://tovid.app/api/v1/videos?limit=10" \
  -H "Authorization: Bearer $TOVID_API_KEY"
Response · 200 OK
{
  "videos": [
    { "id": "cmh2x8k3v0001l5089qjz7w1a", "status": "ready", "title": "5 things to know about my article", "renders": [ … ], … }
  ]
}

Render a video

POSThttps://tovid.app/api/v1/videos/{id}/render

Renders a video whose script is ready: a draft made with render: false, or a finished video again in another ratio (or after editing it in the dashboard). Holds the credits of the video's length. Not needed after a plain Create a video, which renders by itself.

Answers 409 NOT_READY while the script is still being written and 429 TOO_MANY_RENDERS when the account is at its limit of renders running at once.

JSON body

FieldTypeDescription
ratiostring"9:16" | "16:9" | "1:1". Default: the video's ratio.
Request
curl -X POST https://tovid.app/api/v1/videos/cmh2x8k3v0001l5089qjz7w1a/render \
  -H "Authorization: Bearer $TOVID_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"ratio": "16:9"}'
Response · 201 Created
{ "renderId": "cmh2xq1rs0005l508u9v0w1x2", "ratio": "16:9", "credits": 25000 }

Create posts

POSThttps://tovid.app/api/v1/posts

Posts a video to social channels, one post per channel: right away, at a time you give, or in the project's next free posting time. The video must be ready (409 RENDER_NOT_READY otherwise). With only videoId, it is posted now to all the channels of its project.

Text you leave out is filled in: for a video tovid made, the title, caption and hashtags are written from its script; for your own video, its title and caption are used.

Each channel costs 500 credits when its post goes out, and the balance must cover all the posts of the call (402 INSUFFICIENT_CREDITS otherwise). TikTok channels use the TikTok settings saved in the project (privacy level and so on).

The answer returns the posts as they were queued (pending or scheduled). Poll Get a post, or posts on Get a video, for the result on each channel.

JSON body

FieldTypeDescription
videoIdstringRequired. A video whose status is ready.
channelIdsstring[]Channels to post to (ids from List channels), up to 10. Default: the channels of the video's project.
whenstring"now" (default) | "at" (at scheduledAt) | "slot" (the project's next free posting time).
scheduledAtstringWith when: "at": ISO 8601 time with an offset, at least a minute ahead (e.g. "2026-11-13T09:00:00+07:00").
titlestringTitle of the posts (YouTube title, first line elsewhere), up to 500 characters.
captionstringText of the posts.
hashtagsstring[]Up to 30, with or without #.
privacystring"public" (default) | "unlisted" | "private", on platforms that have it (YouTube).
linkstringThe link the posts carry: "project" (default: as the project's settings say) | "source" (the page the video was made from) | "affiliate" | "none". It goes in the first comment on Facebook and Threads, at the end of the caption elsewhere.
Request
curl -X POST https://tovid.app/api/v1/posts \
  -H "Authorization: Bearer $TOVID_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "videoId": "cmh2x8k3v0001l5089qjz7w1a",
    "channelIds": ["cmg9u4d5e0002l408k1m2n3p4"],
    "when": "at",
    "scheduledAt": "2026-11-13T09:00:00+07:00",
    "caption": "A short take on my article.",
    "hashtags": ["news", "howto"]
  }'
Response · 201 Created
{
  "posts": [
    {
      "id": "cmh2y3tu0007l508y3z4a5b6",
      "videoId": "cmh2x8k3v0001l5089qjz7w1a",
      "projectId": "cmg9t1b2c0000l408h3x9y7zq",
      "channelId": "cmg9u4d5e0002l408k1m2n3p4",
      "platform": "youtube",
      "channel": "My channel",
      "status": "scheduled",
      "title": "5 things to know about my article",
      "caption": "A short take on my article.",
      "hashtags": ["news", "howto"],
      "privacy": "public",
      "url": null,
      "scheduledAt": "2026-11-13T02:00:00.000Z",
      "postedAt": null,
      "error": null,
      "createdAt": "2026-11-12T08:40:55.000Z"
    }
  ],
  "scheduledAt": "2026-11-13T02:00:00.000Z"
}

Response fields

FieldTypeDescription
scheduledAtstring | nullWhen the posts go out: the time you gave, the slot that was taken, or null for now.

Get a post

GEThttps://tovid.app/api/v1/posts/{id}

One post: its status on its channel and, once it is up, the link of the published post.

Request
curl https://tovid.app/api/v1/posts/cmh2y3tu0007l508y3z4a5b6 \
  -H "Authorization: Bearer $TOVID_API_KEY"
Response · 200 OK
{
  "post": {
    "id": "cmh2y3tu0007l508y3z4a5b6",
    "videoId": "cmh2x8k3v0001l5089qjz7w1a",
    "projectId": "cmg9t1b2c0000l408h3x9y7zq",
    "channelId": "cmg9u4d5e0002l408k1m2n3p4",
    "platform": "youtube",
    "channel": "My channel",
    "status": "posted",
    "title": "5 things to know about my article",
    "caption": "A short take on my article.",
    "hashtags": ["news", "howto"],
    "privacy": "public",
    "url": "https://www.youtube.com/watch?v=abc123",
    "scheduledAt": "2026-11-13T02:00:00.000Z",
    "postedAt": "2026-11-13T02:00:41.000Z",
    "error": null,
    "createdAt": "2026-11-12T08:40:55.000Z"
  }
}

Response fields

FieldTypeDescription
statusstringpending | scheduled | processing | posted | failed | canceled (see Posting to social channels).
urlstring | nullLink of the published post once status is posted (a few platforms give none).
errorstring | nullWhy a failed post failed: the platform's own message, or a code such as INSUFFICIENT_CREDITS, CHANNEL_DISCONNECTED, INTERRUPTED.
captionstringThe text as it is posted, with the link appended on platforms that carry it in the caption.

List posts

GEThttps://tovid.app/api/v1/posts

The account's posts, newest first, in the shape of Get a post. Includes posts made in the dashboard and by content plans.

Query parameters

FieldTypeDescription
videoIdstringOnly the posts of this video.
projectIdstringOnly the posts of this project.
channelIdstringOnly the posts on this channel.
statusstringpending | scheduled | processing | posted | failed | canceled.
limitnumber1 to 100, default 20.
Request
curl "https://tovid.app/api/v1/posts?status=scheduled&limit=50" \
  -H "Authorization: Bearer $TOVID_API_KEY"
Response · 200 OK
{
  "posts": [
    { "id": "cmh2y3tu0007l508y3z4a5b6", "platform": "youtube", "status": "scheduled", "scheduledAt": "2026-11-13T02:00:00.000Z", … }
  ]
}

Cancel a post

DELETEhttps://tovid.app/api/v1/posts/{id}

Cancels a post that has not gone out yet (pending or scheduled); no credits are taken. A post that is processing, posted or failed answers 409 POST_NOT_CANCELABLE: tovid does not delete what is already on a platform.

Request
curl -X DELETE https://tovid.app/api/v1/posts/cmh2y3tu0007l508y3z4a5b6 \
  -H "Authorization: Bearer $TOVID_API_KEY"
Response · 200 OK
{ "ok": true }

Publish a video

POSThttps://tovid.app/api/v1/videos/{id}/publish

A shortcut for Create posts with the video in the path: the same body without videoId, the same answer. An empty body posts now to all the channels of the video's project.

Request
curl -X POST https://tovid.app/api/v1/videos/cmh2x8k3v0001l5089qjz7w1a/publish \
  -H "Authorization: Bearer $TOVID_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"when": "slot"}'
Response · 201 Created
{ "posts": [ … ], "scheduledAt": "2026-11-12T11:30:00.000Z" }

Delete a video

DELETEhttps://tovid.app/api/v1/videos/{id}

Deletes the video with its renders. Refused with 409 RENDERING while a render is running. Posts that already went out stay on their platforms.

Request
curl -X DELETE https://tovid.app/api/v1/videos/cmh2x8k3v0001l5089qjz7w1a \
  -H "Authorization: Bearer $TOVID_API_KEY"
Response · 200 OK
{ "ok": true }

Errors

Every failed request answers with its HTTP status and { "error": "CODE", "message": "…" }.

StatusCodeMeaning
400INVALID_INPUTThe body does not match the fields above (for Create a video: not exactly one of url, idea, text).
400INVALID_URLThe url is not a public http(s) address.
400SOCIAL_VIDEO_LINKThe url is a video or post on a social network, which tovid does not read.
400UPLOAD_VIDEO_TYPECreate an upload: the type is not video/mp4 or video/quicktime.
400UPLOAD_VIDEO_TOO_BIGOwn video: the file is over 200 MB (100 MB from a link).
400VIDEO_URL_UNREADABLEOwn video: the file could not be downloaded from videoUrl (not a direct link, an error status, or too slow).
400UPLOAD_VIDEO_UNREADABLEOwn video: the file is not a readable video.
400UPLOAD_VIDEO_CODECOwn video: the video is not H.264 or HEVC.
400UPLOAD_VIDEO_TOO_SHORTOwn video: shorter than 3 seconds.
400UPLOAD_VIDEO_TOO_LONGOwn video: longer than 10 minutes.
400NO_CHANNELSPosts: no channel given and the project has none (or the ids are not active channels of the account).
400SCHEDULE_IN_PASTPosts: scheduledAt is missing or less than a minute ahead.
400NO_SLOT_TIMESPosts with when: "slot": the project has no posting times.
400MEDIA_EXPIRES_FIRSTPosts: the MP4 is deleted before the post would go out.
400TIKTOK_PRIVACY_REQUIREDPosts to TikTok: the project has no TikTok settings yet (set them in the dashboard).
401UNAUTHORIZEDNo key, a malformed key, or a key that was deleted.
402INSUFFICIENT_CREDITSNot enough credits for the render or the posts.
403PLAN_REQUIREDThe account's plan does not include the API (Growth and up).
403PLAN_LIMITThe length asked is longer than the plan allows.
404NOT_FOUNDNo video or post with this id in the account.
404UPLOAD_NOT_FOUNDOwn video: no file was PUT for this uploadId.
404PROJECT_NOT_FOUNDNo project with this id in the account.
409NOT_READYRender: the script is still being written, or the video has none.
409RENDER_NOT_READYPosts: the video has no finished render.
409POST_NOT_CANCELABLECancel a post: the post is already processing, posted, failed or canceled.
409RENDERINGDelete: a render is running.
409UPLOADED_VIDEORender: the video is an uploaded MP4, which is posted as it is.
429RATE_LIMITEDToo many requests; wait for Retry-After seconds.
429TOO_MANY_RENDERSRender: the account is at its limit of renders running at once.
503TTS_NOT_CONFIGUREDNarration is unavailable; nothing was charged. Try again later.

Full example (Node.js)

Create a video, wait for the MP4, then post it in the project's next free posting time. Node 18 or later, no dependencies.

Node.js
const API = "https://tovid.app/api/v1";
const headers = {
  Authorization: `Bearer ${process.env.TOVID_API_KEY}`,
  "Content-Type": "application/json",
};

async function call(method, path, body) {
  const res = await fetch(API + path, { method, headers, body: body ? JSON.stringify(body) : undefined });
  const data = await res.json();
  if (!res.ok) throw new Error(`${data.error}: ${data.message}`);
  return data;
}

const { video } = await call("POST", "/videos", { url: "https://example.com/blog/my-article" });

let current = video;
while (current.status === "processing") {
  await new Promise((resolve) => setTimeout(resolve, 15_000));
  current = (await call("GET", `/videos/${video.id}`)).video;
}
if (current.status !== "ready") throw new Error(`Video failed: ${current.error}`);

console.log("MP4:", current.renders[0].downloadUrl);
const { posts } = await call("POST", "/posts", { videoId: video.id, when: "slot" });
for (const post of posts) console.log(post.platform, post.status, post.scheduledAt);