API de CRM
API REST para o parceiro manter os Contatos e os Negócios do Workfuse em dia a partir do sistema dele: criar, atualizar, ler, sincronizar contatos em lote, e enviar eventos que atualizam propriedades de Contato e de Negócio.
Os endpoints
| Endpoint | O que faz | Estado |
|---|---|---|
POST /crm/contacts | Cria um contato | 🟢 responde hoje |
PATCH /crm/contacts/{contactId} | Atualiza um contato | 🟢 responde hoje |
GET /crm/contacts/{contactId} | Lê um contato | 🟢 responde hoje |
POST /crm/contacts/batch/upsert | Sincroniza em lote, assíncrono | 🟢 responde hoje |
GET /crm/batches/{batchId} | Consulta o resultado de um lote | 🟢 responde hoje |
POST /crm/deals | Cria um negócio | 🟢 responde hoje |
PATCH /crm/deals/{dealId} | Atualiza propriedades de um negócio | 🟢 responde hoje |
GET /crm/deals/{dealId} | Lê um negócio | 🟢 responde hoje |
POST /crm/events | Envia evento, síncrono (201) | 🟢 responde hoje |
Mais os webhooks, que é o que a sua aplicação recebe quando algo muda aqui.
Fundamentos
- Base URL:
API_BASE/public/v1/crm—API_BASEé a URL da API do seu ambiente (em produção,https://api.workfuse.com). Todos os caminhos desta seção são relativos a ela. - Autenticação: API key no header
Authorization: Bearer wf_..., com o módulocrmno escopo. A chave define a instituição — você não envia header de tenant. Veja Autenticação. - Formato: JSON em request e response. Datas são ISO 8601 em UTC.
- Limites e processamento assíncrono: Limites e cotas.
Os dois objetos
| Objeto | O que é |
|---|---|
| Contato | A pessoa — candidato, aluno, responsável. Um cadastro só, que atravessa a jornada inteira |
| Negócio | Uma oportunidade sobre um Contato — uma inscrição, uma rematrícula |
Esta API escreve Contato e Negócio diretamente, por POST/GET/PATCH em
/contacts e em /deals; além disso, um
evento também atualiza um Negócio já
existente, pelo mapeamento que a instituição configura.
Duas regras do modelo que mudam como você integra:
- O Contato é único por instituição, e a API key já fixa a instituição. Não existe contato "da organização" — existe contato daquela instituição.
- O evento nunca cria Negócio. Ele atualiza o Negócio que já existe; quando não há
nenhum, os mapeamentos de Negócio são ignorados e os de Contato seguem valendo. Para
criar um Negócio, use
POST /deals.
Propriedades
Um campo é uma propriedade, e vive na bag properties. No Contato, todo campo vive
nela: não há campo fora dela. No Negócio, contact, pipelineId, stageId, ownerId e
externalId ficam fora, e properties leva só as customizadas.
{
"properties": {
"email": "ana.souza@exemplo.com",
"name": "Ana Souza",
"interesse_em_bolsa": "Sim"
}
}
Dois tipos, sem diferença de sintaxe:
| Tipo | Exemplo | Quem cria |
|---|---|---|
| Nativa (do Contato) | name, email, phone, taxId, externalId | A plataforma |
| Customizada | interesse_em_bolsa, turno_preferido | A instituição, em Configurações → Propriedades |
A chave de uma propriedade customizada é derivada do nome e é imutável. "Interesse em
bolsa" vira interesse_em_bolsa: sem acento, minúscula, não-alfanumérico vira _. Você
nunca escolhe a chave — ela é o contrato, e por isso não muda depois de criada.
Nome nativo nunca é sombreado. A plataforma recusa criar propriedade customizada com a
chave de um campo nativo, então email dentro de properties significa sempre o e-mail do
contato — nunca uma propriedade customizada de mesmo nome.
Tipos de propriedade
TEXT · NUMBER · DATE · BOOLEAN · SELECT · MULTI_SELECT · REFERENCE · USER
MULTI_SELECT guarda uma lista, e é o único tipo em que escrever acrescenta em vez de
substituir quando vem de um evento — detalhe em
Eventos. REFERENCE e USER apontam para
registros internos e não podem ser alvo de evento: uma string vinda de sistema de
terceiro não é um id confiável.
Erros
Toda resposta traz o header X-Request-Id: é ele que você manda no suporte.
Erro de regra de negócio (404, 409, 422) tem este formato:
{
"errorCode": "DEAL_OWNER_REQUIRED",
"message": "Informe o dono do negócio ou configure um dono padrão na instituição",
"details": {}
}
errorCodeé estável: é por ele que você trata o erro. As tabelas de cada página listam os códigos.messageé em português, para gente ler. Não faça parse dela.detailsé opcional e varia por código (keys,fields,property...). Trate todo campo como opcional ao fazer parse.
Dois erros chegam em outro formato:
- Autenticação e escopo (
401,403):{ "error": "...", "code": "API_KEY_INVALID" }. OscodesãoAPI_KEY_MISSING,API_KEY_INVALIDeMODULE_NOT_IN_KEY_SCOPE. Com o CRM desligado para a organização, o403é{ "error": "CAPABILITY_NOT_ENABLED", "code": "crm", "message": "..." }. - Corpo malformado (
400):{ "success": false, "error": { "name": "ZodError", ... } }. Não temerrorCode: leia o status.
| HTTP | Quando |
|---|---|
202 | Aceito para processamento assíncrono — não é "pronto". |
400 | Corpo malformado. |
401 | API key ausente, inválida ou revogada. |
403 | A chave não tem o módulo crm no escopo, ou a organização não tem o CRM habilitado. |
404 | Registro não encontrado, ou de outra instituição. |
409 | Conflito de identidade ou de externalId (ver Contatos e Negócios). |
422 | Corpo bem formado, mas recusado pela regra de negócio. |
429 | Limite de requisições excedido, quando os limites entrarem em vigor (hoje em prévia) — ver Limites e cotas. |
Se você vem do HubSpot
A API foi desenhada para ser familiar a quem já integrou com o HubSpot: mesma ergonomia
(bag properties, upsert por propriedade de identidade, errorCode estável). O que
muda é o vocabulário e o modelo — e a diferença importa na migração:
| No HubSpot | Aqui | Cuidado |
|---|---|---|
Contact | Contato | Mesmo conceito, escopo diferente: lá a identidade é global por e-mail; aqui é por instituição |
Company | — | Organization aqui é o tenant, não a empresa do contato. Não são a mesma coisa |
Deal | Negócio | Mesmo conceito — POST/GET/PATCH /deals cria, lê e atualiza propriedades; evento só atualiza um já existente |
firstname + lastname | name | Um campo só |
hs_object_id | id | |
| Record ID do HubSpot | não é o externalId | O externalId é o id no seu sistema, não o id no CRM de origem |
POST /events/v3/send | POST /crm/events | Lá o evento vai para a timeline; aqui ele também atualiza propriedade |
batch/upsert síncrono, 100 itens | Assíncrono, 1.000 itens | Você recebe 202 e consulta o resultado em GET /batches/{batchId} ou assina o webhook |
Comparação medida em developers.hubspot.com e knowledge.hubspot.com em 17/09/2026.