POST /api/v1/calls/ingest
Ingest a call recording or transcript for AI-powered intent classification and optional Google Ads conversion tracking.
Request
POST https://app.trolleyshield.com/api/v1/calls/ingest
Authorization: Bearer tsk_your_api_key_here
Content-Type: application/json
Body
{
"tracking_number": "+15551234567",
"caller": "+15559876543",
"caller_name": "Jimmy Pesto, Sr.",
"transcript": "Hi, I'm calling about getting a quote for a new roof...",
"duration": 145,
"call_time": "2026-03-25T14:30:00Z",
"external_call_id": "call_abc123",
"gclid": "CjwKCAiAhreqBhAvEiwAQWDgMrxZ...",
"utm_source": "google",
"utm_medium": "cpc",
"utm_campaign": "roofing_dallas",
"business_context": "Residential roofing company in Dallas, TX"
}
Required Fields
| Field | Type | Description |
|---|---|---|
tracking_number | string | The tracking phone number (normalized automatically) |
transcript | string* | Call transcript text |
audio_url | string* | URL to audio file for automatic transcription |
* Either transcript or audio_url is required. If both are provided, transcript takes priority.
Optional Fields
| Field | Type | Description |
|---|---|---|
caller | string | Caller phone number |
caller_name | string | Caller ID name (displayed in call views) |
duration | integer | Call duration in seconds (defaults to 0) |
call_time | string | ISO 8601 datetime of the call |
external_call_id | string | Your system's call ID (used for deduplication) |
caller_city | string | Caller's city |
caller_state | string | Caller's state |
caller_country | string | Caller's country |
is_voicemail | boolean | Mark as voicemail (defaults to false) |
business_context | string | Override account-level business context for this analysis |
Attribution Fields
| Field | Type | Description |
|---|---|---|
gclid | string | Google Click ID for conversion tracking |
gbraid | string | Google brand ID (iOS) |
wbraid | string | Web-to-App bridge ID |
fbclid | string | Facebook Click ID |
msclid | string | Microsoft Click ID |
utm_source | string | Marketing source (e.g. google, facebook) |
utm_medium | string | Marketing medium (e.g. cpc, organic, email) |
utm_campaign | string | Campaign name |
utm_term | string | Search keywords |
utm_content | string | Ad content variant |
landing_page | string | Landing page URL |
referrer | string | Referrer URL |
ga_client_id | string | Google Analytics client ID (_ga cookie value) for GA4 session stitching |
fbp | string | Facebook browser ID (_fbp cookie value) for Meta CAPI matching |
You don't need to provide business_context in every request — it's automatically pulled from your account settings. Set it in Settings → General → Business Context.
Response
Success (201)
{
"call_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"transcript_status": "completed",
"intent": "qualified_lead",
"confidence": 92,
"reasoning": "Caller is requesting a specific service quote with timeline and budget discussion.",
"action": "convert",
"conversion_queued": true
}
| Field | Type | Description |
|---|---|---|
call_id | string | UUID of the created call record |
transcript_status | string | completed, pending, or failed |
intent | string | null | AI classification (see table below) |
confidence | number | null | Confidence score (0-100) |
reasoning | string | null | Explanation of the classification |
action | string | null | Recommended action |
conversion_queued | boolean | Whether a Google Ads conversion was queued |
Intent Values
| Intent | Action | Description |
|---|---|---|
qualified_lead | convert | Actively looking to purchase, book, or commit |
general_inquiry | ignore | Early research, price shopping, gathering info |
existing_customer | ignore | References past orders, account, or previous interactions |
spam | block | Telemarketer, robocall, or solicitation |
wrong_number | ignore | Reached wrong business or unrelated services |
job_seeker | ignore | Employment inquiry or resume submission |
vendor | ignore | B2B sales call from supplier or service provider |
incomplete | ignore | Dropped call, too short, or no conversation |
Error Responses
| Status | Error | Description |
|---|---|---|
| 400 | Invalid request | Missing required fields or validation failure |
| 401 | Missing or invalid API key | No Bearer token or key doesn't exist |
| 403 | API access requires a paid plan | Account is on free tier |
| 403 | Call plan required | Account is on a Form Shield-only plan |
| 404 | Tracking number not found | No matching tracking number in your account |
| 409 | Duplicate call | external_call_id already exists |
| 429 | Usage limit exceeded | Trial/free tier limit reached |
| 500 | Internal server error | Something went wrong on our end |
Example: Node.js
const response = await fetch('https://app.trolleyshield.com/api/v1/calls/ingest', {
method: 'POST',
headers: {
'Authorization': 'Bearer tsk_your_api_key_here',
'Content-Type': 'application/json',
},
body: JSON.stringify({
tracking_number: '+15551234567',
caller: '+15559876543',
caller_name: 'Jimmy Pesto, Sr.',
transcript: 'Hi, I need a quote for roof repair on my home...',
duration: 180,
gclid: 'CjwKCAiAhreqBhAvEiwAQWDgMrxZ...',
utm_source: 'google',
utm_medium: 'cpc',
utm_campaign: 'roofing_dallas',
}),
});
const result = await response.json();
console.log(`Intent: ${result.intent}, Action: ${result.action}`);
if (result.conversion_queued) {
console.log('Google Ads conversion queued for upload');
}
Example: Python
import requests
response = requests.post(
'https://app.trolleyshield.com/api/v1/calls/ingest',
headers={
'Authorization': 'Bearer tsk_your_api_key_here',
'Content-Type': 'application/json',
},
json={
'tracking_number': '+15551234567',
'caller': '+15559876543',
'caller_name': 'Jimmy Pesto, Sr.',
'transcript': 'Hi, I need a quote for roof repair on my home...',
'duration': 180,
'gclid': 'CjwKCAiAhreqBhAvEiwAQWDgMrxZ...',
'utm_source': 'google',
'utm_medium': 'cpc',
'utm_campaign': 'roofing_dallas',
},
)
result = response.json()
print(f"Intent: {result['intent']}, Action: {result['action']}")
Example: cURL
curl -X POST https://app.trolleyshield.com/api/v1/calls/ingest \
-H "Authorization: Bearer tsk_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"tracking_number": "+15551234567",
"caller": "+15559876543",
"caller_name": "Jimmy Pesto, Sr.",
"transcript": "Hi, I need a quote for roof repair on my home...",
"duration": 180,
"gclid": "CjwKCAiAhreqBhAvEiwAQWDgMrxZ...",
"utm_source": "google",
"utm_medium": "cpc",
"utm_campaign": "roofing_dallas"
}'
Deduplication
If you provide external_call_id, the API will reject duplicate submissions with a 409 status. This lets you safely retry failed requests without creating duplicate records.
Conversion Tracking
When a call is classified as qualified_lead and meets the confidence and duration thresholds, and a gclid, gbraid, or wbraid is provided, a conversion record is automatically queued for upload to Google Ads.
You can configure conversion thresholds per tracking number:
- Conversion Action — which Google Ads conversion to fire
- Conversion Value — dollar amount to report
- Min Confidence — minimum AI confidence score (default: 80)
- Min Duration — minimum call length in seconds (default: 60)
- Qualifying Intents — which intents trigger conversions (default:
qualified_lead)
To use conversion tracking, connect your Google Ads account in Settings → Integrations → Google Ads.
Attribution
Attribution fields (utm_source, utm_medium, utm_campaign, gclid, etc.) are stored with the call record and displayed in the call detail view under Source Attribution. This gives you visibility into which marketing channels drive each call.
When gclid is provided alongside a qualifying call, Google Ads can attribute the offline conversion back to the specific ad click.