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.

1

Cadastre o lojista, uma vez por loja

Cada loja da sua plataforma vira um lojista na 3Print. É o que amarra o pedido à origem certa e o que separa os seus dados dos de outros parceiros.
2

Leia o catálogo, uma vez (e quando mudar)

O catálogo devolve a gramática de SKU e o preço unitário de cada combinação. É o de/para entre o seu produto e o que a fábrica sabe produzir.
3

Mande o pedido a cada venda

Um POST com cliente, endereço e itens. A resposta traz o 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.
4

Receba as atualizações

Cadastre uma URL de callback e a gente avisa a cada mudança de status, com assinatura. Sem callback, dá para consultar por polling.

Base URL

HTTP
https://3print-srv-01.com.br/api/partner/v2
Não existe ambiente de homologação. Pedido criado aqui entra na esteira física e é produzido. Para desenvolver e validar a integração sem gerar produção, use o endpoint de simulação: ele valida o payload inteiro e devolve os totais sem gravar nada.

02Credenciais

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:

CampoTipoObrig.Descrição
X-3Print-KeystringsimA chave da API. Vai no cabeçalho de toda requisição. Começa com ptk_.
segredo de assinaturastringnãoUsado 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:

HTTP
X-3Print-Key:  ptk_sua_chave_aqui
Content-Type:  application/json
Accept:        application/json

Rotaçã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.

Só do seu back-end. A chave dá acesso a criar pedidos e ler dados de clientes finais. Nunca em código de front-end, repositório público ou log de aplicação.

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

4xx é com você, 5xx é com a gente. Resposta na faixa 4xx significa que alguma coisa no pedido precisa mudar — reenviar igual vai dar o mesmo erro. Só reenvie em 5xx ou timeout, e aí pode reenviar à vontade: a idempotência protege.

04Cadastrar o lojista

Tem uma loja só? Pule esta seção inteira. Com a chave da loja o pedido já entra na sua conta, e chamar /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:

