Cut.ProDocs

Recetas

Flujos completos listos para copiar: del enlace al post publicado, una cola de videos y los mejores momentos de una transmisión en vivo publicados solos.

Cada página de la guía muestra una etapa. Aquí están las etapas juntas, tal como corren en producción. Cambia las URLs, el template_id y los connection_id y ejecuta con CUTPRO_API_KEY en el entorno.

Del enlace de YouTube al post publicado

Analiza, genera clips con tu plantilla, elige el clip de mayor nota, renderiza y publica en TikTok e Instagram.

POST /clips cobra créditos y el post usa la cuota diaria de cada cuenta. Lo demás es gratis.

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);
}
  1. POST /clips/info muestra el costo antes de gastar.
  2. POST /clips con template_id ya entrega cada clip editado.
  3. El clip de mayor nota sale de sort=rating&order=desc&limit=1.
  4. from_cache: true significa que el MP4 ya existía.
  5. El mismo edit_id va a las dos cuentas, cada una con la metadata de su red.

El render explícito es opcional: el post renderiza lo que falta. Está aquí para que tengas el MP4 antes de publicar.

Un post puede terminar en partial: una cuenta publicó y la otra falló. Revisa elemento por elemento y usa POST /posts/{id}/items/{itemId}/retry en el que falló. Lo que ya está al aire no se toca.

Una cola de varios videos

Generar clips de un canal entero es el mismo flujo repetido. El cuidado es no disparar todo de una vez: el polling de decenas de envíos juntos es lo que roza el límite de solicitudes. Una cadena con pocos trabajadores lo resuelve. Usa call y until de la receta 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 del lote, suma los credits_cost de cada POST /clips/info y compáralo con GET /balance. Un envío detenido en downloading con waiting_reason completo está esperando la fuente (estreno, transmisión en vivo al aire, grabación en proceso) y sigue solo. Ve Fuente que aún no está en el aire.

Los mejores momentos de una transmisión en vivo, publicados solos

Un monitor con template_id entrega cada clip de la transmisión ya editado. Este bucle consulta la transmisión cada minuto y publica todo clip nuevo con nota 8 o más. Usa call de la primera receta.

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));
}
  • La transmisión genera clips en bloques de 10 minutos mientras está al aire. Consultar cada minuto ya es más que suficiente.
  • posted vive en la memoria. En producción, guarda los clip.id publicados en tu base de datos para no volver a publicarlos después de reiniciar.
  • El filtro omite el clip sin edit_id. Para publicar esos también, renderiza con POST .../clips/{clipId}/render y usa el edit_id de la respuesta.
  • El monitor cobra por bloque de 10 minutos que generó clips mientras está encendido. Ve Monitorea canales y transmisiones en vivo.

Publicar solo publica en la cuenta de la persona sin revisión. Empieza con privacyLevel: "SELF_ONLY" en TikTok, revisa los primeros resultados y solo después ábrelo a todos.

En esta página