Back to Help Home
Last updated: 2026-10-08

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. The same key also authorizes the related endpoints below.

EndpointUse it to
PATCH /v1/accounts/{account_id}/contactsUpdate a contact that already exists, identified by contact_id or phone, without creating a new lead. See Contact Update API.
GET /v1/accounts/{account_id}/custom-fieldsLook up the account's custom field IDs, types, and options to use in custom_fields. See Custom Fields API.

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": [],
  "account_status": "active",
  "notice": null
}
FieldMeaning
account_statusactive normally. frozen when the account is paused for a payment issue (see below).
noticenull normally. A human-readable explanation when account_status is not active.

Account Paused for a Payment Issue

If the target account's payment method has failed or its wallet is empty, brayv.ai pauses outbound activity for that account until billing is fixed. Submissions still succeed: the contact is created and the lead is routed into its journey, but no texts, calls, or AI outreach go out until the account admin resolves the payment issue. Journey steps that come due while the account is paused are held, and are re-scheduled at their normal interval once billing recovers.

{
  "status": "success",
  "submission_id": "e7ac371f-478e-4290-b147-524a0f491ab2",
  "contact_id": "34726940-88ec-49e3-9529-6e0845703eda",
  "idempotent_replay": false,
  "ignored_fields": [],
  "account_status": "frozen",
  "notice": "Account is paused for a payment issue. The contact was created, but no outbound outreach will occur until the payment issue is resolved."
}

Account Inactive

If the target account's brayv.ai subscription has been cancelled, the request returns 403 Forbidden. The contact is still created so the data is there when the account reactivates, but nothing else happens: no opportunity, no journey, no outreach. Have an account admin reactivate the subscription, then resubmit. Do not treat this as a transient error; retrying without reactivating will keep returning 403.

{
  "detail": {
    "code": "account_inactive",
    "message": "Account is inactive. Please have an account admin reactivate the Brayv subscription.",
    "contact_id": "34726940-88ec-49e3-9529-6e0845703eda",
    "submission_id": "e7ac371f-478e-4290-b147-524a0f491ab2"
  }
}

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. See Forwarding Facebook Instant-Form Leads.
campaign_idstringNoAd platform campaign ID (for Facebook leads, the Meta campaign ID - enables per-campaign ROAS reporting).
facebook_ad_idstringNoMeta ad ID (ad_id on a Facebook lead). Credits the lead to the specific ad, so it shows in the campaign's By set of ads results.
brayv_campaign_idstringNoThe brayv_campaign_id URL parameter on a brayv.ai website ad's link. Send it (and the link's brayv_ad_id parameter as facebook_ad_id) if your own website form captures URL parameters on a page brayv.ai ads send people to.

Forwarding Facebook Instant-Form Leads

If your team collects Facebook instant-form leads in its own system first (your CRM, Zapier, or an agency's tool) and forwards them here, include Meta's IDs with each lead. brayv.ai then treats the lead exactly like one it received from Facebook directly:

FieldNeededFacebook lead fieldWhat it does in brayv.ai
facebook_lead_idYesid (also called leadgen_id)Counts the lead as coming from a Facebook ad, and lets brayv.ai match the updates it sends Facebook (first reply, converted lead, job won) to that exact lead
campaign_idYescampaign_idCredits the lead to its campaign in AI Ads reporting
facebook_ad_idOptionalad_idCredits the lead to the specific ad, so it appears in the campaign's By set of ads results

Send both facebook_lead_id and campaign_id. Neither works on its own:

  • With only facebook_lead_id, the lead counts as a Facebook ad lead but isn't credited to any campaign, so it's missing from that campaign's results.
  • With only campaign_id, the lead isn't recognized as coming from a Facebook ad at all.

Without facebook_ad_id, the lead still counts toward its campaign but shows as unmatched in By set of ads. Send Meta's numeric IDs exactly as Facebook provides them, not campaign or ad names.

Send these with the lead itself. Ad credit is decided when the lead is submitted, so adding them later through the Contact Update API doesn't change it.

Don't also connect the form in brayv.ai. brayv.ai only imports a Facebook form's leads directly once that form is mapped under Settings > Lead Sources; until then it skips them on purpose, so teams that forward leads themselves aren't double-counted. If you forward a form's leads through this API, leave that form unmapped in brayv.ai, or every lead will arrive twice. See Facebook Lead Forms.

The journey, assignment, and notifications come from the API lead source the request is sent to, like any other API lead.

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. Use the Custom Fields API to list an account's field IDs, types, and options.

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",
  "facebook_ad_id": "120210000000000123",
  "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"
      }
    ]
  }
}