Ações em lote
Desativa ou reativa muitos usuários de uma vez, por critério, com simulação e confirmação obrigatórias.
Use o lote quando a mesma ação vale para muitos cadastros: desativar quem não compra há dois anos, reativar um grupo que foi desativado por engano. Em vez de uma chamada por usuário, você descreve o recorte (um filtro) e a plataforma resolve a lista no servidor.
Há duas ações: deactivate (status D) e activate (volta para A). Excluir cadastros em massa não é oferecido. activate reativa somente quem está desativado; cadastro pendente é aprovado um a um (Alterar cliente).
O fluxo em dois passos
O lote nunca executa de primeira. Ele exige que você simule, olhe a contagem e a amostra, e só então confirme:
- Simule
Chame a rota com o seu
filtere"simulate": true. A resposta traztotal(quantos usuários o recorte alcança),sample(até 10 exemplos, com o status de antes e de depois) e umconfirm_token. - Confira o recorte
Mostre o total e a amostra a quem decide. Se o número não é o esperado, ajuste o filtro e simule de novo.
- Confirme
Repita o mesmo corpo, sem
simulate, com oconfirm_tokenrecebido. Se o recorte mudou desde a simulação (cresceu), o token é recusado e você precisa simular de novo. O token tem validade: o campoexpires_atda simulação diz até quando.
/api/v1/admin/users/bulkAdminDesativa ou reativa usuários em lote
Exige manage_users. Sem simulação prévia, a resposta é 428.
Corpo da requisição
actionstringobrigatóriodeactivateactivatedeactivate desativa quem o filtro alcança (e ainda não está desativado). activate reativa só os desativados que o filtro alcança.filterobjectO recorte. Todas as chaves são opcionais e se combinam (E lógico), mas é preciso informar pelo menos uma (ou ids).
user_typestringcustomervendoradmincurrent_statusstringactivedisabledpendingADPstatus é sinônimo. Com activate, só disabled (ou nada) é aceito.without_orders_for_daysintegernever_orderedbooleantrue: nunca fez pedido.person_typestringPFPJhas_cnpjbooleantrue: com CNPJ.email_containsstringname_containsstringlast_login_beforestringAAAA-MM-DD). Quem nunca entrou é medido pela data de cadastro.email_verifiedbooleanhas_limitbooleantrue: já tem limite de crédito.created_afterstringAAAA-MM-DD).created_beforestringAAAA-MM-DD).idsarrayexclude_idsarrayallbooleantrue: a base inteira. Use só quando a pessoa pediu "todos" de forma explícita.idsarrayfilter. Também passa por simulação e confirmação.simulatebooleantrue: só simula, não altera nada.confirm_tokenstringExemplo de requisição
Simulação seguida de execução:
# 1. Simular
curl -X POST "$BASE_URL/api/v1/admin/users/bulk" \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{
"action": "deactivate",
"filter": {"user_type": "customer", "current_status": "active", "without_orders_for_days": 730},
"simulate": true
}'
# 2. Executar, com o confirm_token da resposta anterior
curl -X POST "$BASE_URL/api/v1/admin/users/bulk" \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{
"action": "deactivate",
"filter": {"user_type": "customer", "current_status": "active", "without_orders_for_days": 730},
"confirm_token": "eyJ0IjoiZGZl..."
}'Resposta
{
"simulate": true,
"action": "deactivate",
"description": "Desativar cadastros",
"total": 1,
"sample": [
{ "id": "3dc023ca-3746-4b6e-8afd-686a4a9af99f", "name": "Padaria Pão Doce Ltda", "before": "A", "after": "D" }
],
"confirm_token": "eyJ0IjoiZGZlMjU1NWUtYmZkMC00MWE4...",
"expires_at": "2026-10-11T22:07:16.652274129Z",
"async": false,
"by": "filter"
}{
"action": "deactivate",
"by": "filter",
"status": "done",
"total_requested": 1,
"affected": 1,
"failed": []
}{
"action": "deactivate",
"by": "filter",
"status": "running",
"total_requested": 1200,
"async": true,
"job": { "id": "b1f0...", "status": "running", "total": 1200, "processed": 0, "affected": 0, "failed": 0, "percent": 0 }
}Campos da resposta
totalintegersamplearrayid, name, before (status atual) e after (status depois do lote).confirm_tokenstringexpires_atstringasyncbooleantrue quando a execução vai rodar como tarefa em segundo plano. Recortes de 50 usuários ou mais, ou uma fila ocupada, viram tarefa.affectedintegerfailedarrayid e error. Lista vazia quando tudo deu certo. Se houver falhas, a resposta traz também partial_failure: true e message.jobobjectid, status, total, processed, affected, failed, percent. Acompanhe com GET /api/v1/admin/bulk-jobs/{id} até o status terminar.Erros
| Status | Quando | Corpo |
|---|---|---|
| 400 | action diferente de deactivate e activate | {"error":"action must be deactivate or activate","detail":"action aceita deactivate ... Excluir cadastro em massa por filtro não é oferecido"} |
| 400 | Sem filter e sem ids | {"error":"filter is required","detail":"diga o recorte: user_type, current_status, without_orders_for_days (ou ids)"} |
| 400 | Chave desconhecida no filtro | {"error":"invalid_filter","detail":"filtro de lote inválido: chave \"foo\" não existe no filtro de usuários. Chaves aceitas: ..."} (a lista de chaves vem na mensagem) |
| 400 | activate com current_status diferente de disabled | {"error":"invalid_filter","detail":"com action activate, current_status só pode ser disabled ..."} |
| 401 | Sem credenciais ou token expirado | {"error":"invalid or expired token"} |
| 403 | Falta manage_users | {"error":"insufficient privileges","required":"manage_users"} |
| 409 | confirm_token de outro pedido, ou o recorte cresceu desde a simulação | {"error":"confirm_token_invalid","detail":"..."} |
| 428 | Executou sem simular antes | {"error":"simulate_required","detail":"lote por filtro exige simulação: chame a mesma rota com o mesmo corpo e \"simulate\": true, ..."} |
Mensagens de detail em português voltam como a API devolve.