Integrações & Desenvolvedores
API de integração para parceiros
Você é dono da venda: catálogo, vitrine, pagamento e pré-venda. A 3Print é dona da produção: impressão, montagem, embalagem, nota fiscal e transportadora. Esta API é o contrato entre os dois lados — você manda o pedido, a gente devolve o andamento.
Isto é para você?
A mesma API atende três situações bem diferentes. Acha a sua antes de seguir — a terceira economiza semanas de trabalho.
Tenho vários lojistas
Marketplace, plataforma de criadores, agência que atende várias marcas. Cada vendedor seu é um lojista aqui, e o external_seller_id é o identificador dele do seu lado. Leia tudo, na ordem.
Tenho uma loja só, com site próprio
Peça uma chave da loja. Ela já aponta para a sua conta: o pedido entra no seu painel, com o seu catálogo e a sua tabela de preço. Você não cadastra lojista e não manda external_seller_id. O pedido para em Aguardando Aprovação no seu painel, e a arte de impressão pode ir no pedido ou ser enviada por lá antes de aprovar. Pule a seção 4 e vá direto para Catálogo e SKU.
Uso Nuvemshop, Shopify, Bling ou CartPanda
Não use esta API. Já existe integração pronta para essas plataformas: você conecta a loja pelo painel e os pedidos entram sozinhos, com rastreio voltando automaticamente. Construir contra esta API seria refazer o que já está feito. Veja Integrações.
01Como funciona
É a mesma esteira que processa os pedidos da Nuvemshop, da Shopify e dos marketplaces — exposta para quem tem plataforma própria. Do seu lado são quatro chamadas, e só a terceira acontece a cada venda.
Cadastre o lojista, uma vez por loja
Leia o catálogo, uma vez (e quando mudar)
Mande o pedido a cada venda
order_id da 3Print. Com a chave da loja, o pedido espera a sua aprovação no painel antes de ir para a fábrica — ou vai direto, se você pedir.Receba as atualizações
Base URL
https://3print-srv-01.com.br/api/partner/v202Credenciais
Existem dois tipos de chave, e o tipo define se você cadastra lojista ou não:
Chave da loja
Aponta para a sua conta de lojista. O pedido entra nela, e /sellers não existe para você — se chamar, responde 422 dizendo isso. O external_seller_id é opcional e ignorado. A arte de impressão é opcional e o pedido espera a sua aprovação no painel (ver Aprovação e arte).
Chave de parceiro
Para quem tem vários lojistas embaixo (marketplace, plataforma de criadores). Aí sim cada loja é cadastrada uma vez e o external_seller_id acompanha todo pedido. Como os seus lojistas não usam o painel, a arte de impressão é obrigatória e o pedido vai direto para a fábrica.
O time da 3Print emite duas coisas para você, uma única vez:
| Campo | Tipo | Obrig. | Descrição |
|---|---|---|---|
X-3Print-Key | string | sim | A chave da API. Vai no cabeçalho de toda requisição. Começa com ptk_. |
segredo de assinatura | string | não | Usado só para verificar os callbacks que a gente manda. Nunca vai numa requisição sua. Necessário apenas se você usar callback. |
Cabeçalhos de toda chamada:
X-3Print-Key: ptk_sua_chave_aqui
Content-Type: application/json
Accept: application/jsonRotação e revogação
Se a chave vazar (log, repositório, ex-colaborador), avise o suporte. A chave antiga para de funcionar na hora e passa a receber 401. Rotacionar troca também o segredo de assinatura — se você usa callback, atualize os dois no mesmo deploy.
03Convenções
Valores e datas
Valores em reais, número com até 2 casas e ponto decimal (129.90, nunca 129,90). Datas em ISO 8601 com offset (2026-09-03T14:11:48-03:00). Tudo em UTF-8.
Identificadores
Os campos com prefixo external_ são seus — você escolhe o formato. O order_id é da 3Print, inteiro, e vem na resposta da criação. Guarde os dois: os endpoints de leitura aceitam qualquer um.
Idempotência
A chave de idempotência é o external_order_id, dentro do lojista. Reenviar o mesmo pedido — timeout, retry, duplo clique — devolve 200 com duplicate: true e o mesmo order_id. Nada é duplicado e nada é reproduzido.
Reenviar o mesmo id com conteúdo diferente devolve 409. Isso quase sempre significa que dois pedidos da sua plataforma receberam o mesmo número.
Quando reenviar
04Cadastrar o lojista
/sellers responde 422. Esta seção é para quem tem vários lojistas embaixo de si.Cada loja da sua plataforma é um lojista aqui. O external_seller_id é o identificador dela do seu lado, e é o que você repete em todo pedido.
Há dois caminhos, e você escolhe o que encaixa melhor no seu fluxo:
{
"external_seller_id": "77001",
"name": "Loja do Vale",
"email": "contato@lojadovale.com.br",
"document": "12345678000199",
"phone": "17997765724",
"address": {
"street": "Rua Bernardino de Campos",
"number": "1200",
"complement": "Sala 4",
"district": "Centro",
"city": "São José do Rio Preto",
"state": "SP",
"zipcode": "15015000"
}
}Responde 201 quando cadastra e 200 com duplicate: true quando o lojista já existe — recadastrar não é erro, então dá para chamar sem checar antes.
Ou: cadastre junto com o primeiro pedido
Se preferir não gerenciar lojista em separado, mande um bloco seller dentro do próprio pedido. Mesmos campos de cima, sem o external_seller_id (que já vai na raiz do pedido):
{
"external_order_id": "PED-2026-000871",
"external_seller_id": "77001",
"seller": {
"name": "Loja do Vale",
"email": "contato@lojadovale.com.br",
"document": "12345678000199",
"phone": "17997765724",
"address": { "...": "igual ao POST /sellers" }
},
"customer": { "...": "..." },
"items": [ "..." ]
}| Situação | O que acontece |
|---|---|
| Bloco presente, lojista ainda não existe | Criado junto com o pedido, na mesma transação |
| Bloco presente, lojista já existe | Usa o cadastro que está lá e ignora o bloco |
| Bloco ausente, lojista não existe | 404 — o pedido não entra |
external_seller_id digitado errado criaria um lojista vazio e o pedido entraria nele. Você só descobriria semanas depois, com pedidos espalhados por lojistas que não existem. Com a regra acima, o typo devolve 404 na hora.POST /sellers.05Catálogo e gramática de SKU
unit_price), que é só faturamento.Um SKU se lê da esquerda para a direita. Por exemplo Q1CM2030P:
| Trecho | Significa |
|---|---|
Q1 | Composição Avulso (Q2 = Duo, Q3 = Trio) |
CM | Canvas com moldura |
2030 | 20 × 30 cm |
P | Moldura preta |
São sete formatos aceitos. O catálogo devolve todos, junto com as composições, as cores, os tamanhos de tabela e o preço unitário de cada combinação:
curl "https://3print-srv-01.com.br/api/partner/v2/catalog" \
-H "X-3Print-Key: ptk_sua_chave_aqui"{
"success": true,
"data": {
"sku_grammar": {
"composition": [
{ "digit": "1", "name": "Avulso", "pieces": 1, "price_multiplier": 1 },
{ "digit": "2", "name": "Duo", "pieces": 2, "price_multiplier": 2 },
{ "digit": "3", "name": "Trio", "pieces": 3, "price_multiplier": 3 }
],
"colors": {
"P": "Preta", "B": "Branca", "N": "Natural",
"T": "Tabaco", "FJ": "Freijó"
},
"size_format": {
"standard": ["40x60", "50x70", "60x90", "80x120", "100x150",
"40x40", "50x50", "60x60", "80x80", "100x100"],
"custom": "Qualquer medida é aceita. Fora da tabela, o preço é calculado
por área e arredondado para o tier imediatamente maior."
},
"variants": [
{ "pattern": "Q{g}CM{tamanho}{cor}", "frame": "Canvas Com Moldura",
"accepts_color": true, "example": "Q1CM2030P" },
{ "pattern": "Q{g}C{tamanho}", "frame": "Canvas Sem Moldura",
"accepts_color": false, "example": "Q1C4060" },
{ "pattern": "Q{g}F{tamanho}SM", "frame": "Somente Impressão",
"accepts_color": false, "example": "Q1F5070SM" },
{ "pattern": "Q{g}F{tamanho}C{cor}SV", "frame": "Caixa",
"finish": "Sem Vidro", "example": "Q1F4060CPSV" }
]
},
"price_table": {
"currency": "BRL",
"basis": "Preço unitário da composição Avulso. Duo x2, Trio x3.",
"sizes": [
{ "size": "40x60", "variants": [
{ "frame": "Canvas Com Moldura", "finish": null, "unit_price": 0.00 }
]}
]
}
}
}06Simular o pedido
Mesmo corpo do endpoint de criação, mesma validação, mesmos cálculos — só que nada é gravado e nenhuma produção é acionada. Use durante o desenvolvimento e, em produção, para mostrar o custo antes de fechar a venda.
{
"success": true,
"valid": true,
"external_order_id": "PED-2026-000871",
"seller": {
"external_seller_id": "77001",
"name": "Loja do Vale",
"will_be_created": false
},
"items": [
{
"sku": "Q1CM2030P",
"external_item_id": "a7f3-arte-01",
"description": "Quadro Avulso Canvas 20x30 Canvas Com Moldura Preta -",
"quantity": 2,
"unit_price": 0.00,
"line_total": 0.00
}
],
"subtotal": 0.00,
"shipping_cost": 24.90,
"total": 24.90,
"status": "Pendente"
}O status diz onde o pedido vai nascer: Aguardando Aprovação ou Pendente (direto para a fábrica). Ver Aprovação e arte.
/preview — o que passa aqui passa na criação, porque é o mesmo código.07Criar o pedido
external_seller_id e o bloco seller do exemplo abaixo: o pedido é da conta da chave. Se eles vierem, são ignorados — integração que já manda não precisa ser mexida.{
"external_order_id": "PED-2026-000871",
"external_seller_id": "77001",
"shipping_cost": 24.90,
"customer": {
"name": "Marina Prado",
"document": "12345678909",
"email": "marina@exemplo.com.br",
"phone": "17997765724",
"zipcode": "15015000",
"street": "Rua Bernardino de Campos",
"number": "1200",
"complement": "Sala 4",
"district": "Centro",
"city": "São José do Rio Preto",
"state": "SP"
},
"items": [
{
"external_item_id": "a7f3-arte-01",
"sku": "Q1CM2030P",
"quantity": 2,
"unit_price": 149.90,
"print_files": ["https://cdn.suaplataforma.com/arte-01.tif"],
"mockup": "https://cdn.suaplataforma.com/mockup-01.jpg"
}
]
}Campos do pedido
| Campo | Tipo | Obrig. | Descrição |
|---|---|---|---|
external_order_id | string | sim | O número do pedido na sua plataforma. É a chave de idempotência. |
external_seller_id | string | sim | A loja, como cadastrada em POST /sellers. Só na chave de parceiro — com a chave da loja é opcional e ignorado. |
shipping_cost | number | sim | O frete que você cobrou do seu cliente (use 0 se foi grátis). Entra no total do pedido e na nota fiscal. Não é o frete que a 3Print cobra de você nem escolhe a transportadora — ver Frete. |
auto_approve | boolean | não | Só na chave da loja. Padrão false: o pedido para em Aguardando Aprovação no seu painel. Com true, vai direto para a fábrica — e aí todo item precisa de print_files. |
customer | object | sim | Cliente final e endereço de entrega. Todos os campos são obrigatórios, menos complement. |
items | array | sim | Pelo menos um item. |
Campos do item
| Campo | Tipo | Obrig. | Descrição |
|---|---|---|---|
sku | string | sim | O tipo físico do quadro. Ver catálogo. |
quantity | integer | sim | Quantas peças iguais desta mesma arte. |
print_files | string[] | sim | URLs da arte em alta, acessíveis sem autenticação. Obrigatório (pelo menos uma) na chave de parceiro e na chave da loja com auto_approve: true. Na chave da loja sem ele, é opcional: você pode subir a arte pelo painel antes de aprovar. |
unit_price | number | não | Só na chave da loja. O que você cobrou do seu cliente, por unidade — é o que aparece como faturamento no seu painel. Sem ele, o valor do item vira o preço de tabela da 3Print, que não é a sua venda. Não mexe no custo de produção, que sai da sua tabela de preço. |
external_item_id | string | não | Identificador da arte, único por linha. Leia o alerta abaixo. |
mockup | string | não | URL da imagem de capa. Aparece no painel, não é impressa. |
external_item_id distintos. O SKU descreve só o tipo físico e se repete entre itens. Sem essa chave, um pedido com três canvas 20×30 pretos e três artes diferentes vira um produto só — e duas das artes são descartadas silenciosamente. Se você manda uma arte por SKU, pode omitir o campo.Resposta
{
"success": true,
"duplicate": false,
"order_id": 2474,
"external_order_id": "PED-2026-000871",
"subtotal": 0.00,
"shipping_cost": 24.90,
"total": 24.90,
"status": "Aguardando Aprovação"
}201 quando cria, 200 quando é reenvio do mesmo pedido. Guarde o order_id.
422 apontando qual item, corrige e reenvia.08Aprovação e arte de impressão
A regra depende do tipo de chave, porque depende de quem consegue entrar no painel para conferir o pedido e subir a arte.
| Chave | print_files | O pedido nasce em |
|---|---|---|
| Da loja (padrão) | Opcional | Aguardando Aprovação — você confere, sobe a arte que faltar e aprova pelo painel |
| Da loja, com auto_approve: true | Obrigatório em todo item | Pendente — vai direto para a fábrica |
| De parceiro | Obrigatório em todo item | Pendente — vai direto para a fábrica |
Na chave de parceiro a arte é obrigatória porque os seus lojistas não entram no painel da 3Print: um pedido sem arte ficaria parado sem ninguém para completar. Pelo mesmo motivo, não há aprovação manual — o auto_approve é ignorado.
awaiting_payment e nenhum callback é disparado. Se você cancelar por lá, ele não é produzido."auto_approve": true junto com a arte em todo item.09Erros
| Código | Significa | O que fazer |
|---|---|---|
| 201 | Pedido criado | Guarde o order_id. |
| 200 | Pedido já existia | Reenvio idêntico. Traz duplicate: true e o mesmo order_id. Trate como sucesso — porque é. |
| 401 | Chave ausente, inválida ou revogada | Confira o cabeçalho X-3Print-Key. Se estiver certo, a chave foi revogada: fale com o suporte. |
| 404 | Lojista não cadastrado | O external_seller_id não existe nesta parceria. Cadastre em POST /sellers antes. |
| 409 | Mesmo id, conteúdo diferente | Dois pedidos seus receberam o mesmo número. A resposta traz o order_id do que já existe. |
| 422 | Payload ou SKU inválido | A resposta diz qual campo, e no caso de SKU traz também item_index. Arte faltando aparece como items.N.print_files. Nada foi gravado. |
| 5xx | Falha nossa | O único caso em que reenviar o mesmo pedido é a atitude certa. Use backoff exponencial. |
Exemplo de 422 por SKU:
{
"success": false,
"message": "Invalid SKU 'Q1XX9999' on item #1: SKU inválido ou fora do padrão
esperado. See GET /api/partner/v2/catalog for the accepted SKU grammar.",
"sku": "Q1XX9999",
"item_index": 1
}10Receber as atualizações
Cadastre uma URL com o suporte e a 3Print manda um POST a cada mudança relevante do pedido. É o caminho recomendado: sem ele, você precisa consultar em loop para saber que um pedido foi despachado.
{
"event": "order.status_changed",
"sent_at": "2026-09-03T14:11:48-03:00",
"order": {
"order_id": 2474,
"external_order_id": "PED-2026-000871",
"status": "shipped",
"status_label": "Enviado",
"tracking_code": "BR123456789BR",
"carrier": "Correios",
"updated_at": "2026-09-03T14:11:47-03:00"
}
}Verifique a assinatura
Todo callback vai assinado no cabeçalho Signature: HMAC-SHA256 do corpo bruto com o seu segredo. Confira antes de processar — é o que garante que o aviso veio da 3Print.
$esperado = hash_hmac('sha256', $corpoBruto, $seuSegredo);
if (! hash_equals($esperado, $request->header('Signature'))) {
abort(401);
}Status possíveis
O campo status usa um vocabulário fixo — o mesmo do endpoint de consulta, para você não precisar de dois dicionários. O status_label traz o texto interno da fábrica e pode mudar; não construa lógica em cima dele.
| status | Significa |
|---|---|
awaiting_payment | Aguardando aprovação no painel (chave da loja) — ainda não é produção. Só aparece na consulta; não gera callback |
processing | Aceito, na fila ou em produção |
ready_to_ship | Pronto para coleta |
shipped | Despachado — a etiqueta saiu |
in_transit | Coletado pela transportadora |
delivered | Entregue |
returned | Devolvido ou não entregue |
canceled | Cancelado |
order_id mais o status identificam o evento.shipped e outro quando o código de rastreio fica disponível. Nem sempre eles saem juntos.11Consultar o pedido
Todos aceitam o order_id da 3Print ou o seu external_order_id. Você só enxerga os seus pedidos.
{
"success": true,
"order_id": 2474,
"external_order_id": "PED-2026-000871",
"status": "shipped",
"status_label": "Enviado",
"status_3print": "Coletado",
"approved_at": "2026-09-01T09:22:10-03:00",
"tracking_code": "BR123456789BR"
}Código de rastreio, transportadora e, quando existe página pública, a URL de acompanhamento.
Dados da NF-e: número, série, chave de acesso e link do DANFE. A nota é emitida quando o pedido entra em despacho — antes disso os campos vêm nulos.
external_order_id existir em duas lojas suas, a consulta por ele devolve 409 com a lista de candidatos, em vez de arriscar mostrar o rastreio do pedido errado ao cliente final. Consulte pelo order_id para desempatar.12Frete
Existem dois fretes num pedido, e só um deles vem de você:
| Frete | De onde vem | Para que serve |
|---|---|---|
| Cobrado do cliente (shipping_cost) | Você informa no pedido | Total do pedido, faturamento no seu painel e valor de frete da nota fiscal |
| Custo de envio | A 3Print calcula na aprovação: a opção mais barata da cotação para o CEP do cliente, com a sua tabela de frete | É o frete que entra na sua cobrança da 3Print |
Ou seja: com a chave da loja, um shipping_cost "errado" não muda o que a 3Print cobra de você pelo envio — só o que aparece como frete da venda e na nota. Mande o valor que o seu cliente pagou.
shipping_cost que você informa — então ele precisa ser o valor da cotação. Na dúvida, fale com o suporte.Cotação de frete
Cotação antes de fechar a venda, a partir do CEP de destino e dos itens do carrinho. Devolve as opções disponíveis com prazo e valor, já com a sua tabela de frete — é a mesma conta do custo de envio. Use o value da opção que você mostrou ao cliente como shipping_cost do pedido. Os SKUs seguem o mesmo padrão do catálogo.
Requisição
{
"zipcode": "01310-100",
"items": [
{ "sku": "Q1CM2030P", "quantity": 2 },
{ "sku": "Q1C4060" }
]
}| Campo | Tipo | Obrig. | Descrição |
|---|---|---|---|
zipcode | string | sim | CEP de destino. Aceita com ou sem traço. |
items | array | sim | Itens do carrinho. |
items[].sku | string | sim | SKU no padrão do catálogo. SKU fora do padrão faz a cotação inteira voltar 422. |
items[].quantity | number | não | Quantidade. Padrão: 1. |
Resposta
{
"success": true,
"shipments": [
{ "id": 1, "description": "PAC", "value": 32.5, "time": 9, "weight": 0, "totalValue": 0 },
{ "id": 2, "description": "SEDEX", "value": 54.9, "time": 4, "weight": 0, "totalValue": 0 }
]
}| Campo | Tipo | Obrig. | Descrição |
|---|---|---|---|
shipments[].id | integer | sim | Posição da opção na lista desta resposta (1, 2, 3…). Não é um identificador estável — não guarde. |
shipments[].description | string | sim | Nome da modalidade, para exibir ao comprador. |
shipments[].value | number | sim | Valor do frete em reais. |
shipments[].time | integer | sim | Prazo de entrega em dias. |
weight / totalValue | number | não | Sempre 0. Existem só por compatibilidade — ignore. |
fallback | boolean | não | Só aparece quando a cotação em tempo real está fora: vem true com uma única opção "Frete estimado", calculada por tabela com margem de segurança. |
As opções vêm ordenadas da mais barata para a mais cara.
Erros
CEP inválido, SKU fora do padrão ou nenhuma transportadora disponível voltam 422:
{
"success": false,
"error": "API de frete não retornou opções"
}422 (ou, se a cotação em tempo real estiver fora, uma estimativa com fallback). Trate esse caso no seu checkout em vez de assumir que sempre vem pelo menos uma opção.13Limites e suporte
| Campo | Tipo | Obrig. | Descrição |
|---|---|---|---|
Tamanho do corpo | 1 MB | não | Pedido com muitos itens pode precisar ser dividido. |
Arquivos de impressão | URL pública | não | Precisam responder sem autenticação enquanto o pedido não for produzido. Nós baixamos e re-hospedamos, mas não imediatamente. |
Ambiente de teste | não há | não | Use /orders/preview. Pedido criado é produzido de verdade. |
Versionamento | /v2 | não | A versão está na URL. Existe uma v1 anterior, sem prefixo, que continua no ar para quem já a usa — se você está integrando agora, é a v2. Mudança incompatível viraria /v3, com a anterior mantida e aviso por e-mail antes. |
Antes de subir para produção
- Rodou a integração inteira contra
/orders/preview? - Trata 4xx sem reenviar e 5xx com backoff?
- Guarda o
order_idjunto do seu número de pedido? - Verifica a assinatura dos callbacks?
- Com a chave da loja: decidiu entre aprovar pelo painel (padrão) e
auto_approvecom arte em todo item? - Manda
external_item_iddistinto quando duas artes compartilham o SKU?
external_order_id, o horário aproximado e o corpo da resposta que você recebeu.