API do fornecedor: pegar pedidos manuais sem disputar com você mesmo
O endpoint que a maioria das integrações usa para buscar trabalho entrega o mesmo pedido para todos os processos que perguntarem. Existe um que reserva o pedido de forma atômica, uma trava de trinta minutos que só funciona se você acionar, e dois limites de requisição que ninguém documentou. O laço que aguenta carga.
Sarah JohnsonA primeira decisão de uma integração de entrega é qual endpoint recolhe o trabalho, e é a decisão que quase todo mundo erra. GET /orders é leitura. Ele devolve os pedidos que casam com o filtro para quem perguntar, e não altera nada. Rode dois processos, ou um processo com nova tentativa, e os dois recebem o mesmo pedido e os dois tentam entregar.
Quem reserva trabalho é POST /orders-pull. Ele seleciona os seus pedidos manuais pendentes, vira cada um de pendente para em processamento com uma comparação e troca, e devolve somente aqueles cuja troca realmente aconteceu. Se um segundo processo pedir um milissegundo depois, a comparação falha e aquele pedido não vem na resposta dele.
Uma assimetria que vale saber antes de comparar respostas: orders-pull só considera pedido com pagamento concluído, enquanto GET /orders, sem parâmetro, traz pagamento concluído ou parcial. Os dois endpoints não enxergam o mesmo conjunto, e isso não é bug.
O laço, na ordem em que deve rodar
- Reservar.
POST /orders-pull. Corpo opcional comlimit, padrão 100, teto rígido de 500. Mais antigo primeiro. - Travar, se o seu processo for lento.
PATCH /orders/{id}/last-process-timesem corpo carimba agora e esconde o pedido da sua própria consulta por trinta minutos. - Entregar.
POST /orders-updatecom as linhas de credencial, ouPOST /orders/{id}/accounts-bulkpara volume grande, que devolve um identificador de tarefa para você consultar depois. - Fechar. A mesma chamada de
orders-updatelevastatus: "completed", ou"partial"se você preencheu só uma parte.
A autenticação é um cabeçalho em toda chamada, e existem duas formas aceitas:
curl -X POST "https://api.hstockplus.com/api/admin/v2/orders-pull" \
-H "X-Api-Key: SUA_CHAVE" \
-H "Content-Type: application/json" \
-d '{"limit": 50}'
# equivalente
curl -X POST "https://api.hstockplus.com/api/admin/v2/orders-pull" \
-H "Authorization: Bearer SUA_CHAVE" \
-H "Content-Type: application/json" \
-d '{"limit": 50}'
A chave é gerada na página de configurações de API do seu painel. Cabeçalho ausente devolve API_KEY_REQUIRED; chave errada devolve INVALID_API_KEY. São códigos diferentes e apontam para erros diferentes: o primeiro é o seu cliente não montando o cabeçalho, o segundo é a chave.
A janela de trinta minutos é uma trava, e ela é opcional
Os dois endpoints de listagem pulam qualquer pedido cujo lastProcessTime esteja dentro dos últimos trinta minutos. Isso parece deduplicação automática e não é, porque nenhum dos dois escreve o campo. Quem escreve é você, por PATCH /orders/{id}/last-process-time.
Então é uma trava cooperativa. Carimbe quando pegar o pedido e ele some da sua próxima consulta por meia hora, que é o que você quer quando a entrega demora e o seu processo roda de minuto em minuto. Envie um instante em formato ISO para definir outro vencimento, ou null para liberar na hora. Ignore o endpoint e a janela nunca se aplica a você.
O orders-pull já protege contra reserva dupla pela troca de status. A trava serve para a outra falha: o seu processo pegou, ainda está trabalhando, e você não quer ver aquilo de novo enquanto isso.
Lendo pedidos, e os padrões de filtro que surpreendem
GET /orders continua sendo o endpoint certo para conciliação, recuperação de histórico e painéis. Dois padrões dele valem conhecer antes de construir em cima.
- O status de pagamento tem dois valores por padrão, não um. Sem o parâmetro
paymentStatusvocê recebe pedidos com pagamento concluído ou parcial. Parcial significa parcialmente reembolsado, e esses pedidos ainda são trabalho vivo. O parâmetro aceita lista separada por vírgula entre pending, completed, failed, refunded, processing e partial. - O tipo de produto é opcional e aceita lista. Ele não é obrigatório para buscar pedidos manuais. Aceita
manual,inventoryeautoseparados por vírgula, entãoproductType=manual,autoé válido, e omitir traz todos os seus tipos.
O filtro de status de pedido aceita pending, processing, completed, refunded e error. partial não está aí: é status de pagamento. Os demais filtros são entityType como product ou smm_service, subCategory por nome, createdFrom e createdTo em formato ISO, limit até 500 e offset.
curl -X GET "https://api.hstockplus.com/api/admin/v2/orders\
?status=pending,processing&productType=manual&paymentStatus=completed,partial&limit=50" \
-H "X-Api-Key: SUA_CHAVE"
O que acontece com as linhas de credencial que você envia
É daqui que vem a maior parte dos chamados sobre a API, porque três transformações rodam antes de qualquer coisa ser gravada e nenhuma delas aparece na resposta.
- Todo elemento é quebrado por quebra de linha. Um único item do array pode carregar um arquivo inteiro.
["a:1\nb:2\nc:3"]e["a:1","b:2","c:3"]são o mesmo envio. Linhas vazias e espaços nas pontas somem. Importante: a barra vertical não é separador de linha, ela é parte do formato da credencial e sobrevive intacta. - Repetições são descartadas, sem diferenciar maiúsculas de minúsculas. Dentro do lote e contra tudo que já foi entregue naquele pedido. Reenviar o mesmo lote depois de um tempo esgotado é seguro e não entrega nada duas vezes.
- Linhas além da quantidade do pedido são descartadas em silêncio. O envio é cortado nas vagas restantes. Não dá erro, então mande a contagem certa.
O conteúdo de cada linha é problema seu. A plataforma guarda como um texto opaco e nunca interpreta, então um par separado por dois pontos, uma trinca com o segredo de duas etapas, um token ou um bloco de sessão são todos apenas texto. O que a descrição do seu anúncio prometeu é o que o comprador vai esperar encontrar ali, e nada no sistema confere isso por você.
Como as repetições contra entregas anteriores são descartadas em vez de recusadas, entregar em partes é um caminho suportado. Mande o que você tem, marque o pedido como partial, mande o resto depois e só então marque completed.
Dois limites de requisição
orders-update tem intervalo mínimo por chave de API, 200 milissegundos por padrão, e um teto de duas requisições simultâneas por usuário. Estourar qualquer um dos dois devolve HTTP 429 com o atraso já calculado no corpo:
{ "error": "RATE_LIMIT", "message": "Too many order update requests",
"retry_after_ms": 137 }
{ "error": "CONCURRENCY_LIMIT", "message": "Too many concurrent sync requests",
"retry_after_ms": 5000 }
Leia retry_after_ms e durma exatamente aquilo. Espera fixa vai ser lenta demais para o caso dos 200 milissegundos ou rápida demais para o caso de concorrência. Existe ainda uma trava por pedido: duas entregas correndo no mesmo pedido devolvem ORDER_DELIVERY_BUSY, também 429, e a resposta certa é repetir depois de um instante, não reenviar credenciais diferentes.
Códigos de erro que merecem tratamento separado
API_KEY_REQUIREDeINVALID_API_KEY: cabeçalho ausente contra chave desconhecida.ORDER_NOT_FOUND: o identificador não está no cache. Um pedido automático recém-criado pode demorar um instante para ficar visível a um fornecedor comum, então um 404 segundos após a criação não é necessariamente definitivo.ACCESS_DENIED: o pedido não tem nenhuma linha sua.PAYMENT_NOT_COMPLETED: não pago ou totalmente reembolsado. Não dá para entregar e não vale repetir.INVALID_ACCOUNTS, mensagem "No valid accounts provided": o array ficou vazio depois da quebra por linha. Quase sempre é texto vazio ou elementos que não são texto.RATE_LIMIT,CONCURRENCY_LIMITeORDER_DELIVERY_BUSY: espere e repita a mesma requisição.
Dois campos para o seu próprio controle
orders-update aceita external_id para a sua referência interna, e aceita supplierOrderId como apelido do mesmo campo, se for esse o formato que o seu painel já envia. Aceita também external_price, um número registrando quanto o pedido custou a você na origem. Esse valor fica guardado para consulta e não entra em nenhum total da plataforma. Enviar null ou texto vazio mantém o que já estava lá, então atualizações repetidas não apagam o histórico.
O que manual quer dizer, e o quanto ele é rápido de verdade
Tipo manual significa que o fornecedor sobe a mercadoria depois que o pedido chega. Não é configuração de velocidade, e os números deixam isso claro.
Nos 6.500 pedidos manuais concluídos com data de conclusão registrada, a mediana entre pagamento e conclusão é de menos de trinta segundos, mais de três quartos fecham dentro de dez minutos, e cerca de um em cada quarenta e três passa de um dia. Pedidos de estoque pré-carregado são praticamente instantâneos, com mediana abaixo de um segundo. Pedidos automáticos ficam entre os dois, com mediana perto de meio minuto e nove em cada dez dentro de poucos minutos.
A razão de manual parecer quase tão rápido é exatamente esta página: os fornecedores com volume nesse tipo rodam esta API em consulta curta em vez de olhar um painel. Se a sua integração acrescenta minutos, você é a cauda lenta, não a norma. A cauda é onde a diferença mora: um pedido manual em quarenta e três passa de um dia, contra praticamente nenhum no estoque pré-carregado.
Um relógio à parte, se você vende serviço de crescimento em vez de conta: o pedido de serviço recebe exatamente três dias de pós-venda contados da entrega, independentemente do que o anúncio diga. Anúncio de conta carrega a garantia própria dele, contada também da entrega e não do pagamento, então pedido que ficou um dia em processamento não perdeu nada da janela.
Um processo que se comporta
const BASE = 'https://api.hstockplus.com/api/admin/v2';
const HEAD = { 'X-Api-Key': process.env.API_KEY, 'Content-Type': 'application/json' };
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
async function call(method, path, body) {
const res = await fetch(BASE + path, {
method,
headers: HEAD,
body: body ? JSON.stringify(body) : undefined,
});
const json = await res.json().catch(() => ({}));
if (res.status === 429) {
await sleep(json.retry_after_ms ?? 1000);
return call(method, path, body);
}
if (!res.ok) throw Object.assign(new Error(json.message || res.status), { code: json.error });
return json;
}
async function tick() {
const { orders = [] } = await call('POST', '/orders-pull', { limit: 50 });
for (const order of orders) {
// O pedido ja esta em processamento e e seu. Trave se voce for lento.
await call('PATCH', '/orders/' + order.id + '/last-process-time', {});
const accounts = await prepararCredenciais(order); // seu sistema
await call('POST', '/orders-update', {
order: order.id,
status: accounts.length >= order.quantity ? 'completed' : 'partial',
accounts,
supplierOrderId: 'SUP-' + order.id,
});
}
}
Três coisas que esse laço faz e que a forma antiga não fazia: ele reserva em vez de ler, respeita o atraso que o próprio servidor calculou em vez de chutar um, e reporta partial honestamente em vez de fechar um pedido que não conseguiu preencher.
A referência completa de endpoints, incluindo criação de produto, chamados, reembolsos e a tarefa em lote de contas, está na página da API do fornecedor. Se você ainda está decidindo se um anúncio deve ser manual, de estoque ou automático, essa escolha está no guia de cadastro de produto, e o cadastro de serviços de crescimento está no guia do painel SMM.



