Guia 5 de 8
Etiquetas para e-commerce: expedição automatizada
Etiquetas de marketplace vêm em folha A4, duas ou quatro por página. Com a API, a expedição recebe um PDF só, já no tamanho da impressora térmica, junto com os DANFEs simplificados.
O que você vai construir
Uma rotina diária que pega os PDFs de etiquetas e os XMLs das notas do dia e gera dois PDFs prontos para a térmica: etiquetas recortadas em 10×15 e DANFEs simplificados. E um código de barras Code 128 para cada pedido.
Antes de começar
- Uma chave de API em
CONVERTAJA_KEY(veja Primeiros passos). - Os PDFs das etiquetas baixados do marketplace e, se for imprimir o DANFE, os XMLs das notas.
Passo 1: Recorte as etiquetas para 10×15
A ferramenta recortar-etiqueta encontra as etiquetas na folha e recorta no tamanho da impressora. Opções:
| Opção | Valores |
|---|---|
layout | auto (detecta sozinho, padrão), 2 ou 4 por folha, page (página inteira) |
size | 100x150 (10×15 cm, padrão), 100x100, a6 |
merge | true (padrão): todas num PDF só |
curl -X POST "https://convertaja.com.br/v1/tools/recortar-etiqueta?wait=true" \
-H "Authorization: Bearer $CONVERTAJA_KEY" \
-F "files=@etiquetas-mercado-livre.pdf" \
-F 'options={"layout":"auto","size":"100x150"}'import { openAsBlob } from 'node:fs';
const form = new FormData();
form.append('files', await openAsBlob('etiquetas-mercado-livre.pdf'), 'etiquetas-mercado-livre.pdf');
form.append('options', JSON.stringify({ layout: 'auto', size: '100x150' }));
const res = await fetch('https://convertaja.com.br/v1/tools/recortar-etiqueta?wait=true', {
method: 'POST',
headers: { Authorization: `Bearer ${process.env.CONVERTAJA_KEY}` },
body: form,
});
const job = await res.json();
console.log(job.status, job.info, job.download_url);import json
import os
import requests
with open("etiquetas-mercado-livre.pdf", "rb") as f:
res = requests.post(
"https://convertaja.com.br/v1/tools/recortar-etiqueta",
params={"wait": "true"},
headers={"Authorization": f"Bearer {os.environ['CONVERTAJA_KEY']}"},
files=[("files", ("etiquetas-mercado-livre.pdf", f, "application/pdf"))],
data={"options": json.dumps({"layout": "auto", "size": "100x150"})},
timeout=90,
)
job = res.json()
print(job["status"], job.get("info"), job.get("download_url"))<?php
$ch = curl_init('https://convertaja.com.br/v1/tools/recortar-etiqueta?wait=true');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 90,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('CONVERTAJA_KEY')],
CURLOPT_POSTFIELDS => [
'files' => new CURLFile('etiquetas-mercado-livre.pdf', 'application/pdf'),
'options' => json_encode(['layout' => 'auto', 'size' => '100x150']),
],
]);
$job = json_decode(curl_exec($ch), true);
echo "{$job['status']} {$job['info']} {$job['download_url']}\n";
{
"id": "kwVtn2XtOMA5",
"tool": "recortar-etiqueta",
"operation": "label-crop",
"status": "done",
"progress": 100,
"message": "Pronto!",
"created_at": "2026-10-16T20:37:18.382Z",
"finished_at": "2026-10-16T20:37:18.954Z",
"expires_at": "2026-10-16T21:37:18.382Z",
"credits_charged": 1,
"credits_refunded": 0,
"test": false,
"outputs": [
{
"index": 0,
"name": "etiquetas-10x15.pdf",
"size": 2427,
"url": "https://convertaja.com.br/v1/jobs/kwVtn2XtOMA5/files/0"
}
],
"download_url": "https://convertaja.com.br/v1/jobs/kwVtn2XtOMA5/download",
"info": "2 etiquetas recortadas de 1 página"
}Custo: 1 crédito por PDF enviado, não importa quantas etiquetas ele tenha.
Passo 2: Gere o DANFE simplificado do pedido
Muitas transportadoras pedem o DANFE junto com o pacote. O layout simplificado sai em 10×15, para a mesma impressora:
curl -X POST "https://convertaja.com.br/v1/tools/xml-nfe-para-pdf?wait=true" \
-H "Authorization: Bearer $CONVERTAJA_KEY" \
-F "files=@nota-pedido-1023.xml" \
-F 'options={"layout":"simplificado","merge":true}'
Mais detalhes no guia NF-e em lote.
Passo 3: A rotina do dia: tudo pronto para imprimir
Coloque as etiquetas em ./etiquetas e os XMLs em ./notas. O script gera imprimir-etiquetas.pdf e imprimir-danfes.pdf:
// expedicao.mjs: prepara a impressão do dia na térmica 10×15
// ./etiquetas/*.pdf → etiquetas recortadas, num PDF só
// ./notas/*.xml → DANFEs simplificados, num PDF só
import { openAsBlob } from 'node:fs';
import { readdir, writeFile } from 'node:fs/promises';
import path from 'node:path';
const BASE = 'https://convertaja.com.br/v1';
const headers = { Authorization: `Bearer ${process.env.CONVERTAJA_KEY}` };
async function rodar(ferramenta, pasta, extensao, opcoes, saida) {
const nomes = (await readdir(pasta)).filter((n) => n.toLowerCase().endsWith(extensao));
if (!nomes.length) return console.log(`${pasta}: nada para processar`);
const form = new FormData();
for (const n of nomes) form.append('files', await openAsBlob(path.join(pasta, n)), n);
form.append('options', JSON.stringify(opcoes));
let res = await fetch(`${BASE}/tools/${ferramenta}?wait=true`, { method: 'POST', headers, body: form });
let job = await res.json();
if (!res.ok) throw new Error(`${ferramenta}: ${job.error.code} — ${job.error.message}`);
while (job.status !== 'done' && job.status !== 'error') {
await new Promise((r) => setTimeout(r, 2000));
job = await (await fetch(`${BASE}/jobs/${job.id}`, { headers })).json();
}
if (job.status === 'error') throw new Error(`${ferramenta}: ${job.error.message}`);
res = await fetch(job.download_url, { headers });
await writeFile(saida, Buffer.from(await res.arrayBuffer()));
console.log(`${saida}: ${job.info} (${job.credits_charged} créditos)`);
}
await rodar('recortar-etiqueta', './etiquetas', '.pdf', { layout: 'auto', size: '100x150', merge: true }, 'imprimir-etiquetas.pdf');
await rodar('xml-nfe-para-pdf', './notas', '.xml', { layout: 'simplificado', merge: true }, 'imprimir-danfes.pdf');# expedicao.py: prepara a impressão do dia na térmica 10×15
# ./etiquetas/*.pdf → etiquetas recortadas, num PDF só
# ./notas/*.xml → DANFEs simplificados, num PDF só
import json
import os
import time
import requests
BASE = "https://convertaja.com.br/v1"
HEADERS = {"Authorization": f"Bearer {os.environ['CONVERTAJA_KEY']}"}
def rodar(ferramenta, pasta, extensao, opcoes, saida):
nomes = sorted(n for n in os.listdir(pasta) if n.lower().endswith(extensao))
if not nomes:
print(f"{pasta}: nada para processar")
return
abertos = [open(os.path.join(pasta, n), "rb") for n in nomes]
try:
res = requests.post(f"{BASE}/tools/{ferramenta}", params={"wait": "true"}, headers=HEADERS, timeout=120,
files=[("files", (os.path.basename(f.name), f)) for f in abertos],
data={"options": json.dumps(opcoes)})
finally:
for f in abertos:
f.close()
job = res.json()
if not res.ok:
raise RuntimeError(f"{ferramenta}: {job['error']['code']} — {job['error']['message']}")
while job["status"] not in ("done", "error"):
time.sleep(2)
job = requests.get(f"{BASE}/jobs/{job['id']}", headers=HEADERS, timeout=30).json()
if job["status"] == "error":
raise RuntimeError(f"{ferramenta}: {job['error']['message']}")
arquivo = requests.get(job["download_url"], headers=HEADERS, timeout=120)
with open(saida, "wb") as f:
f.write(arquivo.content)
print(f"{saida}: {job['info']} ({job['credits_charged']} créditos)")
rodar("recortar-etiqueta", "./etiquetas", ".pdf", {"layout": "auto", "size": "100x150", "merge": True}, "imprimir-etiquetas.pdf")
rodar("xml-nfe-para-pdf", "./notas", ".xml", {"layout": "simplificado", "merge": True}, "imprimir-danfes.pdf")<?php
// expedicao.php: prepara a impressão do dia na térmica 10×15
// ./etiquetas/*.pdf → etiquetas recortadas, num PDF só
// ./notas/*.xml → DANFEs simplificados, num PDF só
const BASE = 'https://convertaja.com.br/v1';
function chamar(string $url, $campos = null): array {
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 120,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('CONVERTAJA_KEY')],
]);
if ($campos !== null) curl_setopt($ch, CURLOPT_POSTFIELDS, $campos); // vira POST multipart
$data = json_decode(curl_exec($ch), true);
if (curl_getinfo($ch, CURLINFO_HTTP_CODE) >= 400) throw new RuntimeException("{$data['error']['code']}: {$data['error']['message']}");
return $data;
}
function rodar(string $ferramenta, string $pasta, string $extensao, array $opcoes, string $saida): void {
$arquivos = array_values(array_filter(glob("$pasta/*"), fn($f) => strtolower(pathinfo($f, PATHINFO_EXTENSION)) === $extensao));
if (!$arquivos) { echo "$pasta: nada para processar\n"; return; }
$campos = ['options' => json_encode($opcoes)];
foreach ($arquivos as $i => $arquivo) $campos["files[$i]"] = new CURLFile($arquivo, '', basename($arquivo));
$job = chamar(BASE . "/tools/$ferramenta?wait=true", $campos);
while (!in_array($job['status'], ['done', 'error'], true)) {
sleep(2);
$job = chamar(BASE . "/jobs/{$job['id']}");
}
if ($job['status'] === 'error') throw new RuntimeException("$ferramenta: {$job['error']['message']}");
$fp = fopen($saida, 'wb');
$ch = curl_init($job['download_url']);
curl_setopt_array($ch, [CURLOPT_FILE => $fp, CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('CONVERTAJA_KEY')]]);
curl_exec($ch);
fclose($fp);
echo "$saida: {$job['info']} ({$job['credits_charged']} créditos)\n";
}
rodar('recortar-etiqueta', './etiquetas', 'pdf', ['layout' => 'auto', 'size' => '100x150', 'merge' => true], 'imprimir-etiquetas.pdf');
rodar('xml-nfe-para-pdf', './notas', 'xml', ['layout' => 'simplificado', 'merge' => true], 'imprimir-danfes.pdf');
imprimir-etiquetas.pdf: 4 etiquetas recortadas de 2 páginas (2 créditos)
imprimir-danfes.pdf: 2 notas convertidas (2 créditos)
Dica Ao imprimir, escolha o papel 100×150 mm e escala 100% (sem “ajustar à página”), para o código de barras sair no tamanho certo.
Passo 4: Código de barras para o pedido
Para identificar pacotes e pedidos na separação, gere um Code 128 (aceita letras e números) em PNG ou SVG com POST /v1/barcode (1 crédito por chamada). Também há EAN-13, EAN-8, UPC-A, Code 39, ITF, ITF-14, DataMatrix e QR:
# Code 128 com o número do pedido, em PNG (requer jq e base64)
curl -s -X POST "https://convertaja.com.br/v1/barcode" \
-H "Authorization: Bearer $CONVERTAJA_KEY" \
-H "Content-Type: application/json" \
-d '{"type":"code128","value":"PED-10023","format":"png","height":80}' \
| jq -r .png_base64 | base64 --decode > pedido-10023.pngimport { writeFile } from 'node:fs/promises';
const res = await fetch('https://convertaja.com.br/v1/barcode', {
method: 'POST',
headers: { Authorization: `Bearer ${process.env.CONVERTAJA_KEY}`, 'Content-Type': 'application/json' },
body: JSON.stringify({ type: 'code128', value: 'PED-10023', format: 'png', height: 80 }),
});
const codigo = await res.json();
if (!res.ok) throw new Error(`${codigo.error.code}: ${codigo.error.message}`);
await writeFile('pedido-10023.png', Buffer.from(codigo.png_base64, 'base64'));
console.log(`pedido-10023.png (${codigo.width}×${codigo.height} px)`);import base64
import os
import requests
res = requests.post(
"https://convertaja.com.br/v1/barcode",
headers={"Authorization": f"Bearer {os.environ['CONVERTAJA_KEY']}"},
json={"type": "code128", "value": "PED-10023", "format": "png", "height": 80},
timeout=30,
)
codigo = res.json()
if not res.ok:
raise SystemExit(f"{codigo['error']['code']}: {codigo['error']['message']}")
with open("pedido-10023.png", "wb") as f:
f.write(base64.b64decode(codigo["png_base64"]))
print(f"pedido-10023.png ({codigo['width']}×{codigo['height']} px)")<?php
$ch = curl_init('https://convertaja.com.br/v1/barcode');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('CONVERTAJA_KEY'), 'Content-Type: application/json'],
CURLOPT_POSTFIELDS => json_encode(['type' => 'code128', 'value' => 'PED-10023', 'format' => 'png', 'height' => 80]),
]);
$codigo = json_decode(curl_exec($ch), true);
file_put_contents('pedido-10023.png', base64_decode($codigo['png_base64']));
echo "pedido-10023.png ({$codigo['width']}×{$codigo['height']} px)\n";
{
"type": "code128",
"value": "PED-10023",
"width": 286,
"height": 105,
"png_base64": "iVBORw0KGgoAAAANSUhEUgAAAR4AAABpCAYAAAD…"
}Para a página de rastreio, um QR Code com POST /v1/qrcode funciona do mesmo jeito (campo text). Veja o guia Consultas e dados.
E a ZPL?
A conversão de ZPL para PDF (e de PDF para ZPL) ainda não está na API: ela roda no navegador, na ferramenta ZPL para PDF do site. Se você precisa dela por API, conte para a gente.
Erros e o que fazer
415 unsupported_file_type: o recorte só aceita PDF. Etiqueta em imagem? Converta antes comjpg-para-pdf.- Etiquetas cortadas no lugar errado: troque
layout: "auto"por"2"ou"4", conforme a folha. 400 too_many_files: mais PDFs do que o plano aceita numa tarefa (limits.max_files_per_job). Divida em lotes.
Próximos passos
Todos os endpoints e opções: referência da API. Dúvidas? Fale com a gente e informe o X-Request-Id da chamada.