Pular para o conteúdo

Criar venda com itens

Uma venda com dois itens, um produto e um serviço, criada em uma única requisição a POST /v1/sales e protegida por Idempotency-Key.

No caminho você vê onde encontrar os identificadores que o corpo exige, como o servidor calcula subtotal e total_amount, com que status a venda nasce e como conferir o histórico de status depois.

  • Uma chave de API com o escopo sales:write e o módulo vendas habilitado no contrato. Confira os dois em GET /v1/me.
  • Pelo menos um item de catálogo cadastrado, produto ou serviço. A venda aponta para ele por catalog_item_id.
  • Um cliente, se você quiser vincular a venda a alguém. customer_id é opcional: venda de balcão pode ir sem cliente.
Terminal window
export LIGGA_API_KEY='ligga_live_…'
export LIGGA_BASE='https://api.ligga.app/functions/v1/api'
  1. Confirme escopo e módulo. Sem sales:write a resposta é 403 insufficient_scope; sem o módulo vendas no contrato, é 403 module_not_enabled:

    Terminal window
    curl "$LIGGA_BASE/v1/me" \
    -H "Authorization: Bearer $LIGGA_API_KEY"
  2. Encontre o cliente. Busque pelo dado que você já tem e guarde o id:

    Terminal window
    curl -G "$LIGGA_BASE/v1/customers" \
    -H "Authorization: Bearer $LIGGA_API_KEY" \
    --data-urlencode 'filter[email][eq]=maria@exemplo.com'
  3. Encontre os itens do catálogo. Produtos e serviços são recursos separados, e o catalog_type de cada item da venda diz de qual dos dois o catalog_item_id veio:

    Terminal window
    curl -G "$LIGGA_BASE/v1/products" \
    -H "Authorization: Bearer $LIGGA_API_KEY" \
    --data-urlencode 'filter[name][ilike]=%ração%'
    curl -G "$LIGGA_BASE/v1/services" \
    -H "Authorization: Bearer $LIGGA_API_KEY" \
    --data-urlencode 'filter[name][ilike]=%banho%'
  4. Escolha a chave de idempotência. Use o identificador que o seu sistema já dá ao pedido, não um valor sorteado a cada tentativa. É ele que garante que uma nova tentativa não vire uma segunda venda.

  5. Crie a venda. Os itens vão aninhados no mesmo corpo. A resposta é 201 Created e já traz items:

    Terminal window
    curl -X POST "$LIGGA_BASE/v1/sales" \
    -H "Authorization: Bearer $LIGGA_API_KEY" \
    -H "Content-Type: application/json" \
    -H "Idempotency-Key: pedido-loja-8842" \
    -d '{
    "customer_id": "9c8d7e6f-1a2b-4c3d-8e9f-0a1b2c3d4e5f",
    "transaction_type": "sale",
    "payment_method": "pix",
    "payment_status": "pending",
    "items": [
    {
    "catalog_type": "product",
    "catalog_item_id": "b7e1f3a2-8c4d-4e5f-9a6b-1c2d3e4f5a6b",
    "quantity": 2,
    "unit_price": 49.9
    },
    {
    "catalog_type": "service",
    "catalog_item_id": "0d9e8f7a-6b5c-4d3e-2f1a-9b8c7d6e5f4a",
    "quantity": 1,
    "unit_price": 90,
    "discount_amount": 10
    }
    ]
    }'
    {
    "data": {
    "id": "3f9a1b7e-2c4d-4e8f-b6a1-5d0c9e8f7a2b",
    "customer_id": "9c8d7e6f-1a2b-4c3d-8e9f-0a1b2c3d4e5f",
    "transaction_type": "sale",
    "transaction_number": "000231",
    "status": "pending",
    "sale_date": "2026-07-09T18:40:00.000Z",
    "delivery_date": null,
    "subtotal": 189.8,
    "discount_amount": null,
    "discount_percentage": null,
    "tax_amount": null,
    "total_amount": 179.8,
    "notes": null,
    "payment_method": "pix",
    "payment_status": "pending",
    "delivery_fee": null,
    "order_code": null,
    "is_active": true,
    "created_at": "2026-07-09T18:40:02.000Z",
    "updated_at": null,
    "items": [
    {
    "id": "5b1c9a7d-3e2f-4a8b-9c0d-1e2f3a4b5c6d",
    "catalog_type": "product",
    "catalog_item_id": "b7e1f3a2-8c4d-4e5f-9a6b-1c2d3e4f5a6b",
    "quantity": 2,
    "unit_price": 49.9,
    "discount_amount": null,
    "discount_percentage": null,
    "subtotal": 99.8,
    "total_amount": 99.8,
    "notes": null,
    "created_at": "2026-07-09T18:40:02.000Z"
    },
    {
    "id": "7d2e0b8c-4f3a-4b9c-8d1e-2f3a4b5c6d7e",
    "catalog_type": "service",
    "catalog_item_id": "0d9e8f7a-6b5c-4d3e-2f1a-9b8c7d6e5f4a",
    "quantity": 1,
    "unit_price": 90,
    "discount_amount": 10,
    "discount_percentage": null,
    "subtotal": 90,
    "total_amount": 80,
    "notes": null,
    "created_at": "2026-07-09T18:40:02.000Z"
    }
    ]
    },
    "meta": { "request_id": "req_01JZXQ8V2K5M7N9P0R1S2T3U4V" }
    }
  6. Confira o histórico de status. O histórico não vem na resposta da criação. Peça por include na busca por id:

    Terminal window
    curl -G "$LIGGA_BASE/v1/sales/3f9a1b7e-2c4d-4e8f-b6a1-5d0c9e8f7a2b" \
    -H "Authorization: Bearer $LIGGA_API_KEY" \
    --data-urlencode 'include=items,status_history'
    {
    "data": {
    "id": "3f9a1b7e-2c4d-4e8f-b6a1-5d0c9e8f7a2b",
    "status": "pending",
    "status_history": [
    {
    "id": "c1d2e3f4-5a6b-4c7d-8e9f-0a1b2c3d4e5f",
    "old_status": null,
    "new_status": "pending",
    "change_reason": "created via public api",
    "created_at": "2026-07-09T18:40:02.000Z"
    }
    ]
    },
    "meta": { "request_id": "req_01JZXQ8V2K5M7N9P0R1S2T3U4W" }
    }
  7. Avance o status quando o pedido andar. PATCH altera apenas status, payment_status, payment_method, notes, delivery_date e delivery_fee, e cada mudança de status grava uma entrada nova no histórico:

    Terminal window
    curl -X PATCH "$LIGGA_BASE/v1/sales/3f9a1b7e-2c4d-4e8f-b6a1-5d0c9e8f7a2b" \
    -H "Authorization: Bearer $LIGGA_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{ "status": "completed", "payment_status": "paid" }'
