Public API Lead Endpoint
This endpoint lets an external system submit a lead to a specific brayv.ai account. A successful request creates or updates a contact immediately, records submission history, and queues background lead processing such as journey routing, opportunity creation, assignment, and notifications.
Endpoint
POST https://api.brayv.ai/v1/accounts/{account_id}/leads
Authorization: Bearer {api_key}
Content-Type: application/json
Replace {account_id} with the brayv.ai account ID receiving the lead.
Authentication
Send the API key in the Authorization header:
Authorization: Bearer brayv_live_...
Each API key is scoped to either one account or an organization that has access to the target account.
Response
Successful submissions return immediately after the contact is created or updated and the background processing job is queued.
{
"status": "success",
"submission_id": "e7ac371f-478e-4290-b147-524a0f491ab2",
"contact_id": "34726940-88ec-49e3-9529-6e0845703eda",
"idempotent_replay": false,
"ignored_fields": []
}
Required Fields
| Field | Type | Required | Notes |
|---|---|---|---|
phone | string | Yes | Must match one of the accepted phone formats below. Saved as +1XXXXXXXXXX. |
source | string | Yes | Saved on the contact as api: {source}. Example: api: angi. |
text_opt_in | boolean | Yes | Whether the contact opted into SMS/text outreach. |
call_opt_in | boolean | Yes | Whether the contact opted into outbound calls. |
voice_ai_opt_in | boolean | Yes | Whether the contact opted into outbound AI voice calls. Controls has_voice_ai_outbound. |
full_name | string | Conditional | At least one of full_name, first_name, or last_name is required. |
first_name | string | Conditional | At least one of full_name, first_name, or last_name is required. |
last_name | string | Conditional | At least one of full_name, first_name, or last_name is required. |
Recommended Fields
These fields are not required, but they are strongly recommended because they are commonly used in scripted engagement messages and AI agent instructions.
| Field | Type | Why it is useful |
|---|---|---|
hook | string | A short description of what the lead is interested in. Commonly used in scripted messages, for example: Following up on your {hook} request, is now a better time to chat? |
friendly_source | string | A human-readable description of where the lead came from (e.g. 'our ad' or 'our website'). Commonly used in AI agent instructions, for example: The user you are chatting with reached out to us via {friendly_source}. |
Consent Behavior
| Submitted fields | Contact behavior |
|---|---|
text_opt_in: false | Sets dnd_sms = true and has_text_ai = false. |
text_opt_in: true | Allows SMS/text AI, unless the contact has a carrier-level STOP opt-out. |
| Existing contact has carrier-level STOP opt-out | API cannot clear SMS DND or enable text AI. The submitted opt-in is ignored and logged. |
call_opt_in: false | Sets dnd_calls = true and has_voice_ai_outbound = false. A submitted voice_ai_opt_in: true is ignored and logged, because nobody may call the contact at all. |
call_opt_in: true, voice_ai_opt_in: false | Sets dnd_calls = false and has_voice_ai_outbound = false. Your team may call; an AI agent may not dial. |
call_opt_in: true, voice_ai_opt_in: true | Sets dnd_calls = false and has_voice_ai_outbound = true. |
Opt-out fields do not block contact creation or opportunity creation. They control outreach eligibility.
The Two Voice AI Fields
A contact carries two separate voice AI settings, and they answer different questions:
| Field | Meaning when false | How to set it |
|---|---|---|
has_voice_ai | An AI voice agent will never answer a call from this contact and will never dial them. Voice AI is off for them entirely. | Send has_voice_ai directly. Optional; defaults to true. |
has_voice_ai_outbound | An AI voice agent will never dial this contact. They can still reach an AI agent when they call you. | Derived from the required voice_ai_opt_in field, per the table above. |
Both default to true, so voice AI is available unless you say otherwise. Send has_voice_ai: false for a contact who should only ever deal with a human, and use voice_ai_opt_in: false for contacts where you did not explictly gather consent to call them using AI.
has_voice_ai: false implies no AI dialing regardless of voice_ai_opt_in, since it turns voice AI off in both directions. The two fields are stored independently, so you can still send voice_ai_opt_in: true alongside it, and the outbound flag will read true while has_voice_ai keeps the AI away from the contact.
Both fields are enforced. An AI voice agent asked to dial a contact whose has_voice_ai or has_voice_ai_outbound is false does not place the call, and the reason is recorded on the contact's activity history. A contact whose has_voice_ai is false who calls one of the account's Voice AI numbers is forwarded to a person instead of reaching an agent.
dnd_calls remains the broadest setting and covers the whole team, not just the AI: it also blocks calls placed by hand from the in-app dialer. call_opt_in: false sets it, and it is what to rely on for a contact who asked not to be called by anyone.
Required Formats
Phone
Only these formats are accepted:
| Format | Example | Saved as |
|---|---|---|
+1XXXXXXXXXX | +15551234567 | +15551234567 |
1XXXXXXXXXX | 15551234567 | +15551234567 |
XXXXXXXXXX, where first digit is not 1 | 5551234567 | +15551234567 |
Formatted numbers such as (555) 123-4567, 555-123-4567, or numbers with spaces are rejected. Rejected submissions are still logged, but no contact is created.
email is optional. If provided, it must match a basic email pattern:
[email protected]
If the email does not match, the submission can still succeed, but the email is ignored and not saved to the contact.
Dates
birthday and date custom fields should be parseable date strings. ISO format is recommended:
YYYY-MM-DD
Invalid optional dates are ignored and logged.
Optional Standard Contact Fields
Unknown fields are ignored and logged. These standard contact fields are accepted:
| Field | Type | Required | Notes |
|---|---|---|---|
email | string | No | Must match [email protected], otherwise ignored. |
has_voice_ai | boolean | No | Whether an AI voice agent may handle this contact's calls at all. Defaults to true. Non-boolean values are ignored. See The Two Voice AI Fields. |
business_name | string | No | Business or company name. |
website | string | No | Website URL or domain. |
birthday | string | No | Parseable date string. ISO YYYY-MM-DD recommended. |
timezone | string | No | Must be one of the accepted timezone values below. |
additional_phones | string[] | No | List of additional phone values. |
additional_emails | string[] | No | Invalid emails are ignored. |
street_address | string | No | Street address. |
city | string | No | City. |
state | string | No | State or province. |
zip_code | string | No | ZIP or postal code. |
country | string | No | Country. |
tags | string[] | No | Tags to apply to the contact. |
friendly_source | string | No | Human-readable source label for AI context. |
hook | string | No | Short description of what the contact is interested in. |
service_description | string | No | Longer description of requested service or issue. |
utm_source | string | No | UTM source. |
utm_medium | string | No | UTM medium. |
utm_campaign | string | No | UTM campaign. |
utm_content | string | No | UTM content. |
utm_keyword | string | No | UTM keyword. |
referrer | string | No | Referrer URL. |
landing_page_url | string | No | Landing page URL. |
facebook_click_id | string | No | Facebook fbclid. |
google_click_id | string | No | Google gclid. |
google_wbraid | string | No | Google wbraid. |
google_gbraid | string | No | Google gbraid. |
facebook_lead_id | string | No | Facebook instant-form lead ID (leadgen_id). Definitive Facebook ad attribution - send this plus campaign_id when forwarding instant-form leads from an external system. |
campaign_id | string | No | Ad platform campaign ID (for Facebook leads, the Meta campaign ID - enables per-campaign ROAS reporting). |
Accepted timezone Values
US/Eastern
US/Central
US/Mountain
US/Pacific
US/Alaska
US/Hawaii
America/New_York
America/Detroit
America/Toronto
America/Montreal
America/Chicago
America/Winnipeg
America/Denver
America/Edmonton
America/Phoenix
America/Los_Angeles
America/Vancouver
America/Anchorage
Pacific/Honolulu
America/Halifax
America/St_Johns
If timezone is provided and is not one of these values, it is ignored and logged.
Custom Fields
Custom fields should be sent inside the custom_fields object.
{
"custom_fields": {
"service_type": "HVAC",
"lead_priority": "High"
}
}
The key must match the custom field ID configured in brayv.ai.
Custom field validation:
| Custom field type | Expected value | Behavior |
|---|---|---|
text | string | Saved as submitted. |
number | number or numeric string | Saved if numeric, otherwise ignored. |
date | parseable date string | Saved as YYYY-MM-DD, otherwise ignored. |
select | string matching one configured option | Case-insensitive match. Non-matching values ignored. |
radio | string matching one configured option | Case-insensitive match. Non-matching values ignored. |
multiselect | string or string[] matching configured options | Matching options are saved. Non-matching options ignored. |
checkbox | boolean-like value | Accepts true, false, 1, 0, yes, no, y, n, on, off, t, f. |
Invalid optional custom field values do not fail the submission. They are ignored and logged in submission history.
Note
note is optional and attaches a single note to the contact. It is not a contact field; it creates a note on the contact's Notes panel in brayv.ai.
{
"note": {
"content": "Homeowner says the upstairs unit is the problem, downstairs is fine.",
"surface_to_agents": true
}
}
Tip: if surfacing to agents, and the note is derived from something like an open ended 'comments' section on a lead form, it's good practice to pass in that context, i.e.:
{
"note": {
"content": "Comment submitted on quote request form: the upstairs unit is the problem, downstairs is fine.",
"surface_to_agents": true
}
}
A plain string is also accepted as shorthand, and leaves surface_to_agents at its default of false:
{
"note": "Homeowner says the upstairs unit is the problem."
}
| Field | Type | Required | Notes |
|---|---|---|---|
content | string | Yes | The note text. Trimmed to 1000 characters if longer. |
surface_to_agents | boolean | No | Whether AI agents may read this note. Defaults to false. |
Note Visibility
surface_to_agents controls who can read the note:
| Value | Who sees the note |
|---|---|
false (default) | Your team only, in the contact's Notes panel. AI agents never see it. |
true | Your team, plus the AI text and voice agents, which receive the note as context about the contact. |
Send surface_to_agents: true only for context an AI agent should know, and keep it false for internal commentary you do not want repeated to the contact. An agent reads at most the 3 most recently edited surfaced notes on a contact, so a surfaced note can push an older one out of view.
Note Behavior
| Situation | Behavior |
|---|---|
| More than 1000 characters | Trimmed to 1000 characters. The submission still succeeds and the trim is reported in ignored_fields. |
Empty or whitespace-only content | No note is created. Ignored and logged. |
note is not a string or object, or content is not a string | No note is created. Ignored and logged. |
surface_to_agents is not a boolean | Note is created as internal-only (false) and the value is logged. |
| The contact already has a note with identical text | No duplicate note is created. Logged as a duplicate. |
| Submission is rejected for invalid required fields | No contact and no note are created. |
Notes created this way are labelled Integration in brayv.ai and show no author, since they did not come from a team member.
Idempotency
idempotency_key is optional.
When provided, repeated requests with the same API key, target account, and idempotency key within about 5 minutes return the same result instead of creating a second submission.
{
"idempotency_key": "external-lead-123"
}
Use a unique idempotency key per lead from your system.
Full Example Payload
{
"phone": "5551234567",
"full_name": "Jane Doe",
"first_name": "Jane",
"last_name": "Doe",
"source": "angi",
"text_opt_in": true,
"call_opt_in": true,
"voice_ai_opt_in": false,
"has_voice_ai": true,
"idempotency_key": "angi-lead-12345",
"email": "[email protected]",
"business_name": "Doe Family Home",
"website": "https://example.com",
"timezone": "US/Eastern",
"additional_phones": ["+15557654321"],
"additional_emails": ["[email protected]"],
"street_address": "123 Main St",
"city": "Charlotte",
"state": "NC",
"zip_code": "28202",
"country": "US",
"tags": ["angi", "priority-lead"],
"friendly_source": "Angi",
"hook": "HVAC repair",
"service_description": "Customer says the AC is not cooling and wants service this week.",
"utm_source": "angi",
"utm_medium": "lead_partner",
"utm_campaign": "summer_hvac",
"utm_content": "ac_repair",
"utm_keyword": "emergency ac repair",
"referrer": "https://www.angi.com/",
"landing_page_url": "https://example.com/hvac",
"facebook_click_id": "fbclid-example",
"google_click_id": "gclid-example",
"google_wbraid": "wbraid-example",
"google_gbraid": "gbraid-example",
"facebook_lead_id": "1234567890123456",
"campaign_id": "120210000000000000",
"custom_fields": {
"service_type": "HVAC",
"lead_priority": "High",
"preferred_service_date": "2026-06-15"
},
"note": {
"content": "Customer says the AC is not cooling upstairs only, and asked us to call before 5pm.",
"surface_to_agents": true
}
}
Example cURL
curl -X POST "https://api.brayv.ai/v1/accounts/ACCOUNT_ID/leads" \
-H "Authorization: Bearer brayv_live_YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"phone": "5551234567",
"full_name": "Jane Doe",
"source": "angi",
"text_opt_in": true,
"call_opt_in": true,
"voice_ai_opt_in": false,
"idempotency_key": "angi-lead-12345",
"friendly_source": "Angi",
"hook": "HVAC repair",
"custom_fields": {
"service_type": "HVAC",
"lead_priority": "High"
}
}'
Error Example
Invalid required fields return a validation error. The rejected submission is still recorded in submission history.
{
"detail": {
"message": "Invalid lead submission",
"submission_id": "e7ac371f-478e-4290-b147-524a0f491ab2",
"validation_errors": [
{
"field": "phone",
"reason": "Phone must be one of: +1XXXXXXXXXX, 1XXXXXXXXXX, or XXXXXXXXXX where the leading digit is not 1"
}
]
}
}