Receitas
Fluxos inteiros prontos para copiar: do link ao post publicado, uma fila de vídeos e os melhores momentos de uma live postados sozinhos.
Cada página do guia mostra uma etapa. Aqui estão as etapas juntas, do jeito que rodam em produção. Troque as URLs, o template_id e os connection_id e rode com CUTPRO_API_KEY no ambiente.
Do link do YouTube ao post publicado
Analisa, clipa com o seu template, escolhe o clipe de maior nota, renderiza e publica no TikTok e no Instagram.
POST /clips cobra créditos e o post usa a cota diária de cada conta. O resto é grátis.
const API = "https://api.cut.pro/api/v1";
const HEADERS = { "X-Api-Key": process.env.CUTPRO_API_KEY, "Content-Type": "application/json" };
const CLIPPING = ["queued", "downloading", "transcribing", "video_analysis", "analyzing", "finalizing"];
const SOURCE = "https://www.youtube.com/watch?v=dQw4w9WgXcQ";
const TEMPLATE_ID = "7398110254410021";
async function call(path, init) {
const response = await fetch(`${API}${path}`, { ...init, headers: HEADERS });
const body = await response.json();
if (!response.ok) throw new Error(`${response.status} ${body.code}`);
return body;
}
async function until(read, working, everySeconds) {
let state = await read();
while (working.includes(state.status)) {
await new Promise((resolve) => setTimeout(resolve, everySeconds * 1000));
state = await read();
}
return state;
}
const video = await call("/clips/info", { method: "POST", body: JSON.stringify({ url: SOURCE }) });
console.log(`${video.title}: ${video.credits_cost} créditos`);
const submission = await call("/clips", {
method: "POST",
body: JSON.stringify({ video_id: video.id, template_id: TEMPLATE_ID }),
});
const base = `/clips/${video.id}/submissions/${submission.id}`;
const done = await until(() => call(base), CLIPPING, 15);
if (done.status === "failed") throw new Error(done.error_code);
const { clips } = await call(`${base}/clips?sort=rating&order=desc&limit=1`);
const best = clips[0];
const render = await call(`${base}/clips/${best.id}/render`, { method: "POST" });
const finished = render.from_cache ? render : await until(() => call(`/renders/${render.id}`), ["waiting_quality", "queued", "active"], 10);
if (finished.status !== "completed") throw new Error(finished.status);
const post = await call("/posts", {
method: "POST",
body: JSON.stringify({
videos: [
{
edit_id: render.edit_id,
targets: [
{ connection_id: "7390012299881100", metadata: { tiktok: { title: best.title, privacyLevel: "PUBLIC_TO_EVERYONE" } } },
{ connection_id: "7390012299881300", metadata: { instagram: { caption: best.title } } },
],
},
],
}),
});
const published = await until(() => call(`/posts/${post.id}`), ["pending", "processing"], 10);
for (const item of published.items) {
console.log(item.platform, item.status, item.published_url ?? item.error_code);
}POST /clips/infomostra o custo antes de gastar.POST /clipscomtemplate_idjá entrega cada clipe editado.- O clipe de maior nota sai de
sort=rating&order=desc&limit=1. from_cache: truequer dizer que o MP4 já existia.- O mesmo
edit_idvai para as duas contas, cada uma com ametadatada sua rede.
O render explícito é opcional: o post renderiza o que falta. Ele está aqui para você ter o MP4 antes de publicar.
Um post pode terminar em partial: uma conta publicou e a outra falhou. Olhe item por item e use POST /posts/{id}/items/{itemId}/retry no que falhou. O que já está no ar não é tocado.
Uma fila de vários vídeos
Clipar um canal inteiro é o mesmo fluxo repetido. O cuidado é não disparar tudo de uma vez: o polling de dezenas de submissões juntas é o que encosta no limite de requisições. Uma esteira com poucos trabalhadores resolve. Usa call e until da receita anterior.
const SOURCES = ["https://youtu.be/aaa", "https://youtu.be/bbb", "https://youtu.be/ccc"];
const AT_A_TIME = 3;
async function clipOne(url) {
const video = await call("/clips/info", { method: "POST", body: JSON.stringify({ url }) });
const submission = await call("/clips", { method: "POST", body: JSON.stringify({ video_id: video.id }) });
const base = `/clips/${video.id}/submissions/${submission.id}`;
const done = await until(() => call(base), CLIPPING, 15);
if (done.status === "failed") return { url, error: done.error_code };
const { clips } = await call(`${base}/clips`);
return { url, clips };
}
const queue = [...SOURCES];
const results = await Promise.all(
Array.from({ length: AT_A_TIME }, async () => {
const mine = [];
while (queue.length > 0) mine.push(await clipOne(queue.shift()));
return mine;
}),
);
console.log(results.flat());Antes do lote, some os credits_cost de cada POST /clips/info e compare com GET /balance. Uma submissão parada em downloading com waiting_reason preenchido está esperando a fonte (estreia, live no ar, gravação em processamento) e segue sozinha. Veja Fonte que ainda não está no ar.
Os melhores momentos de uma live, postados sozinhos
Um monitor com template_id entrega cada clipe da live já editado. Este laço consulta a live a cada minuto e publica todo clipe novo com nota 8 ou mais. Usa call da primeira receita.
const MONITOR_ID = "7413020011876610";
const TIKTOK = "7390012299881100";
const posted = new Set();
while (true) {
const { broadcasts } = await call(`/monitors/${MONITOR_ID}/broadcasts?limit=1`);
const live = broadcasts[0];
if (live && !live.recording_expired) {
const { clips } = await call(`/monitors/${MONITOR_ID}/broadcasts/${live.id}/clips?min_rating=8&limit=100`);
const fresh = clips.filter((clip) => clip.edit_id && !posted.has(clip.id));
for (const clip of fresh) {
await call("/posts", {
method: "POST",
body: JSON.stringify({
videos: [{ edit_id: clip.edit_id, targets: [{ connection_id: TIKTOK, metadata: { tiktok: { title: clip.title, privacyLevel: "PUBLIC_TO_EVERYONE" } } }] }],
}),
});
posted.add(clip.id);
}
}
await new Promise((resolve) => setTimeout(resolve, 60_000));
}- A live gera clipes em blocos de 10 minutos enquanto está no ar. Consultar a cada minuto já é mais que suficiente.
postedvive na memória. Em produção, guarde osclip.idpublicados no seu banco para não repostar depois de reiniciar.- O filtro pula clipe sem
edit_id. Para postar esses também, renderize comPOST .../clips/{clipId}/rendere use oedit_idda resposta. - O monitor cobra por bloco de 10 minutos que gerou clipes enquanto está ligado. Veja Monitore canais e lives.
Publicar sozinho posta na conta da pessoa sem revisão. Comece com privacyLevel: "SELF_ONLY" no TikTok, confira os primeiros resultados e só depois abra para todos.