curl --request POST \
--url https://api.nuvia.ai/v1/contacts \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"name": "João Silva",
"country_code": "55",
"phone_number": "11999998888",
"email": "joao.silva@exemplo.com.br",
"job_title": "Gerente de Vendas",
"city": "São Paulo",
"additional_attributes": {
"cpf": "12345678900"
}
}
'import requests
url = "https://api.nuvia.ai/v1/contacts"
payload = {
"name": "João Silva",
"country_code": "55",
"phone_number": "11999998888",
"email": "joao.silva@exemplo.com.br",
"job_title": "Gerente de Vendas",
"city": "São Paulo",
"additional_attributes": { "cpf": "12345678900" }
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
name: 'João Silva',
country_code: '55',
phone_number: '11999998888',
email: 'joao.silva@exemplo.com.br',
job_title: 'Gerente de Vendas',
city: 'São Paulo',
additional_attributes: {cpf: '12345678900'}
})
};
fetch('https://api.nuvia.ai/v1/contacts', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.nuvia.ai/v1/contacts",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'name' => 'João Silva',
'country_code' => '55',
'phone_number' => '11999998888',
'email' => 'joao.silva@exemplo.com.br',
'job_title' => 'Gerente de Vendas',
'city' => 'São Paulo',
'additional_attributes' => [
'cpf' => '12345678900'
]
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.nuvia.ai/v1/contacts"
payload := strings.NewReader("{\n \"name\": \"João Silva\",\n \"country_code\": \"55\",\n \"phone_number\": \"11999998888\",\n \"email\": \"joao.silva@exemplo.com.br\",\n \"job_title\": \"Gerente de Vendas\",\n \"city\": \"São Paulo\",\n \"additional_attributes\": {\n \"cpf\": \"12345678900\"\n }\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.nuvia.ai/v1/contacts")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"name\": \"João Silva\",\n \"country_code\": \"55\",\n \"phone_number\": \"11999998888\",\n \"email\": \"joao.silva@exemplo.com.br\",\n \"job_title\": \"Gerente de Vendas\",\n \"city\": \"São Paulo\",\n \"additional_attributes\": {\n \"cpf\": \"12345678900\"\n }\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.nuvia.ai/v1/contacts")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"name\": \"João Silva\",\n \"country_code\": \"55\",\n \"phone_number\": \"11999998888\",\n \"email\": \"joao.silva@exemplo.com.br\",\n \"job_title\": \"Gerente de Vendas\",\n \"city\": \"São Paulo\",\n \"additional_attributes\": {\n \"cpf\": \"12345678900\"\n }\n}"
response = http.request(request)
puts response.read_body{
"name": "<string>",
"company": "<unknown>",
"created": true,
"matched_by": "email",
"matches_count": 123,
"ignored_fields": [
"<string>"
],
"_id": "<string>",
"id": "<string>",
"country_code": "<string>",
"email": "<string>",
"last_activity_at": "<unknown>",
"phone_number": "<string>",
"linkedin_identifier": "<string>",
"job_title": "<string>",
"seniority": "<string>",
"department": "<string>",
"country": "<string>",
"region": "<string>",
"city": "<string>",
"experience": "<string>",
"skills": "<string>",
"additional_attributes": {},
"business": "<string>",
"notes": [
{
"_id": "<string>",
"content": "<string>",
"user": "<string>",
"createdAt": "<unknown>",
"updatedAt": "<unknown>"
}
],
"marketing_opt_out": {},
"whatsapp_user_id": "<string>",
"whatsapp_username": "<string>",
"whatsapp_parent_user_id": "<string>",
"createdAt": "<unknown>",
"updatedAt": "<unknown>"
}{
"name": "<string>",
"company": "<unknown>",
"created": true,
"matched_by": "email",
"matches_count": 123,
"ignored_fields": [
"<string>"
],
"_id": "<string>",
"id": "<string>",
"country_code": "<string>",
"email": "<string>",
"last_activity_at": "<unknown>",
"phone_number": "<string>",
"linkedin_identifier": "<string>",
"job_title": "<string>",
"seniority": "<string>",
"department": "<string>",
"country": "<string>",
"region": "<string>",
"city": "<string>",
"experience": "<string>",
"skills": "<string>",
"additional_attributes": {},
"business": "<string>",
"notes": [
{
"_id": "<string>",
"content": "<string>",
"user": "<string>",
"createdAt": "<unknown>",
"updatedAt": "<unknown>"
}
],
"marketing_opt_out": {},
"whatsapp_user_id": "<string>",
"whatsapp_username": "<string>",
"whatsapp_parent_user_id": "<string>",
"createdAt": "<unknown>",
"updatedAt": "<unknown>"
}{
"statusCode": 123,
"code": "CONTACT_IDENTITY_CONFLICT",
"message": "<string>",
"conflicts": [
{
"field": "email",
"contact": {
"_id": "<string>",
"name": "<string>",
"email": "<string>",
"phone_number": "<string>",
"country_code": "<string>",
"linkedin_identifier": "<string>"
}
}
]
}Criar contato (ou reaproveitar o existente)
Cria um contato ou, se a empresa já tem um contato com algum dos identificadores enviados, reaproveita esse contato em vez de criar outro.
- Identificadores:
phone_number(comcountry_code),emailelinkedin_identifier. Além doname, é obrigatório pelo menos um deles; sem nenhum, 400"Informe pelo menos um identificador: telefone, e-mail ou LinkedIn.". O contato pode nascer sem telefone. - Ordem de procura: e-mail, LinkedIn e telefone. E-mail só conta com
@e é reconhecido na forma normalizada (sem espaços nas pontas, em minúsculas), que é como passa a ser gravado; e-mails antigos gravados com maiúscula só são reconhecidos com a mesma grafia, até a normalização da base. LinkedIn aceita URL do perfil ou só o nome. Telefone é normalizado (DDD, 9º dígito). - 201: contato criado. O corpo é o contato com
created: true,matched_by: null,matches_count: 0eignored_fields: []. - 200: contato existente reaproveitado. O corpo é o contato com
created: false,matched_by(email,linkedinouphone) ematches_count(quantos contatos da empresa casaram por essa chave; acima de 1, há duplicados e o mais antigo foi usado). Achado pelo telefone, os campos enviados são atualizados, como o POST sempre fez; achado por e-mail ou LinkedIn, só os campos que estavam vazios são preenchidos, eignored_fieldslista os enviados que não foram gravados porque o contato já tinha outro valor (ex.:email,phone_number,job_title,additional_attributes.<slug>).namee o dono nunca mudam. Para alterar qualquer campo de um contato existente, usePUT /v1/contacts/{id}. - 409
CONTACT_IDENTITY_CONFLICT: os identificadores enviados pertencem a contatos diferentes (ex.: o telefone é de um contato e o e-mail, de outro). Nada é gravado;conflictstraz o contato de cada identificador. - Campos de perfil:
job_title,seniority,department,country,region,cityeexperiencesão gravados na criação; num contato existente, seguem a regra do 200 (atualizados se achado pelo telefone, só preenchidos se vazios quando achado por e-mail ou LinkedIn). - Telefone: só dígitos, DDD + número, sem o DDI (o DDI vai em
country_code, obrigatório junto com o telefone). Telefone inválido retorna 400. - additional_attributes:
{ "slug": valor }, com valor primitivo (string, number, boolean ou null). Slug inexistente, ou de custom field com contexto diferente de CONTACT, é ignorado em silêncio. Num contato existente, merge por slug: achado pelo telefone, os slugs enviados são atualizados; achado por e-mail ou LinkedIn, só os vazios são preenchidos. - Conferir antes:
POST /v1/contacts/identity-checkdiz o que esta rota faria, sem gravar.
curl --request POST \
--url https://api.nuvia.ai/v1/contacts \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"name": "João Silva",
"country_code": "55",
"phone_number": "11999998888",
"email": "joao.silva@exemplo.com.br",
"job_title": "Gerente de Vendas",
"city": "São Paulo",
"additional_attributes": {
"cpf": "12345678900"
}
}
'import requests
url = "https://api.nuvia.ai/v1/contacts"
payload = {
"name": "João Silva",
"country_code": "55",
"phone_number": "11999998888",
"email": "joao.silva@exemplo.com.br",
"job_title": "Gerente de Vendas",
"city": "São Paulo",
"additional_attributes": { "cpf": "12345678900" }
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
name: 'João Silva',
country_code: '55',
phone_number: '11999998888',
email: 'joao.silva@exemplo.com.br',
job_title: 'Gerente de Vendas',
city: 'São Paulo',
additional_attributes: {cpf: '12345678900'}
})
};
fetch('https://api.nuvia.ai/v1/contacts', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.nuvia.ai/v1/contacts",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'name' => 'João Silva',
'country_code' => '55',
'phone_number' => '11999998888',
'email' => 'joao.silva@exemplo.com.br',
'job_title' => 'Gerente de Vendas',
'city' => 'São Paulo',
'additional_attributes' => [
'cpf' => '12345678900'
]
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.nuvia.ai/v1/contacts"
payload := strings.NewReader("{\n \"name\": \"João Silva\",\n \"country_code\": \"55\",\n \"phone_number\": \"11999998888\",\n \"email\": \"joao.silva@exemplo.com.br\",\n \"job_title\": \"Gerente de Vendas\",\n \"city\": \"São Paulo\",\n \"additional_attributes\": {\n \"cpf\": \"12345678900\"\n }\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.nuvia.ai/v1/contacts")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"name\": \"João Silva\",\n \"country_code\": \"55\",\n \"phone_number\": \"11999998888\",\n \"email\": \"joao.silva@exemplo.com.br\",\n \"job_title\": \"Gerente de Vendas\",\n \"city\": \"São Paulo\",\n \"additional_attributes\": {\n \"cpf\": \"12345678900\"\n }\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.nuvia.ai/v1/contacts")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"name\": \"João Silva\",\n \"country_code\": \"55\",\n \"phone_number\": \"11999998888\",\n \"email\": \"joao.silva@exemplo.com.br\",\n \"job_title\": \"Gerente de Vendas\",\n \"city\": \"São Paulo\",\n \"additional_attributes\": {\n \"cpf\": \"12345678900\"\n }\n}"
response = http.request(request)
puts response.read_body{
"name": "<string>",
"company": "<unknown>",
"created": true,
"matched_by": "email",
"matches_count": 123,
"ignored_fields": [
"<string>"
],
"_id": "<string>",
"id": "<string>",
"country_code": "<string>",
"email": "<string>",
"last_activity_at": "<unknown>",
"phone_number": "<string>",
"linkedin_identifier": "<string>",
"job_title": "<string>",
"seniority": "<string>",
"department": "<string>",
"country": "<string>",
"region": "<string>",
"city": "<string>",
"experience": "<string>",
"skills": "<string>",
"additional_attributes": {},
"business": "<string>",
"notes": [
{
"_id": "<string>",
"content": "<string>",
"user": "<string>",
"createdAt": "<unknown>",
"updatedAt": "<unknown>"
}
],
"marketing_opt_out": {},
"whatsapp_user_id": "<string>",
"whatsapp_username": "<string>",
"whatsapp_parent_user_id": "<string>",
"createdAt": "<unknown>",
"updatedAt": "<unknown>"
}{
"name": "<string>",
"company": "<unknown>",
"created": true,
"matched_by": "email",
"matches_count": 123,
"ignored_fields": [
"<string>"
],
"_id": "<string>",
"id": "<string>",
"country_code": "<string>",
"email": "<string>",
"last_activity_at": "<unknown>",
"phone_number": "<string>",
"linkedin_identifier": "<string>",
"job_title": "<string>",
"seniority": "<string>",
"department": "<string>",
"country": "<string>",
"region": "<string>",
"city": "<string>",
"experience": "<string>",
"skills": "<string>",
"additional_attributes": {},
"business": "<string>",
"notes": [
{
"_id": "<string>",
"content": "<string>",
"user": "<string>",
"createdAt": "<unknown>",
"updatedAt": "<unknown>"
}
],
"marketing_opt_out": {},
"whatsapp_user_id": "<string>",
"whatsapp_username": "<string>",
"whatsapp_parent_user_id": "<string>",
"createdAt": "<unknown>",
"updatedAt": "<unknown>"
}{
"statusCode": 123,
"code": "CONTACT_IDENTITY_CONFLICT",
"message": "<string>",
"conflicts": [
{
"field": "email",
"contact": {
"_id": "<string>",
"name": "<string>",
"email": "<string>",
"phone_number": "<string>",
"country_code": "<string>",
"linkedin_identifier": "<string>"
}
}
]
}Autorizações
Token JWT de autenticação
Cabeçalhos
Identificador da empresa-alvo. Obrigatório apenas para API Keys globais (type=global). Ignorado para API Keys de empresa e usuários humanos.
Corpo
Nome completo do contato. Campo obrigatório. Se o contato já existe, o nome dele é preservado.
1Código DDI do país (1-3 dígitos). Exemplos: 55 (Brasil), 1 (EUA), 351 (Portugal). Obrigatório quando phone_number é enviado; sem telefone, não envie.
1 - 3^\d{1,3}$Endereço de e-mail do contato. Identifica o contato pela forma normalizada (sem espaços nas pontas, em minúsculas), que é como é gravado. E-mails antigos gravados com maiúscula só são reconhecidos com a mesma grafia, até a normalização da base. Só conta como identificador se tiver "@". Num contato existente, é atualizado quando o contato é achado pelo telefone; achado por e-mail ou LinkedIn, só é preenchido se estiver vazio.
Identificador do LinkedIn do contato: URL do perfil ou só o nome do perfil. Exemplo: joao-silva-123. Identifica o contato. Num contato existente, é atualizado quando o contato é achado pelo telefone; achado por e-mail ou LinkedIn, só é preenchido se estiver vazio.
Número de telefone sem formatação, apenas dígitos (DDD + número, sem o DDI). Exemplo: 11999998888. Identifica o contato. Telefone que não forma um número válido com o country_code é recusado com 400. Opcional: o contato precisa de pelo menos um identificador entre telefone, e-mail e LinkedIn.
^\d*$Cargo do contato. Exemplo: Gerente de Vendas. Gravado na criação; num contato existente, é atualizado quando o contato é achado pelo telefone; achado por e-mail ou LinkedIn, só é preenchido se estiver vazio.
Nível de senioridade do contato. Exemplo: Senior, Junior, Pleno. Gravado na criação; num contato existente, é atualizado quando o contato é achado pelo telefone; achado por e-mail ou LinkedIn, só é preenchido se estiver vazio.
Departamento do contato. Exemplo: Comercial, TI, Marketing. Gravado na criação; num contato existente, é atualizado quando o contato é achado pelo telefone; achado por e-mail ou LinkedIn, só é preenchido se estiver vazio.
País do contato. Exemplo: Brasil. Gravado na criação; num contato existente, é atualizado quando o contato é achado pelo telefone; achado por e-mail ou LinkedIn, só é preenchido se estiver vazio.
Estado ou região do contato. Exemplo: São Paulo. Gravado na criação; num contato existente, é atualizado quando o contato é achado pelo telefone; achado por e-mail ou LinkedIn, só é preenchido se estiver vazio.
Cidade do contato. Exemplo: São Paulo. Gravado na criação; num contato existente, é atualizado quando o contato é achado pelo telefone; achado por e-mail ou LinkedIn, só é preenchido se estiver vazio.
Experiência profissional do contato. Gravado na criação; num contato existente, é atualizado quando o contato é achado pelo telefone; achado por e-mail ou LinkedIn, só é preenchido se estiver vazio.
Campos personalizados do contato. Formato: { "slug_do_campo": valor }. Os slugs devem corresponder a custom fields cadastrados com contexto CONTACT. Slugs inválidos ou inexistentes são ignorados silenciosamente. Os valores devem ser primitivos (string, number, boolean ou null). Num contato existente achado pelo telefone, os slugs enviados são atualizados (merge); achado por e-mail ou LinkedIn, só os slugs vazios são preenchidos. Slugs não enviados ficam como estão. Exemplo: { "cpf": "12345678900", "data_nascimento": "1990-01-15", "valor_total": 1500.50 }
Show child attributes
Show child attributes
Resposta
Contato existente reaproveitado (created: false). Achado pelo telefone, os campos enviados foram atualizados; por e-mail ou LinkedIn, só os vazios foram preenchidos e ignored_fields lista o que não foi gravado.
Nome do contato
Empresa do contato
true quando o contato foi criado (201); false quando foi reaproveitado (200).
Identificador que casou
email, linkedin, phone Quantos contatos da empresa casaram pela chave matched_by (0 ao criar). Acima de 1, a empresa tem contatos duplicados e o mais antigo foi reaproveitado.
Campos enviados que NÃO foram gravados porque o contato existente já tinha outro valor (só quando reaproveitado por e-mail ou LinkedIn). Ex.: email, phone_number, job_title, additional_attributes.<slug>. Vazio ao criar e quando achado pelo telefone.
ID do contato
ID do contato (mesmo valor de _id)
Código do país do número. Ausente junto com o telefone quando o contato só tem identificador do WhatsApp.
E-mail do contato
Data da última atividade do contato
Número de telefone do contato. Ausente quando o contato só tem identificador do WhatsApp.
Identificador do perfil do LinkedIn
Cargo do contato
Nível de senioridade do contato
Departamento do contato
País do contato
Estado ou região do contato
Cidade do contato
Experiência profissional do contato
Habilidades do contato
Valores dos campos personalizados do contato, por slug. Gravado via additional_attributes, o valor vem no envelope { id, value, type, updatedAt }. Gravado via custom_fields (PUT), vem cru: o option_id (select), a lista de option_id (multi_select) ou o texto (text).
Show child attributes
Show child attributes
ID do Business ao qual o contato pertence
Notas registradas no contato
Show child attributes
Show child attributes
Opt-out de marketing por meio de comunicação (whatsapp, email, linkedin). Ausente ou vazio quando o contato não pediu para sair de nenhum meio.
Show child attributes
Show child attributes
Identificador do contato no WhatsApp (BSUID), atribuído pela Meta. String opaca.
@username do contato no WhatsApp, como a Meta envia
parent_user_id do contato no WhatsApp, como a Meta envia
Data de criação do contato
Data de atualização do contato