Back to Help Home
Last updated: 2026-07-27

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

FieldTypeRequiredNotes
phonestringYesMust match one of the accepted phone formats below. Saved as +1XXXXXXXXXX.
sourcestringYesSaved on the contact as api: {source}. Example: api: angi.
text_opt_inbooleanYesWhether the contact opted into SMS/text outreach.
call_opt_inbooleanYesWhether the contact opted into outbound calls.
voice_ai_opt_inbooleanYesWhether the contact opted into outbound AI voice calls. Controls has_voice_ai_outbound.
full_namestringConditionalAt least one of full_name, first_name, or last_name is required.
first_namestringConditionalAt least one of full_name, first_name, or last_name is required.
last_namestringConditionalAt least one of full_name, first_name, or last_name is required.

These fields are not required, but they are strongly recommended because they are commonly used in scripted engagement messages and AI agent instructions.

FieldTypeWhy it is useful
hookstringA 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_sourcestringA 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}.
Submitted fieldsContact behavior
text_opt_in: falseSets dnd_sms = true and has_text_ai = false.
text_opt_in: trueAllows SMS/text AI, unless the contact has a carrier-level STOP opt-out.
Existing contact has carrier-level STOP opt-outAPI cannot clear SMS DND or enable text AI. The submitted opt-in is ignored and logged.
call_opt_in: falseSets 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: falseSets 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: trueSets 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:

FieldMeaning when falseHow to set it
has_voice_aiAn 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_outboundAn 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:

FormatExampleSaved as
+1XXXXXXXXXX+15551234567+15551234567
1XXXXXXXXXX15551234567+15551234567
XXXXXXXXXX, where first digit is not 15551234567+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

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:

FieldTypeRequiredNotes
emailstringNoMust match [email protected], otherwise ignored.
has_voice_aibooleanNoWhether 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_namestringNoBusiness or company name.
websitestringNoWebsite URL or domain.
birthdaystringNoParseable date string. ISO YYYY-MM-DD recommended.
timezonestringNoMust be one of the accepted timezone values below.
additional_phonesstring[]NoList of additional phone values.
additional_emailsstring[]NoInvalid emails are ignored.
street_addressstringNoStreet address.
citystringNoCity.
statestringNoState or province.
zip_codestringNoZIP or postal code.
countrystringNoCountry.
tagsstring[]NoTags to apply to the contact.
friendly_sourcestringNoHuman-readable source label for AI context.
hookstringNoShort description of what the contact is interested in.
service_descriptionstringNoLonger description of requested service or issue.
utm_sourcestringNoUTM source.
utm_mediumstringNoUTM medium.
utm_campaignstringNoUTM campaign.
utm_contentstringNoUTM content.
utm_keywordstringNoUTM keyword.
referrerstringNoReferrer URL.
landing_page_urlstringNoLanding page URL.
facebook_click_idstringNoFacebook fbclid.
google_click_idstringNoGoogle gclid.
google_wbraidstringNoGoogle wbraid.
google_gbraidstringNoGoogle gbraid.
facebook_lead_idstringNoFacebook 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_idstringNoAd 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 typeExpected valueBehavior
textstringSaved as submitted.
numbernumber or numeric stringSaved if numeric, otherwise ignored.
dateparseable date stringSaved as YYYY-MM-DD, otherwise ignored.
selectstring matching one configured optionCase-insensitive match. Non-matching values ignored.
radiostring matching one configured optionCase-insensitive match. Non-matching values ignored.
multiselectstring or string[] matching configured optionsMatching options are saved. Non-matching options ignored.
checkboxboolean-like valueAccepts 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."
}
FieldTypeRequiredNotes
contentstringYesThe note text. Trimmed to 1000 characters if longer.
surface_to_agentsbooleanNoWhether AI agents may read this note. Defaults to false.

Note Visibility

surface_to_agents controls who can read the note:

ValueWho sees the note
false (default)Your team only, in the contact's Notes panel. AI agents never see it.
trueYour 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

SituationBehavior
More than 1000 charactersTrimmed to 1000 characters. The submission still succeeds and the trim is reported in ignored_fields.
Empty or whitespace-only contentNo note is created. Ignored and logged.
note is not a string or object, or content is not a stringNo note is created. Ignored and logged.
surface_to_agents is not a booleanNote is created as internal-only (false) and the value is logged.
The contact already has a note with identical textNo duplicate note is created. Logged as a duplicate.
Submission is rejected for invalid required fieldsNo 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"
      }
    ]
  }
}