criar-venda.sh
#!/usr/bin/env bash
set -euo pipefail
: "${LIGGA_API_KEY:?defina LIGGA_API_KEY}"
: "${LIGGA_BASE:?defina LIGGA_BASE}"
CUSTOMER_ID='9c8d7e6f-1a2b-4c3d-8e9f-0a1b2c3d4e5f'
PRODUTO_ID='b7e1f3a2-8c4d-4e5f-9a6b-1c2d3e4f5a6b'
SERVICO_ID='0d9e8f7a-6b5c-4d3e-2f1a-9b8c7d6e5f4a'
PEDIDO='pedido-loja-8842'
VENDA=$(curl -sS -X POST "$LIGGA_BASE/v1/sales" \
-H "Authorization: Bearer $LIGGA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $PEDIDO" \
-d "{
\"customer_id\": \"$CUSTOMER_ID\",
\"transaction_type\": \"sale\",
\"payment_method\": \"pix\",
\"payment_status\": \"pending\",
\"items\": [
{
\"catalog_type\": \"product\",
\"catalog_item_id\": \"$PRODUTO_ID\",
\"quantity\": 2,
\"unit_price\": 49.9
},
{
\"catalog_type\": \"service\",
\"catalog_item_id\": \"$SERVICO_ID\",
\"quantity\": 1,
\"unit_price\": 90,
\"discount_amount\": 10
}
]
}")
VENDA_ID=$(echo "$VENDA" | jq -r '.data.id')
echo "venda criada: $VENDA_ID"
echo "$VENDA" | jq '{ subtotal: .data.subtotal, total: .data.total_amount, status: .data.status }'
curl -sS -G "$LIGGA_BASE/v1/sales/$VENDA_ID" \
-H "Authorization: Bearer $LIGGA_API_KEY" \
--data-urlencode 'include=items,status_history' \
| jq '.data.status_history'

