Criar post
Cria um post com um item por rede (fila, horário fixo ou rascunho). Cada item pode levar channel (a conta daquela rede; sem ele, vale o canal padrão). Com kind: "image" é um post de foto única, só no Instagram (pelo Buffer ou direto pela Meta).
Authorization: Bearer praeko_… e X-Praeko-Contract: en. Sempre envie os dois.Parâmetros
| Nome | Onde | Descrição |
|---|---|---|
Idempotency-Key |
cabeçalho | Chave de idempotência (1 a 255 caracteres visíveis, por exemplo um UUID). Repetir o MESMO pedido com a mesma chave, em até 24 h, devolve a resposta guardada (com Idempotent-Replayed: true) sem executar de novo; outro pedido com a mesma chave dá 422 idempotency_key_reuse; um pedido ainda em andamento dá 409 idempotency_in_progress. Erros 5xx não são guardados. A chave vale por workspace e por rota. Veja Repetir uma requisição. |
Corpo da requisição
JSON. Campos obrigatórios precisam ir; os outros podem ser omitidos. O servidor devolve 422 invalid_request com a lista completa dos problemas em details.errors. A mídia precisa estar ready (completa). Post de imagem (kind: image): foto única no Instagram, pelo Buffer ou direto pela Meta. A foto é uma mídia type: image (JPEG); o Instagram aceita JPEG de até 8 MB com proporção de 4:5 a 1,91:1 (use 1080x1350). O item pode voltar com warnings (code estável): alt_ignored (o Buffer não tem texto alternativo), image_small, image_will_be_resized, image_dimensions_unknown. O post volta com kind e media.images.
| Campo | Tipo | Descrição |
|---|---|---|
title (opcional) |
texto | Título do post. Obrigatório, a não ser que texts.youtube.title exista. Até 200 caracteres. |
baseText (opcional) |
texto | Texto base, guardado junto ao post. |
kind (opcional) |
texto | video (padrão) ou image: foto única, só Instagram. YouTube e TikTok recusam image (422 kind_not_supported_by_network). Valores: video, image; padrão: video. |
media obrigatório |
objeto | Mídias do post (ids de POST /v1/media, já completadas). |
media.video (opcional) |
texto | Id da mídia de vídeo. Obrigatório quando kind é video. |
media.cover (opcional) |
texto | Id da mídia de capa (PNG ou JPEG). Só em post de vídeo. |
media.images (opcional) |
objeto[] | Só com kind: image: a foto do post, UMA por enquanto (várias seriam um carrossel, que chega numa fase futura: 422 carousel_not_supported). Até 1 itens. |
media.images[].media obrigatório |
texto | Id de uma mídia type: image já completada. |
media.images[].alt (opcional) |
texto | Texto alternativo (até 1000 caracteres). Vai para o Instagram quando o item publica pela Meta (via: meta); pelo Buffer é ignorado e o item volta com o aviso alt_ignored. Até 1000 caracteres. |
texts obrigatório |
objeto | Textos por rede. Cada rede de items precisa ter o seu text. |
texts.youtube (opcional) |
objeto | Textos do YouTube. |
texts.youtube.title (opcional) |
texto | Título do vídeo (até 100 caracteres; mais longo é cortado). |
texts.youtube.text obrigatório |
texto | Descrição. |
texts.youtube.pinnedComment (opcional) |
texto | Comentário fixo, publicado depois que o vídeo fica público. |
texts.instagram (opcional) |
objeto | Textos do Instagram. |
texts.instagram.text obrigatório |
texto | Legenda: até 2200 caracteres e 30 hashtags. |
texts.instagram.pinnedComment (opcional) |
texto | Comentário fixo. |
texts.tiktok (opcional) |
objeto | Textos do TikTok. |
texts.tiktok.text obrigatório |
texto | Legenda: até 2200 caracteres. |
items obrigatório |
objeto[] | Um item por rede (não pode repetir a rede). |
items[].network obrigatório |
texto | Rede do item. Valores: youtube, instagram, tiktok. |
items[].channel (opcional) |
texto | Id da conta daquela rede (de GET /v1/channels). Sem ele, o canal padrão. |
items[].mode (opcional) |
texto | Como publicar. YouTube: private, scheduled ou public. TikTok: draft ou direct. Instagram: now. Valores: private, scheduled, public, draft, direct, now; padrão: private no YouTube, draft no TikTok, now no Instagram. |
items[].scheduledFor (opcional) |
texto | Horário fixo da publicação. Data e hora ISO 8601 em UTC, por exemplo 2026-11-14T20:30:00Z. No mínimo 30 s à frente (Instagram: 5 min pelo Buffer, 25 min pela Meta; YouTube scheduled: 5 min). |
items[].queue (opcional) |
verdadeiro/falso | true usa o próximo horário livre do canal (veja PUT /v1/channels/:network/slots). |
items[].via (opcional) |
texto | Só Instagram: por onde publicar. Valores: meta, buffer. |
items[].replaces (opcional) |
objeto | Só Instagram com via: meta: apaga este post do Buffer ao publicar. |
items[].replaces.provider obrigatório |
texto | Sempre buffer. Valores: buffer. |
items[].replaces.id obrigatório |
texto | Id do post no Buffer. |
items[].media (opcional) |
objeto | Mídias só deste item (trocam as do post). Só video e cover: imagens por item chegam numa fase futura. |
items[].media.video (opcional) |
texto | Id da mídia de vídeo. |
items[].media.cover (opcional) |
texto | Id da mídia de capa. |
queue (opcional) |
verdadeiro/falso | Vale para todos os itens que não trouxerem queue nem scheduledFor. |
draft (opcional) |
verdadeiro/falso | true cria tudo como rascunho (sem horário obrigatório). Padrão: false. |
options (opcional) |
objeto | Opções por rede e gerais. |
options.format (opcional) |
texto | long marca o post como vídeo longo (sem ele, é um Short). Valores: long. |
options.youtube (opcional) |
objeto | Opções do YouTube. |
options.youtube.mode (opcional) |
texto | O mesmo de items[].mode. Valores: private, scheduled, public. |
options.youtube.categoryId (opcional) |
texto | Categoria do vídeo. Padrão: 28. |
options.youtube.tags (opcional) |
string[] | Etiquetas do vídeo. |
options.youtube.notifySubscribers (opcional) |
verdadeiro/falso | Avisar os inscritos. |
options.youtube.comment (opcional) |
verdadeiro/falso | false não publica o comentário fixo. |
options.tiktok (opcional) |
objeto | Opções do TikTok. |
options.tiktok.mode (opcional) |
texto | O mesmo de items[].mode. Valores: draft, direct. |
options.tiktok.privacy (opcional) |
texto | Quem vê. Valores: PUBLIC_TO_EVERYONE, MUTUAL_FOLLOW_FRIENDS, FOLLOWER_OF_CREATOR, SELF_ONLY; padrão: SELF_ONLY. |
options.tiktok.coverFrameMs (opcional) |
inteiro | Quadro do vídeo usado como capa, em milissegundos. Mínimo 0. |
options.tiktok.disableComments (opcional) |
verdadeiro/falso | Desligar comentários. |
options.tiktok.disableDuet (opcional) |
verdadeiro/falso | Desligar dueto. |
options.tiktok.disableStitch (opcional) |
verdadeiro/falso | Desligar stitch. |
options.tiktok.commercialContent (opcional) |
verdadeiro/falso | Conteúdo comercial (exige ownBrand e/ou thirdPartyBrand). |
options.tiktok.ownBrand (opcional) |
verdadeiro/falso | Divulga a própria marca. |
options.tiktok.thirdPartyBrand (opcional) |
verdadeiro/falso | Divulga marca de terceiros (não pode ser SELF_ONLY no modo direct). |
options.instagram (opcional) |
objeto | Opções do Instagram. |
options.instagram.via (opcional) |
texto | Por onde publicar. Valores: meta, buffer. |
options.instagram.shareToFeed (opcional) |
verdadeiro/falso | Mostrar também no feed. Padrão: true. |
options.instagram.coverFrameMs (opcional) |
inteiro | Quadro do vídeo usado como capa, em milissegundos. Mínimo 0. |
tags (opcional) |
string[] | Etiquetas do post (até 40 caracteres cada). Cada um com até 40 caracteres. |
id (opcional) |
texto | Id escolhido por você (3 a 64 caracteres entre letras, números, - e _). Sem ele, o servidor gera. Repetido dá 409 id_exists. |
origin (opcional) |
texto | Quem criou o post (texto livre; padrão api). |
{
"title": "O teste que só falhava na segunda",
"media": {
"video": "01KA3VQ7M2T9XE4HB8N5RZ6C0D"
},
"texts": {
"youtube": {
"title": "O teste que só falhava na segunda",
"text": "Um teste que passava de terça a domingo. O que ele escondia.\n\n#programação #testes"
}
},
"items": [
{
"network": "youtube",
"mode": "scheduled",
"scheduledFor": "2026-11-10T20:30:00.000Z"
}
],
"tags": [
"bastidores"
]
}Exemplo
curl -X POST "https://praeko.app/v1/posts" \
-H "Authorization: Bearer $PRAEKO_KEY" \
-H "X-Praeko-Contract: en" \
-H "Content-Type: application/json" \
-d '{
"title": "O teste que só falhava na segunda",
"media": {
"video": "01KA3VQ7M2T9XE4HB8N5RZ6C0D"
},
"texts": {
"youtube": {
"title": "O teste que só falhava na segunda",
"text": "Um teste que passava de terça a domingo. O que ele escondia.\n\n#programação #testes"
}
},
"items": [
{
"network": "youtube",
"mode": "scheduled",
"scheduledFor": "2026-11-10T20:30:00.000Z"
}
],
"tags": [
"bastidores"
]
}'const res = await fetch("https://praeko.app/v1/posts", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.PRAEKO_KEY}`,
"X-Praeko-Contract": "en",
"Content-Type": "application/json",
},
body: JSON.stringify({
"title": "O teste que só falhava na segunda",
"media": {
"video": "01KA3VQ7M2T9XE4HB8N5RZ6C0D"
},
"texts": {
"youtube": {
"title": "O teste que só falhava na segunda",
"text": "Um teste que passava de terça a domingo. O que ele escondia.\n\n#programação #testes"
}
},
"items": [
{
"network": "youtube",
"mode": "scheduled",
"scheduledFor": "2026-11-10T20:30:00.000Z"
}
],
"tags": [
"bastidores"
]
}),
});
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.posts.create({
"title": "O teste que só falhava na segunda",
"media": {
"video": "01KA3VQ7M2T9XE4HB8N5RZ6C0D"
},
"texts": {
"youtube": {
"title": "O teste que só falhava na segunda",
"text": "Um teste que passava de terça a domingo. O que ele escondia.\n\n#programação #testes"
}
},
"items": [
{
"network": "youtube",
"mode": "scheduled",
"scheduledFor": "2026-11-10T20:30:00.000Z"
}
],
"tags": [
"bastidores"
]
});
console.log(resultado);import os
import requests
resp = requests.post(
"https://praeko.app/v1/posts",
headers={
"Authorization": f"Bearer {os.environ['PRAEKO_KEY']}",
"X-Praeko-Contract": "en",
},
json={
"title": "O teste que só falhava na segunda",
"media": {
"video": "01KA3VQ7M2T9XE4HB8N5RZ6C0D",
},
"texts": {
"youtube": {
"title": "O teste que só falhava na segunda",
"text": "Um teste que passava de terça a domingo. O que ele escondia.\n\n#programação #testes",
},
},
"items": [
{
"network": "youtube",
"mode": "scheduled",
"scheduledFor": "2026-11-10T20:30:00.000Z",
},
],
"tags": [
"bastidores",
],
},
)
print(resp.status_code, resp.json())<?php
$corpo = <<<'JSON'
{
"title": "O teste que só falhava na segunda",
"media": {
"video": "01KA3VQ7M2T9XE4HB8N5RZ6C0D"
},
"texts": {
"youtube": {
"title": "O teste que só falhava na segunda",
"text": "Um teste que passava de terça a domingo. O que ele escondia.\n\n#programação #testes"
}
},
"items": [
{
"network": "youtube",
"mode": "scheduled",
"scheduledFor": "2026-11-10T20:30:00.000Z"
}
],
"tags": [
"bastidores"
]
}
JSON;
$ch = curl_init('https://praeko.app/v1/posts');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
"Authorization: Bearer " . getenv("PRAEKO_KEY"),
'X-Praeko-Contract: en',
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => $corpo,
]);
$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"
"strings"
)
func main() {
body := strings.NewReader(`{
"title": "O teste que só falhava na segunda",
"media": {
"video": "01KA3VQ7M2T9XE4HB8N5RZ6C0D"
},
"texts": {
"youtube": {
"title": "O teste que só falhava na segunda",
"text": "Um teste que passava de terça a domingo. O que ele escondia.\n\n#programação #testes"
}
},
"items": [
{
"network": "youtube",
"mode": "scheduled",
"scheduledFor": "2026-11-10T20:30:00.000Z"
}
],
"tags": [
"bastidores"
]
}`)
req, err := http.NewRequest("POST", "https://praeko.app/v1/posts", body)
if err != nil {
panic(err)
}
req.Header.Set("Authorization", "Bearer "+os.Getenv("PRAEKO_KEY"))
req.Header.Set("X-Praeko-Contract", "en")
req.Header.Set("Content-Type", "application/json")
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/posts")
request = Net::HTTP::Post.new(uri)
request["Authorization"] = "Bearer #{ENV['PRAEKO_KEY']}"
request["X-Praeko-Contract"] = "en"
request["Content-Type"] = "application/json"
request.body = <<~'JSON'
{
"title": "O teste que só falhava na segunda",
"media": {
"video": "01KA3VQ7M2T9XE4HB8N5RZ6C0D"
},
"texts": {
"youtube": {
"title": "O teste que só falhava na segunda",
"text": "Um teste que passava de terça a domingo. O que ele escondia.\n\n#programação #testes"
}
},
"items": [
{
"network": "youtube",
"mode": "scheduled",
"scheduledFor": "2026-11-10T20:30:00.000Z"
}
],
"tags": [
"bastidores"
]
}
JSON
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.Post, "https://praeko.app/v1/posts");
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", Environment.GetEnvironmentVariable("PRAEKO_KEY"));
request.Headers.Add("X-Praeko-Contract", "en");
request.Content = new StringContent("""
{
"title": "O teste que só falhava na segunda",
"media": {
"video": "01KA3VQ7M2T9XE4HB8N5RZ6C0D"
},
"texts": {
"youtube": {
"title": "O teste que só falhava na segunda",
"text": "Um teste que passava de terça a domingo. O que ele escondia.\n\n#programação #testes"
}
},
"items": [
{
"network": "youtube",
"mode": "scheduled",
"scheduledFor": "2026-11-10T20:30:00.000Z"
}
],
"tags": [
"bastidores"
]
}
""", Encoding.UTF8, "application/json");
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 {
String body = """
{
"title": "O teste que só falhava na segunda",
"media": {
"video": "01KA3VQ7M2T9XE4HB8N5RZ6C0D"
},
"texts": {
"youtube": {
"title": "O teste que só falhava na segunda",
"text": "Um teste que passava de terça a domingo. O que ele escondia.\\n\\n#programação #testes"
}
},
"items": [
{
"network": "youtube",
"mode": "scheduled",
"scheduledFor": "2026-11-10T20:30:00.000Z"
}
],
"tags": [
"bastidores"
]
}
""";
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://praeko.app/v1/posts"))
.header("Authorization", "Bearer " + System.getenv("PRAEKO_KEY"))
.header("X-Praeko-Contract", "en")
.header("Content-Type", "application/json")
.method("POST", HttpRequest.BodyPublishers.ofString(body))
.build();
HttpResponse<String> response = HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.statusCode() + " " + response.body());
}
}http POST "https://praeko.app/v1/posts" \
"Authorization: Bearer $PRAEKO_KEY" \
"X-Praeko-Contract: en" \
"Content-Type: application/json" \
--raw='{
"title": "O teste que só falhava na segunda",
"media": {
"video": "01KA3VQ7M2T9XE4HB8N5RZ6C0D"
},
"texts": {
"youtube": {
"title": "O teste que só falhava na segunda",
"text": "Um teste que passava de terça a domingo. O que ele escondia.\n\n#programação #testes"
}
},
"items": [
{
"network": "youtube",
"mode": "scheduled",
"scheduledFor": "2026-11-10T20:30:00.000Z"
}
],
"tags": [
"bastidores"
]
}'npm install @praeko/sdk (veja SDK e CLI).Resposta
| Status de sucesso | Quando |
|---|---|
201 |
Sucesso. |
{
"id": "01KA3W2Y6H1RC5ZQ8M0T4VXE7N",
"items": [
{
"id": "01KA3W2Y6J9D3BNF7G2K5PQS8A",
"network": "youtube",
"channel": "01KA3V0R5D8M2WQ7X4HT9NZ1BF",
"state": "scheduled",
"scheduledFor": "2026-11-10T20:30:00.000Z",
"custom": true,
"mode": "scheduled",
"runAt": "2026-11-10T19:30:00.000Z"
}
]
}Erros
| Status | code | Quando |
|---|---|---|
| 400 | empty_body |
O corpo veio vazio. |
| 400 | invalid_json |
O corpo não é um objeto JSON. |
| 413 | body_too_large |
O corpo passa de 1 MB. |
| 422 | invalid_request |
Faltou algo; a lista completa vem em details.errors. |
| 422 | invalid_field |
Id, canal, modo ou etiqueta inválidos. |
| 422 | time_in_past |
O horário já passou ou está perto demais. |
| 422 | missing_schedule |
Faltou scheduledFor, queue ou draft. |
| 409 | channel_not_connected |
A rede não está conectada. |
| 409 | no_slots |
queue num canal sem horários. |
| 409 | id_exists |
Já existe um post com esse id. |
| 409 | media_not_ready |
A mídia ainda não foi concluída. |
| 422 | media_not_found |
A mídia não existe. |
| 422 | media_type_mismatch |
A mídia não é do tipo certo (kind: image pede type: image). |
| 422 | kind_not_supported_by_network |
kind: image num item de YouTube ou TikTok. |
| 422 | carousel_not_supported |
Mais de uma imagem em media.images (carrossel chega depois). |
| 422 | invalid_image |
A imagem não é JPEG (o Instagram só aceita JPEG). |
| 422 | image_too_large |
A imagem passa de 8 MB (limite do Instagram). |
| 422 | image_aspect_ratio |
A proporção da imagem fica fora de 4:5 a 1,91:1 (o Instagram recusaria; use 1080x1350). |
| 422 | idempotency_key_reuse |
A Idempotency-Key já foi usada com outro pedido (método, caminho, query ou corpo diferentes). |
| 409 | idempotency_in_progress |
Um pedido com a mesma Idempotency-Key ainda está em andamento (repita em instantes). |
| 409 | idempotency_response_too_large |
A resposta original passou de 256 KB e não foi guardada; consulte o recurso por GET. |
Erros de qualquer rota
| Status | code | Quando |
|---|---|---|
| 401 | unauthorized |
Faltou o cabeçalho Authorization: Bearer …. |
| 401 | invalid_api_key |
A chave não existe. |
| 401 | revoked_api_key |
A chave foi revogada. |
| 401 | expired_api_key |
A chave expirou. |
| 403 | forbidden |
O escopo da chave não basta para esta rota (veja o selo de escopo). |
| 429 | rate_limited |
Passou do limite de requisições por minuto. |
O corpo de todo erro é { "error": { "code", "message", "details"? } }. Veja todos os códigos.
Testar
O teste ao vivo usa a sua chave e a sua conta. Para quem não entrou, fica desligado.