Pular para o conteúdo

Sandbox & Idempotência

Todo endpoint de escrita da API pública tem dois mecanismos de segurança operacional: o sandbox (para testar sem gravar) e a idempotência (para reenviar uma chamada sem duplicar o efeito). Os dois são independentes e podem ser usados juntos.

Uma chave test (prefixo maestron_pk_test_, ver Autenticação) executa a mesma validação e a mesma regra de negócio de uma chave live, mas a operação inteira roda dentro de uma transação de banco que é sempre desfeita ao final — nada é persistido, ganhe ou perca a requisição.

A resposta de uma chamada em sandbox é idêntica à de uma chamada real, com um campo a mais no nível raiz do envelope:

{
"message": "Negócio criado.",
"data": { "id": "b7e8c1a2-3d4f-4a5b-8c9d-0e1f2a3b4c5d", "...": "..." },
"sandbox": true
}

O sandbox se aplica a toda rota de escrita: criação, atualização, remoção, mover de etapa, ganho/perdido/reabertura e concluir/reabrir atividade. Uma chave live nunca entra em sandbox — o middleware verifica o ambiente da chave e, se for live, executa a operação normalmente, sem custo adicional.

Chamadas de criação aceitam um campo opcional idempotency_key no corpo da requisição. Ele evita que uma falha de rede seguida de retry duplique o efeito da chamada.

POST /crm/deals

Exemplo — criação com idempotency_key

idempotency_key é opcional. Reenviar a mesma chamada com a mesma chave devolve a resposta original, sem criar um segundo negócio.

Corpo (body)

CampoTipoObrigatórioDescrição
idempotency_key string — Identificador único escolhido pelo seu sistema para esta operação (ex.: um UUID).
curl -X POST https://api.maestron.com.br/api/v1/crm/deals \
  -H "Authorization: Bearer SEU_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "funnel_id": "b7e8c1a2-3d4f-4a5b-8c9d-0e1f2a3b4c5d",
    "stage_id": "a1c2d3e4-5f6a-4b7c-8d9e-0f1a2b3c4d5e",
    "contact_name": "Maria Silva",
    "contact_date": "2026-08-31",
    "status": "sem_contato",
    "idempotency_key": "3f7b1c9a-2d4e-4f6a-8b0c-5d9e1f3a7c2b"
  }'
<?php
$client = new \GuzzleHttp\Client();
$response = $client->request('POST', 'https://api.maestron.com.br/api/v1/crm/deals', [
    'headers' => [
        'Authorization' => 'Bearer SEU_TOKEN',
        'Accept' => 'application/json',
    ],
    'json' => [
        'funnel_id' => 'b7e8c1a2-3d4f-4a5b-8c9d-0e1f2a3b4c5d',
        'stage_id' => 'a1c2d3e4-5f6a-4b7c-8d9e-0f1a2b3c4d5e',
        'contact_name' => 'Maria Silva',
        'contact_date' => '2026-08-31',
        'status' => 'sem_contato',
        'idempotency_key' => '3f7b1c9a-2d4e-4f6a-8b0c-5d9e1f3a7c2b',
    ],
]);
$data = json_decode($response->getBody(), true);
import requests
headers = {"Authorization": "Bearer SEU_TOKEN"}
payload = {
    "funnel_id": "b7e8c1a2-3d4f-4a5b-8c9d-0e1f2a3b4c5d",
    "stage_id": "a1c2d3e4-5f6a-4b7c-8d9e-0f1a2b3c4d5e",
    "contact_name": "Maria Silva",
    "contact_date": "2026-08-31",
    "status": "sem_contato",
    "idempotency_key": "3f7b1c9a-2d4e-4f6a-8b0c-5d9e1f3a7c2b",
}
response = requests.post(
    "https://api.maestron.com.br/api/v1/crm/deals",
    headers=headers,
    json=payload,
)
data = response.json()
const response = await fetch("https://api.maestron.com.br/api/v1/crm/deals", {
  method: "POST",
  headers: {
    "Authorization": "Bearer SEU_TOKEN",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "funnel_id": "b7e8c1a2-3d4f-4a5b-8c9d-0e1f2a3b4c5d",
    "stage_id": "a1c2d3e4-5f6a-4b7c-8d9e-0f1a2b3c4d5e",
    "contact_name": "Maria Silva",
    "contact_date": "2026-08-31",
    "status": "sem_contato",
    "idempotency_key": "3f7b1c9a-2d4e-4f6a-8b0c-5d9e1f3a7c2b"
  }),
});
const data = await response.json();
using var client = new HttpClient();
client.DefaultRequestHeaders.Authorization =
    new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", "SEU_TOKEN");
var payload = new StringContent(
    "{\"funnel_id\":\"b7e8c1a2-3d4f-4a5b-8c9d-0e1f2a3b4c5d\",\"stage_id\":\"a1c2d3e4-5f6a-4b7c-8d9e-0f1a2b3c4d5e\",\"contact_name\":\"Maria Silva\",\"contact_date\":\"2026-08-31\",\"status\":\"sem_contato\",\"idempotency_key\":\"3f7b1c9a-2d4e-4f6a-8b0c-5d9e1f3a7c2b\"}",
    System.Text.Encoding.UTF8, "application/json");
var response = await client.PostAsync("https://api.maestron.com.br/api/v1/crm/deals", payload);
var data = await response.Content.ReadAsStringAsync();
POST /crm/deals HTTP/1.1
Host: api.maestron.com.br
Authorization: Bearer SEU_TOKEN
Content-Type: application/json

{
  "funnel_id": "b7e8c1a2-3d4f-4a5b-8c9d-0e1f2a3b4c5d",
  "stage_id": "a1c2d3e4-5f6a-4b7c-8d9e-0f1a2b3c4d5e",
  "contact_name": "Maria Silva",
  "contact_date": "2026-08-31",
  "status": "sem_contato",
  "idempotency_key": "3f7b1c9a-2d4e-4f6a-8b0c-5d9e1f3a7c2b"
}

Respostas

{
  "message": "Negócio criado.",
  "data": {
    "id": "f1a2b3c4-..."
  }
}
{
  "message": "Negócio criado.",
  "data": {
    "id": "f1a2b3c4-..."
  }
}
  • A chave de idempotência é escopada por API Key — a mesma string idempotency_key usada por duas chaves diferentes não colide entre elas.
  • Na primeira chamada com uma dada idempotency_key, a operação roda normalmente e a resposta (status e corpo) fica congelada.
  • Qualquer chamada seguinte com a mesma idempotency_key devolve a resposta congelada, com o header Idempotency-Replayed: true, sem executar a operação de novo — nem abre o sandbox de uma chave test.
  • Duas chamadas com a mesma idempotency_key chegando quase ao mesmo tempo não duplicam o efeito: a segunda é bloqueada no nível do banco e recebe a mesma resposta computada pela primeira.
  • Omitir idempotency_key (ou enviar string vazia) executa a operação normalmente, sem nenhuma garantia de deduplicação.
Rotas com idempotency_keyRotas sem idempotência
POST /crm/dealsTodo PATCH e DELETE — já são idempotentes por natureza (repetir com o mesmo id produz o mesmo resultado).
POST /crm/pipelinesPOST /crm/activities/{id}/complete
POST /crm/activitiesPOST /crm/activities/{id}/reopen
POST /crm/deals/{id}/win, /lose, /reopen—

Para o formato de erro e os códigos de status, veja Erros.