Rodar o script duas vezes com o mesmo PEDIDO não cria duas vendas: a segunda execução recebe a resposta guardada da primeira, com o cabeçalho Idempotent-Replay: true.

A venda nasce em pending, não em completed. status é opcional no corpo e, quando você não manda nada, o servidor grava pending. Deixe assim se o pagamento ainda não foi confirmado e avance por PATCH quando ele for. Mandar "status": "completed" na criação é válido, mas aí a venda não passa pelos estados intermediários e o histórico registra só o estado final.

Os totais são calculados no servidor. Você envia quantity, unit_price e, se houver, discount_amount e discount_percentage. O servidor calcula, para cada item, subtotal = quantity × unit_price e total_amount = subtotal − desconto percentual − desconto em valor, nunca negativo. Na venda, subtotal é a soma dos subtotais dos itens e total_amount é a soma dos totais dos itens mais delivery_fee. Campos de total enviados no corpo são simplesmente descartados.

catalog_type diz de onde veio o item. Os valores aceitos são product, service e package. Ele precisa combinar com a origem do catalog_item_id: um identificador de GET /v1/services com catalog_type: "product" grava um item que aponta para o lugar errado.

A chave de idempotência é o número do pedido. Assim, uma nova tentativa depois de um timeout devolve a venda original em vez de criar outra. Reusar a mesma chave com um corpo diferente responde 409 idempotency_conflict, e isso é sinal de defeito no seu lado, não da API.

Os itens não são editáveis depois. PATCH só alcança os campos de topo. Para corrigir quantidade, preço ou composição, cancele a venda com DELETE /v1/sales/{id}, que define status: "cancelled" sem apagar o registro, e crie outra venda com uma chave de idempotência nova.

Quando a criação falha, o corpo vem em application/problem+json e o campo que identifica a causa é type, nunca code:

HTTPtypeQuando acontece
401invalid_tokenCabeçalho Authorization ausente ou chave inexistente
403insufficient_scopeA chave não tem sales:write nem *:write
403module_not_enabledO módulo vendas não está habilitado no contrato
409idempotency_conflictMesma Idempotency-Key reenviada com corpo diferente
422validationitems vazio ou com mais de 200 entradas; quantity igual ou menor que zero; unit_price negativo; catalog_type fora de product, service e package; catalog_item_id que não é um UUID; discount_percentage fora da faixa de 0 a 100
429rate-limit-exceededCota de escrita do plano excedida
  • A operação não é uma venda. Compras e pagamentos a fornecedor vivem em Despesas, com outro conjunto de campos.
  • O item não está no catálogo. Todo item precisa de um catalog_item_id existente. A API não cria produtos nem serviços: cadastre no app Ligga antes.
  • Você precisa corrigir uma venda existente sem perder o registro. Cancelar e recriar troca o id e o número da transação. Se isso for um problema para a sua conciliação, ajuste apenas o que o PATCH alcança.