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>"
}
}
]
}Crear contacto (o reutilizar el existente)
Crea un contacto o, si la empresa ya tiene un contacto con alguno de los identificadores enviados, reutiliza ese contacto en lugar de crear otro.
- Identificadores:
phone_number(concountry_code),emailylinkedin_identifier. Además delname, se exige al menos uno de ellos; sin ninguno, 400"Informe pelo menos um identificador: telefone, e-mail ou LinkedIn.". El contacto puede crearse sin teléfono. - Orden de búsqueda: correo electrónico, LinkedIn y teléfono. El correo solo cuenta con
@y se reconoce en la forma normalizada (sin espacios en los extremos, en minúsculas), que es como pasa a guardarse; los correos antiguos guardados con mayúsculas solo se reconocen con la misma grafía, hasta la normalización de la base. LinkedIn acepta la URL del perfil o solo el nombre. El teléfono se normaliza (código de área, 9.º dígito). - 201: contacto creado. El cuerpo es el contacto con
created: true,matched_by: null,matches_count: 0eignored_fields: []. - 200: contacto existente reutilizado. El cuerpo es el contacto con
created: false,matched_by(email,linkedinophone) ymatches_count(cuántos contactos de la empresa coincidieron por esa clave; por encima de 1, hay duplicados y se usó el más antiguo). Encontrado por teléfono, los campos enviados se actualizan, como el POST siempre hizo; encontrado por correo electrónico o LinkedIn, solo se completan los campos que estaban vacíos, eignored_fieldsindica los enviados que no se guardaron porque el contacto ya tenía otro valor (ej.:email,phone_number,job_title,additional_attributes.<slug>). Elnamey el propietario nunca cambian. Para modificar cualquier campo de un contacto existente, usePUT /v1/contacts/{id}. - 409
CONTACT_IDENTITY_CONFLICT: los identificadores enviados pertenecen a contactos distintos (ej.: el teléfono es de un contacto y el correo, de otro). No se guarda nada;conflictsdevuelve el contacto de cada identificador. - Campos de perfil:
job_title,seniority,department,country,region,cityyexperiencese guardan en la creación; en un contacto existente, siguen la regla del 200 (se actualizan si se encuentra por teléfono, solo se completan si están vacíos cuando se encuentra por correo electrónico o LinkedIn). - Teléfono: solo dígitos, código de área + número, sin el código de país (el código de país va en
country_code, obligatorio junto con el teléfono). Un teléfono inválido devuelve 400. - additional_attributes:
{ "slug": valor }, con valor primitivo (string, number, boolean o null). Un slug inexistente, o de un custom field con contexto distinto de CONTACT, se ignora en silencio. En un contacto existente, merge por slug: encontrado por teléfono, los slugs enviados se actualizan; encontrado por correo electrónico o LinkedIn, solo se completan los vacíos. - Verificar antes:
POST /v1/contacts/identity-checkindica qué haría esta ruta, sin guardar.
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>"
}
}
]
}Autorizaciones
Token JWT de autenticación
Encabezados
Identificador de la empresa objetivo. Obligatorio solo para API keys globales (type=global). Se ignora para API keys de empresa y usuarios humanos.
Cuerpo
Nombre completo del contacto. Obligatorio. Si el contacto ya existe, su nombre se conserva.
1Código de país para llamadas (1-3 dígitos). Ejemplos: 55 (Brasil), 1 (EE. UU.), 351 (Portugal). Obligatorio cuando se envía phone_number; sin teléfono, no lo envíe.
1 - 3^\d{1,3}$Dirección de correo electrónico del contacto. Identifica el contacto por la forma normalizada (sin espacios en los extremos, en minúsculas), que es como se guarda. Los correos antiguos guardados con mayúsculas solo se reconocen con la misma grafía, hasta la normalización de la base. Solo cuenta como identificador si tiene "@". En un contacto existente, se actualiza cuando el contacto se encuentra por teléfono; encontrado por correo electrónico o LinkedIn, solo se completa si está vacío.
Identificador de LinkedIn del contacto: URL del perfil o solo el nombre del perfil. Ejemplo: joao-silva-123. Identifica el contacto. En un contacto existente, se actualiza cuando el contacto se encuentra por teléfono; encontrado por correo electrónico o LinkedIn, solo se completa si está vacío.
Número de teléfono sin formato, solo dígitos (código de área + número, sin el código de país). Ejemplo: 11999998888. Identifica el contacto. Un teléfono que no forma un número válido con country_code se rechaza con 400. Opcional: el contacto necesita al menos un identificador entre teléfono, correo electrónico y LinkedIn.
^\d*$Cargo del contacto. Ejemplo: Gerente de Ventas. Se guarda en la creación; en un contacto existente, se actualiza cuando el contacto se encuentra por teléfono; encontrado por correo electrónico o LinkedIn, solo se completa si está vacío.
Nivel de seniority del contacto. Ejemplo: Senior, Junior, Semi-senior. Se guarda en la creación; en un contacto existente, se actualiza cuando el contacto se encuentra por teléfono; encontrado por correo electrónico o LinkedIn, solo se completa si está vacío.
Departamento del contacto. Ejemplo: Comercial, TI, Marketing. Se guarda en la creación; en un contacto existente, se actualiza cuando el contacto se encuentra por teléfono; encontrado por correo electrónico o LinkedIn, solo se completa si está vacío.
País del contacto. Ejemplo: Brasil. Se guarda en la creación; en un contacto existente, se actualiza cuando el contacto se encuentra por teléfono; encontrado por correo electrónico o LinkedIn, solo se completa si está vacío.
Estado o región del contacto. Ejemplo: São Paulo. Se guarda en la creación; en un contacto existente, se actualiza cuando el contacto se encuentra por teléfono; encontrado por correo electrónico o LinkedIn, solo se completa si está vacío.
Ciudad del contacto. Ejemplo: São Paulo. Se guarda en la creación; en un contacto existente, se actualiza cuando el contacto se encuentra por teléfono; encontrado por correo electrónico o LinkedIn, solo se completa si está vacío.
Experiencia profesional del contacto. Se guarda en la creación; en un contacto existente, se actualiza cuando el contacto se encuentra por teléfono; encontrado por correo electrónico o LinkedIn, solo se completa si está vacío.
Campos personalizados del contacto. Formato: { "slug_del_campo": valor }. Los slugs deben corresponder a custom fields registrados con contexto CONTACT. Los slugs inválidos o inexistentes se ignoran en silencio. Los valores deben ser primitivos (string, number, boolean o null). En un contacto existente encontrado por teléfono, los slugs enviados se actualizan (merge); encontrado por correo electrónico o LinkedIn, solo se completan los slugs vacíos. Los slugs no enviados se mantienen. Ejemplo: { "cpf": "12345678900", "data_nascimento": "1990-01-15", "valor_total": 1500.50 }
Show child attributes
Show child attributes
Respuesta
Contacto existente reutilizado (created: false). Encontrado por teléfono, los campos enviados se actualizaron; por correo electrónico o LinkedIn, solo se completaron los vacíos e ignored_fields indica lo que no se guardó.
Nombre del contacto
Empresa del contacto
true cuando el contacto se creó (201); false cuando se reutilizó (200).
Identificador que coincidió
email, linkedin, phone Cuántos contactos de la empresa coincidieron por la clave matched_by (0 al crear). Por encima de 1, la empresa tiene contactos duplicados y se reutilizó el más antiguo.
Campos enviados que NO se guardaron porque el contacto existente ya tenía otro valor (solo cuando se reutiliza por correo electrónico o LinkedIn). Ej.: email, phone_number, job_title, additional_attributes.<slug>. Vacío al crear y cuando se encuentra por teléfono.
ID del contacto
ID del contacto (mismo valor que _id)
Código de país del número. Ausente junto con el teléfono cuando el contacto solo tiene un identificador de WhatsApp.
Email del contacto
Fecha de la última actividad del contacto
Número de teléfono del contacto. Ausente cuando el contacto solo tiene un identificador de WhatsApp.
Identificador del perfil de LinkedIn
Cargo del contacto
Nivel de seniority del contacto
Departamento del contacto
País del contacto
Estado o región del contacto
Ciudad del contacto
Experiencia profesional del contacto
Habilidades del contacto
Valores de los campos personalizados del contacto, por slug. Si se guardó vía additional_attributes, el valor viene en el envoltorio { id, value, type, updatedAt }. Si se guardó vía custom_fields (PUT), viene sin envoltorio: el option_id (select), la lista de option_id (multi_select) o el texto (text).
Show child attributes
Show child attributes
ID del Business al que pertenece el contacto
Notas registradas en el contacto
Show child attributes
Show child attributes
Opt-out de marketing por medio de comunicación (whatsapp, email, linkedin). Ausente o vacío cuando el contacto no pidió salir de ningún medio.
Show child attributes
Show child attributes
Identificador del contacto en WhatsApp (BSUID), asignado por Meta. String opaco.
@username del contacto en WhatsApp, tal como lo envía Meta
parent_user_id del contacto en WhatsApp, tal como lo envía Meta
Fecha de creación del contacto
Fecha de actualización del contacto