From zero to your first published video
7 steps: account, key, first request, your first video, media, scheduling and a webhook. The examples use curl; switch the tab to see the same step in another language.
Create your account
Sign in with Google at praeko.app and connect at least one channel (YouTube, Instagram or TikTok) under Channels.
Create an API key
In the panel, go to Developers, API keys. Pick the smallest scope that works: each level includes the one before.
| Scope | Use it to |
|---|---|
| read | Read channels, posts, metrics, comments and usage. |
| write | Everything in read, plus create and cancel posts, upload media and reply to comments. |
| admin | Everything in write, plus keys, webhooks, channel connections and imports. |
praeko_… key when you create it and keep it in an environment variable: export PRAEKO_KEY=…Make your first request
List your channels. If you get the list back, the key and the header are right.
curl "https://praeko.app/v1/channels" \
-H "Authorization: Bearer $PRAEKO_KEY" \
-H "X-Praeko-Contract: en"const res = await fetch("https://praeko.app/v1/channels", {
method: "GET",
headers: {
"Authorization": `Bearer ${process.env.PRAEKO_KEY}`,
"X-Praeko-Contract": "en",
},
});
console.log(res.status, await res.json());import { Praeko } from "@praeko/sdk";
const praeko = new Praeko({ url: "https://praeko.app", key: process.env.PRAEKO_KEY });
const resultado = await praeko.channels.list();
console.log(resultado);import os
import requests
resp = requests.get(
"https://praeko.app/v1/channels",
headers={
"Authorization": f"Bearer {os.environ['PRAEKO_KEY']}",
"X-Praeko-Contract": "en",
},
)
print(resp.status_code, resp.json())<?php
$ch = curl_init('https://praeko.app/v1/channels');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'GET',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
"Authorization: Bearer " . getenv("PRAEKO_KEY"),
'X-Praeko-Contract: en',
],
]);
$resposta = curl_exec($ch);
echo curl_getinfo($ch, CURLINFO_HTTP_CODE), " ", $resposta, PHP_EOL;
curl_close($ch);package main
import (
"fmt"
"io"
"net/http"
"os"
)
func main() {
req, err := http.NewRequest("GET", "https://praeko.app/v1/channels", nil)
if err != nil {
panic(err)
}
req.Header.Set("Authorization", "Bearer "+os.Getenv("PRAEKO_KEY"))
req.Header.Set("X-Praeko-Contract", "en")
resp, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
out, _ := io.ReadAll(resp.Body)
fmt.Println(resp.Status, string(out))
}require "net/http"
require "uri"
uri = URI("https://praeko.app/v1/channels")
request = Net::HTTP::Get.new(uri)
request["Authorization"] = "Bearer #{ENV['PRAEKO_KEY']}"
request["X-Praeko-Contract"] = "en"
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(request) }
puts response.code, response.bodyusing System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using var client = new HttpClient();
using var request = new HttpRequestMessage(HttpMethod.Get, "https://praeko.app/v1/channels");
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", Environment.GetEnvironmentVariable("PRAEKO_KEY"));
request.Headers.Add("X-Praeko-Contract", "en");
using var response = await client.SendAsync(request);
Console.WriteLine($"{(int)response.StatusCode} {await response.Content.ReadAsStringAsync()}");import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class Main {
public static void main(String[] args) throws Exception {
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://praeko.app/v1/channels"))
.header("Authorization", "Bearer " + System.getenv("PRAEKO_KEY"))
.header("X-Praeko-Contract", "en")
.method("GET", HttpRequest.BodyPublishers.noBody())
.build();
HttpResponse<String> response = HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.statusCode() + " " + response.body());
}
}http GET "https://praeko.app/v1/channels" \
"Authorization: Bearer $PRAEKO_KEY" \
"X-Praeko-Contract: en"{
"channels": [
{
"network": "youtube",
"supported": true,
"id": "01KA3V0R5D8M2WQ7X4HT9NZ1BF",
"connected": true,
"detail": "Canal do Marcone",
"name": "Canal do Marcone",
"timezone": "America/Sao_Paulo",
"slots": 3,
"avatar": "/v1/channels/youtube/avatar",
"default": true
},
{
"network": "instagram",
"supported": true,
"id": "01KA3V1G4C7N9PXS2WQ6HM8TDZ",
"connected": true,
"detail": "via API da Meta",
"timezone": "America/Sao_Paulo",
"slots": 0,
"meta": {
"connected": true,
"username": "dev.ocioso"
},
"name": "dev.ocioso"
},
{
"network": "tiktok",
"supported": true,
"connected": false,
"detail": "não conectado (faltam os secrets TIKTOK_CLIENT_KEY e TIKTOK_CLIENT_SECRET)"
}
]
}Your first video
Do not have the video yet? Make it with Praeko Studio, using your own coding agent (Claude Code, Codex, Gemini CLI, Cursor, opencode). Then publish with the paths below.
npm install -g @praeko/studio
praeko-studio doctor
praeko-studio init my-channel --agent claude
cd my-channel # open your agent here and ask for the videoFull guide, with the text to paste into your agent, licenses (Remotion is not MIT) and how to publish: Praeko Studio. Agent without a terminal (such as Claude Desktop)? Use the local Studio MCP: it makes the video and uploads the files, but publishing is done by the remote praeko MCP server.
I already have the video: publish with the SDK, the CLI or your agent (MCP).
You have a finished vertical video (MP4 or MOV, 9:16 for Shorts, Reels and TikTok) and want to see it published. There are three ways in and all three reach the same API. The example uses clipe.mp4, a 20 s clip (1080×1920, 18 MiB) that goes up as private on YouTube first, so you can check it before making it public.
PRAEKO_KEY.With the SDK, in TypeScript or JavaScript. The package ships its TypeScript types and has no dependencies.
npm install @praeko/sdk// publicar.mjs: node publicar.mjs
import { Praeko } from "@praeko/sdk";
import { uploadFile } from "@praeko/sdk/node"; // Node only: reads the file from disk in parts
const praeko = new Praeko({ url: "https://praeko.app", key: process.env.PRAEKO_KEY });
// 1) upload the video (up to 50 MiB goes in a single request; above that the SDK splits it into parts)
const video = await uploadFile(praeko, "./clipe.mp4", { type: "video" });
// 2) create the post: one item per network. On YouTube, mode "private" uploads now and keeps the video private
const post = await praeko.posts.create(
{
title: "How a cache brought down the deploy",
media: { video: video.id },
texts: { youtube: { title: "How a cache brought down the deploy", text: "20 seconds on the bug that only showed up after the deploy.\n\n#programming #shorts" } },
items: [{ network: "youtube", mode: "private" }],
},
{ idempotencyKey: "first-video-clipe-01" }, // running the script twice does not create a second post (the key lasts 24 h)
);
console.log(post.id, post.items[0].state);The same code works in .ts: run it with the TypeScript runner you already use. uploadFile returns the ready media; posts.create returns the post:
{
"id": "01KA3VQ7M2T9XE4HB8N5RZ6C0D",
"type": "video",
"name": "clipe.mp4",
"mime": "video/mp4",
"size": 18874368,
"state": "ready",
"publicUrl": "https://praeko.app/v1/m/01KA3VQ7M2T9XE4HB8N5RZ6C0D/k7Qm2vXz9Rt4Lp8Wc3Nd6Hy1Bs5Fa0J/clipe.mp4",
"durationMs": 20040,
"width": 1080,
"height": 1920,
"createdAt": "2026-11-07T14:02:11.482Z"
}{
"id": "01KA3W2Y6H1RC5ZQ8M0T4VXE7N",
"items": [
{
"id": "01KA3W2Y6J9D3BNF7G2K5PQS8A",
"network": "youtube",
"channel": "01KA3V0R5D8M2WQ7X4HT9NZ1BF",
"state": "scheduled",
"scheduledFor": null,
"custom": false,
"mode": "private",
"runAt": "2026-11-07T14:02:12.000Z"
}
]
}The item starts with state: "scheduled" and runAt set to now; Praeko then uploads the video to YouTube. When the item moves to private, the video is on YouTube, private, waiting for you. Check it with GET /v1/posts/:id and, when you are happy, schedule it with praeko.posts.scheduleYoutube(post.id, "2026-11-14T17:30:00Z") or make it public in YouTube Studio.
With the CLI. The CLI reads a package folder with the video, the cover and a publication.json, and checks the texts before sending. It also needs a praeko.json in the current folder (or above).
package/
clipe.mp4 # the video
cover.png # the cover (the name must contain capa, cover or thumb)
publication.json # title, the text for each network and hashtags{
"title": "How a cache brought down the deploy",
"format": "short",
"files": { "video": "clipe.mp4", "cover": "cover.png" },
"yt": { "description": "20 seconds on the bug that only showed up after the deploy." },
"ig": { "caption": "The bug that only showed up after the deploy." },
"tiktok": { "caption": "The bug that only showed up after the deploy." },
"hashtags": { "youtube": ["#programming", "#shorts"], "instagram": ["#programming"], "tiktok": ["#programming"] }
}{
"name": "My channel",
"links": { "main": "https://example.com" }
}In praeko.json, links.main is optional: it is the link the CLI appends, with UTM tags, to the end of the YouTube description. The text for each network comes from publication.json.
npm install -g @praeko/cli # or use npx @praeko/cli in place of praeko
export PRAEKO_SERVER=https://praeko.app
export PRAEKO_KEY="praeko_…"
praeko post ./package --networks yt --yt-mode private # SIMULATES: nothing is sent
praeko post ./package --networks yt --yt-mode private --send # sends for realSIMULATION (nothing will be sent; use --send) [remote]: How a cache brought down the deploy
package: /home/you/package
video: clipe.mp4 cover: cover.png
YouTube now mode private text 126 charsFor all three networks at once, use --when "2026-11-14T17:30" --networks yt,ig,tt (time in the praeko.json time zone, -03:00 by default) or --queue, which takes the next free slot of each channel. Without --send the CLI only simulates. All commands are in SDK and CLI.
With your agent, over MCP. Connect Claude Code, Codex or another agent to the https://praeko.app/mcp server (with an API key or by signing in with your account) and ask: "publish ~/clips/clipe.mp4 as a private YouTube video titled …". The agent shows a preview with a confirmation code before anything is published, and uploads the file from disk with the upload tools. See MCP server and AI agents.
Do not have the video yet? See Generate the video with Praeko Studio.
Upload the media, under the SDK
The SDK, the CLI and your agent do this for you. Create the media, send the bytes and use the id in the post. Up to 50 MiB you can send the whole file with simple: true; above that, send parts of at least 5 MiB and then POST /v1/media/:id/complete. Details in Media upload.
curl -X POST https://praeko.app/v1/media -H "Authorization: Bearer $PRAEKO_KEY" \
-H "X-Praeko-Contract: en" -H "Content-Type: application/json" \
-d '{ "type": "video", "name": "v.mp4", "mime": "video/mp4", "size": 8123456, "simple": true }'
# response: { "id": "…", "state": "uploading", … }
curl -X PUT https://praeko.app/v1/media/$MEDIA_ID/content -H "Authorization: Bearer $PRAEKO_KEY" \
-H "Content-Type: video/mp4" --data-binary @v.mp4Schedule a post, under the SDK
One item per network. Each item goes to the queue (queue), to a fixed time (scheduledFor) or stays a draft (draft). On YouTube and TikTok, an item with no time goes up now.
curl -X POST https://praeko.app/v1/posts -H "Authorization: Bearer $PRAEKO_KEY" \
-H "X-Praeko-Contract: en" -H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{ "title": "My video", "media": { "video": "'$MEDIA_ID'" },
"texts": { "youtube": { "text": "Description" } },
"items": [ { "network": "youtube", "queue": true } ] }'Listen for a webhook
Register a public https URL (admin scope). The whsec_… secret comes back once. Each delivery carries Praeko-Signature, signed with HMAC-SHA256.
post.sent. Next: verify the signature and handle repeats.Generate the video with Praeko Studio
Praeko Studio is Praeko's video-as-code tool: you write the script and it builds a vertical Short. The path is: script (plain text) → voice (narration with ElevenLabs or a local voice; there is a fast, silent draft mode just for laying out scenes) → word-by-word captions with the timing of each word → scenes (interface pieces described in YAML and TSX, rendered with Remotion or HyperFrames) → cover → package, the folder with the video, the cover and the publication.json that the CLI and the SDK know how to publish. The commands are praeko studio new, voice, render, cover and package.
npm install -g @praeko/studio and follow the Praeko Studio guide: requirements, your first video in 10 minutes with your agent, keys, licenses and how to publish through Praeko. Without a terminal, use the local Studio MCP (@praeko/studio-mcp, which never publishes). Remotion, the default engine, is not MIT: free for companies of up to 3 people.