POST/sellers
JSON
{
  "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):

JSON
{
  "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çãoO que acontece
Bloco presente, lojista ainda não existeCriado junto com o pedido, na mesma transação
Bloco presente, lojista já existeUsa o cadastro que está lá e ignora o bloco
Bloco ausente, lojista não existe404 — o pedido não entra
Criar exige mandar os dados — só o id nunca cria nada. É de propósito: se fosse automático, um 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.
O bloco não atualiza cadastro existente. Mandar dados diferentes para um lojista que já existe não muda nada — o endereço dele não pode mudar sozinho a cada venda. Para atualizar, use POST /sellers.

06Simular o pedido

POST/orders/preview

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.

JSON
{
  "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.

É o substituto do ambiente de homologação. Rode a sua suíte de integração inteira contra o /preview — o que passa aqui passa na criação, porque é o mesmo código.

07Criar o pedido

POST/orders
Com a chave da loja, tire o 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.
JSON
{
  "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

CampoTipoObrig.Descrição
external_order_idstringsimO número do pedido na sua plataforma. É a chave de idempotência.
external_seller_idstringsimA loja, como cadastrada em POST /sellers. Só na chave de parceiro — com a chave da loja é opcional e ignorado.
shipping_costnumbersimO 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_approvebooleannãoSó 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.
customerobjectsimCliente final e endereço de entrega. Todos os campos são obrigatórios, menos complement.
itemsarraysimPelo menos um item.

Campos do item

CampoTipoObrig.Descrição
skustringsimO tipo físico do quadro. Ver catálogo.
quantityintegersimQuantas peças iguais desta mesma arte.
print_filesstring[]simURLs 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_pricenumbernãoSó 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_idstringnãoIdentificador da arte, único por linha. Leia o alerta abaixo.
mockupstringnãoURL da imagem de capa. Aparece no painel, não é impressa.
Duas artes diferentes com o mesmo SKU precisam de 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

JSON
{
  "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.

Ou o pedido inteiro entra, ou nada entra. Todos os SKUs são resolvidos antes de qualquer gravação. Um item inválido no meio da lista não deixa pedido pela metade — você recebe 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.

Chaveprint_filesO pedido nasce em
Da loja (padrão)OpcionalAguardando Aprovação — você confere, sobe a arte que faltar e aprova pelo painel
Da loja, com auto_approve: trueObrigatório em todo itemPendente — vai direto para a fábrica
De parceiroObrigatório em todo itemPendente — 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.

Pedido em Aguardando Aprovação ainda não é produção. Ele aparece em Vendas no seu painel; a fábrica só recebe quando você aprova. Enquanto isso, a consulta de status devolve awaiting_payment e nenhum callback é disparado. Se você cancelar por lá, ele não é produzido.
Mudança de comportamento na chave da loja. Até setembro de 2026 todo pedido ia direto para a fábrica. Se a sua integração conta com isso, mande "auto_approve": true junto com a arte em todo item.

09Erros

CódigoSignificaO que fazer
201Pedido criadoGuarde o order_id.
200Pedido já existiaReenvio idêntico. Traz duplicate: true e o mesmo order_id. Trate como sucesso — porque é.
401Chave ausente, inválida ou revogadaConfira o cabeçalho X-3Print-Key. Se estiver certo, a chave foi revogada: fale com o suporte.
404Lojista não cadastradoO external_seller_id não existe nesta parceria. Cadastre em POST /sellers antes.
409Mesmo id, conteúdo diferenteDois pedidos seus receberam o mesmo número. A resposta traz o order_id do que já existe.
422Payload ou SKU inválidoA 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.
5xxFalha nossaO único caso em que reenviar o mesmo pedido é a atitude certa. Use backoff exponencial.

Exemplo de 422 por SKU:

JSON
{
  "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.

JSON
{
  "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.

PHP
$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.

statusSignifica
awaiting_paymentAguardando aprovação no painel (chave da loja) — ainda não é produção. Só aparece na consulta; não gera callback
processingAceito, na fila ou em produção
ready_to_shipPronto para coleta
shippedDespachado — a etiqueta saiu
in_transitColetado pela transportadora
deliveredEntregue
returnedDevolvido ou não entregue
canceledCancelado
Responda 2xx rápido e processe depois. Callback que falha é reenviado com backoff exponencial, então processamento lento vira reentrega. E trate o mesmo evento chegando duas vezes: o order_id mais o status identificam o evento.
Pedido despachado normalmente gera dois avisos: um em 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.

GET/orders/{id}/status
JSON
{
  "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"
}
GET/orders/{id}/tracking

Código de rastreio, transportadora e, quando existe página pública, a URL de acompanhamento.

GET/orders/{id}/invoice

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.

Se o mesmo 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ê:

FreteDe onde vemPara que serve
Cobrado do cliente (shipping_cost)Você informa no pedidoTotal do pedido, faturamento no seu painel e valor de frete da nota fiscal
Custo de envioA 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.

A transportadora não é escolhida no pedido. Quem decide é a 3Print, na expedição, pela melhor opção para o destino (Mandaê, Jadlog e outras) — do mesmo jeito que nos pedidos da Nuvemshop ou da Shopify. O pedido não tem campo de transportadora, e escolher uma opção na cotação abaixo não reserva essa opção.
Chave de parceiro: depende do contrato. No modelo de cobrança consolidada (uma cobrança para todos os seus lojistas), o custo de envio cobrado é o próprio 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

POST/shipping/quote

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

JSON
{
  "zipcode": "01310-100",
  "items": [
    { "sku": "Q1CM2030P", "quantity": 2 },
    { "sku": "Q1C4060" }
  ]
}
CampoTipoObrig.Descrição
zipcodestringsimCEP de destino. Aceita com ou sem traço.
itemsarraysimItens do carrinho.
items[].skustringsimSKU no padrão do catálogo. SKU fora do padrão faz a cotação inteira voltar 422.
items[].quantitynumbernãoQuantidade. Padrão: 1.

Resposta

JSON
{
  "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 }
  ]
}
CampoTipoObrig.Descrição
shipments[].idintegersimPosição da opção na lista desta resposta (1, 2, 3…). Não é um identificador estável — não guarde.
shipments[].descriptionstringsimNome da modalidade, para exibir ao comprador.
shipments[].valuenumbersimValor do frete em reais.
shipments[].timeintegersimPrazo de entrega em dias.
weight / totalValuenumbernãoSempre 0. Existem só por compatibilidade — ignore.
fallbackbooleannãoSó 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:

JSON
{
  "success": false,
  "error": "API de frete não retornou opções"
}
Carrinho acima de 50 kg não recebe opções. Nenhuma transportadora atende, e a cotação volta 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

CampoTipoObrig.Descrição
Tamanho do corpo1 MBnãoPedido com muitos itens pode precisar ser dividido.
Arquivos de impressãoURL públicanãoPrecisam responder sem autenticação enquanto o pedido não for produzido. Nós baixamos e re-hospedamos, mas não imediatamente.
Ambiente de testenão hánãoUse /orders/preview. Pedido criado é produzido de verdade.
Versionamento/v2nãoA 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_id junto 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_approve com arte em todo item?
  • Manda external_item_id distinto quando duas artes compartilham o SKU?
Suporte técnico da integração: WhatsApp (17) 99776-5724. Ao relatar um problema, mande o external_order_id, o horário aproximado e o corpo da resposta que você recebeu.