# TAMradar Agent Instructions
Source: https://docs.tamradar.com/agentic/tamradar-agent
Agent instruction file for setting up, retrieving, and managing TAMradar monitoring.
**π€ For humans**
Give this to your agent and it will set up TAMradar monitoring for you β create radars, check balance, retrieve findings.
**Give this link to your agent:** [https://docs.tamradar.com/agentic/tamradar-agent.md](https://docs.tamradar.com/agentic/tamradar-agent.md)
***
***
You set up TAMradar monitoring for a user. Read this file top to bottom before starting.
**Base URL:** `https://api.tamradar.com`
**Auth header:** `x-api-key: {TAMRADAR_API_KEY}` β required on every request. No Bearer prefix.
***
## Authentication
Check the environment for `TAMRADAR_API_KEY`. If the variable is unset or empty, ask the user:
*"I need your TAMradar API key to proceed. You can find it in your account settings at tamradar.com."*
Never guess or construct the key. Do not proceed without it.
**Canonical request format β use this exact pattern for every call:**
```bash theme={null}
curl -s -X POST "https://api.tamradar.com/v1/radars/companies" \
-H "x-api-key: $TAMRADAR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "domain": "openai.com", "radar_type": "company_job_openings" }'
```
`x-api-key` is the auth header. `Content-Type: application/json` is required on all POST requests.
***
## How this works β overview
```
1. PRE-FLIGHT GET /v1/account β check balance, block if $0
2. CREATE POST /v1/radars/companies β company signals
POST /v1/radars/contacts β person signals
POST /v1/radars/industry β industry-wide signals
POST /v1/radars/bulk β >10 targets: async queue (max 1000 items per request)
3. RETRIEVE GET /v1/updates β poll for findings (always available)
[webhook] β push delivery (faster, if webhook_url set)
4. MANAGE DELETE /v1/radars/:id β stop monitoring a target
```
Detailed breakdown follows.
***
## Inputs β collect before starting
| Input | Required | Notes |
| ------------------ | --------- | ----------------------------------------------------------------------------------------------------------------------- |
| `TAMRADAR_API_KEY` | MUST have | UUID format β check env first, ask if unset |
| Target list | MUST have | Companies (domain), people (name + LinkedIn URL + company domain), or industry keywords |
| `webhook_url` | Optional | Omit for poll-only. Add it for real-time push delivery. |
| `updates_since` | Optional | Use only when the user explicitly requests historical data or names an earliest event date. Must be now or in the past. |
### Webhook URL β default to polling
`webhook_url` is optional. **Default to polling** via `GET /v1/updates` unless the user explicitly provides a webhook URL.
**If no webhook URL provided:** omit `webhook_url` entirely β the radar will be poll-only. Retrieve findings via `GET /v1/updates`. No need to ask.
**If user provides a real webhook URL** (Make.com, Clay, Zapier, n8n): include it. Real-time delivery will work automatically.
β οΈ Once set, `webhook_url` cannot be changed without deactivating and recreating the radar. Avoid ephemeral test URLs like `webhook.site` for production radars.
***
## Phase 1: Pre-flight β ALWAYS run first
```
GET /v1/account
```
**Sample response:**
```json theme={null}
{
"status": "success",
"code": 200,
"data": {
"balance_remaining_usd": 42.50,
"account": {
"active_radars": 12,
"inactive_radars": 2,
"failed_radars": 0,
"total_radars": 14,
"total_updates_found": 3847
},
"usage": {
"month": "2026-05",
"radars_activated": 3,
"radars_deactivated": 0,
"radars_failed": 0,
"balance_used_usd": 7.50,
"updates_found": 142
}
}
}
```
**Balance gate β enforce strictly:**
* IF `data.balance_remaining_usd == 0` β **STOP.** Tell user: *"Your TAMradar balance is \$0. Add funds at tamradar.com before I can create any radars."*
* IF `data.balance_remaining_usd < 5` β **WARN.** Tell user the balance, ask if they want to continue.
* IF `data.balance_remaining_usd >= 5` β Proceed. Report balance to user.
### Recommend: balance monitoring cron (to user only β do not auto-install)
Tell the user:
> *"To avoid radars being deactivated unexpectedly, I recommend setting up a daily job that calls `GET /v1/account` and alerts you if `balance_remaining_usd` drops below a threshold (e.g. $10). TAMradar auto-deactivates radars when balance hits $0 and sends a `radar_failure` webhook β but proactive monitoring is better than reacting after the fact."*
Only suggest this. Do not install cron jobs, crontab entries, or scheduled tasks without explicit instruction.
***
## Phase 2: Create radars
### Idempotency β check local store first
Before every POST, look up `(domain, radar_type)` in `./tamradar-radars.jsonl` (your local mapping store). If a matching entry exists, skip the POST and use the stored `radar_id`. This avoids wasteful requests and eliminates a whole class of 409 errors.
### Singles vs bulk
* **β€10 targets** β create one by one (single POSTs). Simple, easy to debug.
* **>10 targets** β use `POST /v1/radars/bulk` (async queue, max 1000 items per request).
### Single radar creation
`updates_since` is optional. Never invent or infer it. Include it only when the
user explicitly asks for historical data or supplies an earliest date. Require
an RFC 3339 timestamp with timezone; future scheduling is not supported. Omit it
to use radar creation time. Do not use the deprecated `include_historical`
field.
**`custom_fields` recommendation:** always pass your internal IDs here. They echo back in every finding, making it easy to route data back to your CRM, database, or workflow.
```json theme={null}
"custom_fields": {
"crm_account_id": "0011U00000TFV7MQAX",
"owner": "alice@yourcompany.com",
"label": "enterprise-tier"
}
```
#### Company radar
```bash theme={null}
curl -s -X POST "https://api.tamradar.com/v1/radars/companies" \
-H "x-api-key: $TAMRADAR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"domain": "openai.com",
"radar_type": "company_job_openings",
"webhook_url": "https://hook.make.com/your-scenario-url",
"custom_fields": { "crm_account_id": "0011U00000TFV7MQAX" }
}'
```
#### Contact radar
```bash theme={null}
curl -s -X POST "https://api.tamradar.com/v1/radars/contacts" \
-H "x-api-key: $TAMRADAR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"radar_type": "contact_job_changes",
"domain": "openai.com",
"profile_url": "https://www.linkedin.com/in/samaltman",
"full_name": "Sam Altman",
"webhook_url": "https://hook.make.com/your-scenario-url",
"custom_fields": { "crm_contact_id": "003Dn00000AHtLQIA1" }
}'
```
#### Industry radar
```bash theme={null}
curl -s -X POST "https://api.tamradar.com/v1/radars/industry" \
-H "x-api-key: $TAMRADAR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"radar_type": "industry_funding_rounds",
"webhook_url": "https://hook.make.com/your-scenario-url",
"custom_fields": { "label": "ai-funding-watch" }
}'
```
### β οΈ CRITICAL: persist the mapping after every 201
Store immediately to `./tamradar-radars.jsonl` (append one JSON object per line):
```json theme={null}
{ "input_domain": "openai.com", "input_radar_type": "company_job_openings", "radar_id": "c70813b3-7e87-4ca7-90fd-8c64574d911b", "created_at": "2026-05-01T09:05:00Z", "updates_since": null }
```
This file IS your monitoring state. Without it you cannot poll by specific radar, deactivate targets, or check idempotency before creating.
### Bulk creation β for >10 targets
Send up to 1000 radars per request. `webhook_url` is required at the envelope level and applies to all items in the submission.
```bash theme={null}
curl -s -X POST "https://api.tamradar.com/v1/radars/bulk" \
-H "x-api-key: $TAMRADAR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"webhook_url": "https://hook.make.com/your-scenario-url",
"radars": [
{ "domain": "openai.com", "radar_type": "company_job_openings", "custom_fields": { "crm_account_id": "001" } },
{ "domain": "anthropic.com", "radar_type": "company_new_hires", "custom_fields": { "crm_account_id": "002" } }
]
}'
```
**Bulk submission response β HTTP 202 accepted:**
* Response returns `bulk_id` and submission summary only.
* Item-level outcomes are asynchronous and arrive via:
* webhook events (`radar_created` / `radar_failure` / `bulk_completed`)
* `GET /v1/radars/bulk/{bulk_id}` polling
***
## Phase 3: Error handling
MUST handle every error before reporting success.
| Code | Meaning | Action |
| ----- | -------------------- | -------------------------------------------------------------------------------------------------- |
| `401` | Invalid API key | STOP. Ask user to verify key. |
| `402` | Insufficient balance | STOP. Tell user to add funds, then retry. |
| `409` | Radar already exists | NOT an error β it's already running. Read `errors[0].conflicting_radar_id`, store it, and move on. |
| `400` | Validation error | Show `errors[].field` + `errors[].reason` verbatim. Ask user to correct. |
| `429` | Rate limited | Wait, then retry. NEVER retry more than 3 times. |
| `5xx` | Server error | Retry once. If still failing, report `error_id` to user and STOP. |
***
## Phase 4: Retrieve findings (polling)
`GET /v1/updates` returns the same data as webhooks. The response uses `updates[]` β not `data[]`.
**Note:** findings are retained for 60 days. Data older than 60 days is purged.
**Note:** `radar_failure` events are excluded by default. Default `update_type` is `radar_finding` only.
**Sample response:**
```json theme={null}
{
"status": "success",
"code": 200,
"updates": [
{
"update_id": "550e8400-e29b-41d4-a716-446655440000",
"update_type": "radar_finding",
"discovered_at": "2026-05-01T08:30:00Z",
"data": {
"radar_id": "c70813b3-7e87-4ca7-90fd-8c64574d911b",
"radar_type": "company_job_openings",
"domain": "openai.com",
"custom_fields": { "crm_account_id": "001" }
},
"content": { }
}
],
"has_more": false,
"next_cursor": null
}
```
### Fetch findings for specific radars (max 10 radar\_ids per call)
```
GET /v1/updates?radar_id={id1},{id2}&limit=50
```
If you have more than 10 radars, split into multiple calls of up to 10 IDs each.
### Fetch by signal type
```
GET /v1/updates?radar_type=company_job_openings,company_new_hires&limit=50
```
### Incremental β only new since last check
```
GET /v1/updates?since={last_discovered_at}&limit=100
```
ALWAYS store `discovered_at` from the last update you processed. Pass it as `since` on the next call.
### Pagination
IF `has_more: true` β fetch next page:
```
GET /v1/updates?cursor={next_cursor}
```
Repeat until `has_more: false`.
### To include radar failures in polling
```
GET /v1/updates?update_type=radar_finding,radar_failure
```
### Presenting findings to the user
Group by `data.radar_type`. Always include `discovered_at`. Example:
> **New job at openai.com** β Senior Research Engineer, Alignment
> Detected: 2026-04-29 Β· [View posting](https://openai.com/careers/...)
***
## Phase 5: Deactivate radars
```bash theme={null}
curl -s -X DELETE "https://api.tamradar.com/v1/radars/{radar_id}" \
-H "x-api-key: $TAMRADAR_API_KEY"
```
* Irreversible. Must create a new radar to resume monitoring.
* No further charges after deactivation.
IF user says "stop monitoring openai.com for job openings" β look up `radar_id` from `./tamradar-radars.jsonl` and DELETE it.
For multiple radars, send one synchronous request:
```bash theme={null}
curl -s -X DELETE "https://api.tamradar.com/v1/radars/bulk" \
-H "x-api-key: $TAMRADAR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"radar_ids":["RADAR_UUID_1","RADAR_UUID_2"]}'
```
Use single DELETE for one radar and bulk DELETE for 2β500 known IDs. Check
every `radars[]` item: the top-level response is `200` even when some IDs are
missing or already inactive.
***
## Phase 6: Retrieving findings
**Default: use polling.** `GET /v1/updates` always works regardless of `webhook_url`. Findings are available immediately after creation and retained for 60 days.
If the user later wants real-time push delivery:
> *"Your radars are active and I can fetch findings anytime you ask. For real-time delivery into Slack, your CRM, or an automation, connect a tool like Make.com or Clay β it generates a webhook URL you can give me and I'll update the setup."*
***
## Summary template β ALWAYS present when done
```
TAMradar setup complete.
Balance: $42.50 remaining
Radars active:
company_job_openings Β· openai.com Β· c70813b3-7e87-4ca7-90fd-8c64574d911b
contact_job_changes Β· Sam Altman Β· a4f28c91-3b12-4e70-9d55-7e3dda58f100 (already existed β skipped POST)
industry_funding_rounds Β· β Β· f9d01c55-82ba-4f3e-b461-2a9e7c11d830
Failures:
None
Mapping saved to:
./tamradar-radars.jsonl
To fetch findings now:
GET /v1/updates?radar_id=c70813b3-...,a4f28c91-...&limit=50
(max 10 IDs per call β split into multiple requests if needed)
Findings retained for 60 days.
Webhook: Make.com scenario (https://hook.make.com/...) β
β OR β
Polling only. Ask me anytime to check for new findings.
```
***
## Endpoint reference (machine-readable)
Fetch these as raw markdown for full schema details:
* **Company radar:** `https://docs.tamradar.com/api-reference/create-company-radar.md`
* **Contact radar:** `https://docs.tamradar.com/api-reference/create-contact-radar.md`
* **Industry radar:** `https://docs.tamradar.com/api-reference/create-industry-radar.md`
* **Poll updates:** `https://docs.tamradar.com/api-reference/poll-updates.md`
* **Account:** `https://docs.tamradar.com/api-reference/account.md`
***
## Radar type reference
### Company β `POST /v1/radars/companies`
Required: `domain`, `radar_type` β `webhook_url` optional (omit for poll-only)
| `radar_type` | What it tracks |
| ---------------------------- | ----------------------------- |
| `company_job_openings` | New job postings |
| `company_new_hires` | People joining the company |
| `company_promotions` | Internal promotions |
| `company_reviews` | Glassdoor/employer reviews |
| `company_mentions` | Web mentions of the company |
| `company_social_posts` | Company LinkedIn/social posts |
| `company_social_posts_cxo` | Executive social posts |
| `company_social_engagements` | Engagements on company posts |
#### Domain normalization
The API accepts any format β bare domain, URL, or with www/path/port. All are normalized automatically:
* `openai.com` β
* `https://www.openai.com/about` β
β normalized to `openai.com`
* `www.openai.com` β
β normalized to `openai.com`
Best practice: pass the bare domain (`openai.com`).
### Contact β `POST /v1/radars/contacts`
Required: `radar_type` + type-specific identifiers β `webhook_url` optional (omit for poll-only):
| `radar_type` | Additional required fields |
| ---------------------------- | ---------------------------------------------------------------------------- |
| `contact_job_changes` | `domain` + at least one of: `email`, `profile_url`, `full_name` |
| `contact_social_engagements` | `profile_url` (must contain `linkedin.com/in/`, `x.com/`, or `twitter.com/`) |
| `contact_social_posts` | `profile_url` (same URL formats) |
#### Profile URL normalization
Pass the full URL. The API normalizes automatically:
* `https://www.linkedin.com/in/samaltman` β
(preferred)
* `https://linkedin.com/in/samaltman` β
β normalized to `https://www.linkedin.com/in/samaltman`
* `linkedin.com/in/samaltman` β
β `https://` added automatically
* Trailing slash stripped automatically
For `contact_job_changes`, LinkedIn company, school, and legacy `/pub/` URLs are
rejected; only person profiles under `/in/` are accepted. Email must identify an
individual, so role-based, shared, and placeholder addresses are rejected.
For `contact_job_changes`, uniqueness is evaluated within the same account,
domain, and radar type. An exact case-insensitive email match or a normalized
LinkedIn profile URL match returns `409`; `full_name` alone is not a duplicate
key. The error field is `identifiers.email` or `identifiers.profile_url`, showing
which identifier matched. Different radar types for the same person are allowed.
### Industry β `POST /v1/radars/industry`
Required: `radar_type`. No `domain` field β `webhook_url` optional (omit for poll-only).
| `radar_type` | Additional required fields |
| ------------------------- | -------------------------------------------------------- |
| `industry_funding_rounds` | None |
| `industry_new_companies` | None |
| `industry_mentions` | `keyword` (min 3 chars) |
| `industry_job_openings` | `keyword` (min 3 chars) + optional `countries[]` (max 5) |
***
## Key fields reference
| Field | Found in | Purpose |
| ----------------- | -------------------------------------- | ----------------------------------------------------------------- |
| `radar_id` | Creation response β `data.radar_id` | MUST store with input. Used for polling, deactivation. |
| `update_id` | Every finding payload | Deduplicate on this. NEVER process the same `update_id` twice. |
| `discovered_at` | Every finding payload | Store latest value. Use as `since` param for incremental polling. |
| `custom_fields` | Request body β echoed in every finding | Your internal IDs. Route findings back to your CRM using these. |
| `data.radar_type` | Every finding payload | Route your processing logic on this. |
| `content` | Every finding payload | The actual finding. Shape varies by `radar_type`. |
***
## Hard limits
* `webhook_url` is optional β omit for poll-only radars; include only when real-time push delivery is needed
* Rate limit: **100 write requests / 60 seconds**, **200 read requests / 60 seconds** β need more? contact [support@tamradar.com](mailto:support@tamradar.com)
* Bulk max: **1000 radars** per request
* Polling `radar_id` filter: **max 10 IDs** per call β split into multiple requests if you have more
* Polling data retention: **60 days** β older data is purged
* Company uniqueness: one radar per `domain + radar_type` per account β duplicates get `409`
* Contact job-change uniqueness: within the same `domain + radar_type`, an email or normalized profile URL match gets `409`; `full_name` is not a duplicate key
* Different contact radar types for the same person are allowed
* Persistence: save mappings to `./tamradar-radars.jsonl` by default
# TAMradar Monitoring
Source: https://docs.tamradar.com/agentic/tamradar-skill
Skill file for coding agents to configure TAMradar radars and retrieval workflows.
**π€ For humans**
This is a Claude Code skill file. Save it as `.claude/skills/tamradar/SKILL.md` in your project. Claude Code will load it automatically when you mention TAMradar, or invoke it directly with `/tamradar`.
**Give this link to your agent:** [https://docs.tamradar.com/agentic/tamradar-skill.md](https://docs.tamradar.com/agentic/tamradar-skill.md)
***
# TAMradar Monitoring
**Full instructions:** [https://docs.tamradar.com/agentic/tamradar-agent.md](https://docs.tamradar.com/agentic/tamradar-agent.md)
**Base URL:** `https://api.tamradar.com` Β· **Auth:** `x-api-key: $TAMRADAR_API_KEY` on every request β no Bearer prefix
## Key Rules
* **Auth:** check `$TAMRADAR_API_KEY` in env first β if unset, ask the user
* **webhook\_url:** optional β omit for poll-only radars (default); include only if the user provides a real webhook URL for push delivery
* **updates\_since:** optional β include only when the user explicitly requests historical data or supplies an earliest date; require RFC 3339 with timezone, never use a future value, and never invent one
* **include\_historical:** deprecated β do not send it; use `updates_since`
* **Persistence:** save `(domain, radar_type, radar_id)` to `./tamradar-radars.jsonl` after every 201
* **Idempotency:** check `./tamradar-radars.jsonl` before every POST β skip if already exists
* **Singles vs bulk:** β€10 targets β single POSTs; >10 β `POST /v1/radars/bulk` (async queue, max 1000 items per request)
* **409 is not an error** β read `errors[0].conflicting_radar_id`, store it, move on
* **Polling response:** field is `updates[]` β not `data[]`
* **Polling cap:** max 10 `radar_id` values per call β split into multiple requests if needed
## Flow
```
1. PRE-FLIGHT β GET /v1/account (check balance β block if $0)
2. CREATE β POST /v1/radars/companies (company signals)
POST /v1/radars/contacts (person signals)
POST /v1/radars/industry (industry-wide signals)
POST /v1/radars/bulk (>10 targets)
3. RETRIEVE β GET /v1/updates
4. MANAGE β DELETE /v1/radars/:id (one) or DELETE /v1/radars/bulk (2β500)
```
## Endpoint reference (machine-readable)
* `https://docs.tamradar.com/api-reference/create-company-radar.md`
* `https://docs.tamradar.com/api-reference/create-contact-radar.md`
* `https://docs.tamradar.com/api-reference/create-industry-radar.md`
* `https://docs.tamradar.com/api-reference/poll-updates.md`
* `https://docs.tamradar.com/api-reference/account.md`
## Pre-flight
```bash theme={null}
curl -s "https://api.tamradar.com/v1/account" -H "x-api-key: $TAMRADAR_API_KEY"
```
```json theme={null}
{
"data": {
"balance_remaining_usd": 42.50,
"account": { "active_radars": 12, "total_updates_found": 3847 },
"usage": { "month": "2026-05", "balance_used_usd": 7.50, "updates_found": 142 }
}
}
```
`balance_remaining_usd == 0` β STOP Β· `< 5` β WARN and confirm Β· `>= 5` β proceed
## Create β company
```bash theme={null}
curl -s -X POST "https://api.tamradar.com/v1/radars/companies" \
-H "x-api-key: $TAMRADAR_API_KEY" -H "Content-Type: application/json" \
-d '{
"domain": "openai.com",
"radar_type": "company_job_openings",
"custom_fields": { "crm_account_id": "001" }
}'
```
Domain accepts any format (`https://www.openai.com/about` β normalized to `openai.com`). Best practice: bare domain.
## Create β contact
```bash theme={null}
curl -s -X POST "https://api.tamradar.com/v1/radars/contacts" \
-H "x-api-key: $TAMRADAR_API_KEY" -H "Content-Type: application/json" \
-d '{
"radar_type": "contact_job_changes",
"domain": "openai.com",
"profile_url": "https://www.linkedin.com/in/samaltman",
"full_name": "Sam Altman",
"custom_fields": { "crm_contact_id": "003" }
}'
```
For `contact_job_changes`, use an individual email or a LinkedIn `/in/` person
profile; role-based/shared emails and company, school, or `/pub/` LinkedIn URLs
are rejected. Within the same domain and radar type, either an email match or a
normalized profile URL match returns `409`; `full_name` alone is not a duplicate
key. Read `errors[0].field` to see whether `identifiers.email` or
`identifiers.profile_url` matched.
## Create β industry
```bash theme={null}
curl -s -X POST "https://api.tamradar.com/v1/radars/industry" \
-H "x-api-key: $TAMRADAR_API_KEY" -H "Content-Type: application/json" \
-d '{ "radar_type": "industry_funding_rounds" }'
```
## Persist after every 201
```bash theme={null}
echo '{ "input_domain": "openai.com", "input_radar_type": "company_job_openings", "radar_id": "c70813b3-...", "created_at": "2026-05-01T09:05:00Z", "updates_since": null }' \
>> ./tamradar-radars.jsonl
```
## Poll for findings
```bash theme={null}
# By radar ID (max 10 IDs)
curl -s "https://api.tamradar.com/v1/updates?radar_id=id1,id2&limit=50" \
-H "x-api-key: $TAMRADAR_API_KEY"
# Incremental β store discovered_at from last result, pass as since
curl -s "https://api.tamradar.com/v1/updates?since=2026-05-01T08:00:00Z&limit=100" \
-H "x-api-key: $TAMRADAR_API_KEY"
```
```json theme={null}
{
"updates": [
{
"update_id": "550e8400-...",
"update_type": "radar_finding",
"discovered_at": "2026-05-01T08:30:00Z",
"data": { "radar_id": "c70813b3-...", "radar_type": "company_job_openings", "domain": "openai.com" },
"content": { }
}
],
"has_more": false,
"next_cursor": null
}
```
Paginate: while `has_more: true` β fetch `?cursor={next_cursor}`. Findings retained **60 days**.
## Error handling
| Code | Action |
| ----- | ------------------------------------------------------ |
| `401` | STOP β ask user to verify API key |
| `402` | STOP β tell user to add funds |
| `409` | Read `errors[0].conflicting_radar_id`, store, continue |
| `400` | Show `errors[].reason` verbatim, ask user to fix |
| `429` | Wait, retry max 3Γ |
| `5xx` | Retry once, then STOP and report `error_id` |
## Summary (always present when done)
```
TAMradar setup complete.
Balance: $X.XX remaining
Radars active:
company_job_openings Β· openai.com Β· c70813b3-...
contact_job_changes Β· Sam Altman Β· a4f28c91-... (already existed β skipped POST)
industry_funding_rounds Β· β Β· f9d01c55-...
Failures: None
Mapping saved to: ./tamradar-radars.jsonl
Poll now: GET /v1/updates?radar_id=c70813b3-...,a4f28c91-...&limit=50
(max 10 IDs per call β split if needed)
Findings retained 60 days.
```
# Account Summary
Source: https://docs.tamradar.com/api-reference/account
api_specs.yaml GET /v1/account
Get usage and balance summary for the authenticated account.
# Get Bulk Create Status
Source: https://docs.tamradar.com/api-reference/bulk-status
api_specs.yaml GET /v1/radars/bulk/{bulk_id}
Poll async bulk submission status and per-item outcomes.
## How bulk status works
After submitting a bulk request, the response transitions through two states:
* **`processing`** β At least one item is still being created. `summary.processing` shows how many are in flight.
* **`completed`** β All items have reached a terminal state (`created` or `failed`). No more changes.
Poll this endpoint until `status = "completed"`, or β if you provided a `webhook_url` β wait for the `bulk_completed` webhook instead. For webhookless (poll-only) submissions, this endpoint is the tracking surface for every outcome, including failures.
## Per-item fields
Each submitted item appears in `radars[]`. While it is running, the item contains
only its index, custom fields, and processing status. Once it settles, the item
contains either the created radar under `data` or the item-level error fields.
Webhook correlation fields may also appear after webhook processing enriches the
stored item payload.
| Field | Present when | Description |
| ------------------- | ------------------------ | -------------------------------------------------------------------------------------------------------------- |
| `item_index` | Always | Zero-based position in the original `radars[]` array |
| `custom_fields` | Always | Custom fields passed on the item |
| `status` | Always | `"processing"`, `"success"`, or `"error"` for the item |
| `update_id` | After webhook enrichment | Correlation ID linking to the item's `radar_created` or `radar_failure` webhook |
| `update_type` | After webhook enrichment | `radar_created` (success) or `radar_failure` (failure) |
| `completed_at` | After webhook enrichment | Timestamp added to the per-item webhook payload |
| `data` | Created items | Full radar object, including nullable `updates_since`, with the same shape as a single-radar creation response |
| `code` / `errors[]` | Failed items | HTTP error code and error details, same shape as sync API errors |
Items are always returned ordered by `item_index`.
Re-fetch until the top-level `status` is `"completed"`. The `radars[]` array
includes all submitted items from the start; in-flight stubs are replaced by
their success or failure fields as processing completes.
# Bulk Create Radars
Source: https://docs.tamradar.com/api-reference/create-bulk-radars
api_specs.yaml POST /v1/radars/bulk
Queue a bulk submission for asynchronous radar creation.
The `bulk_completed` webhook carries the full per-item result array. At 1,000 items with rich `custom_fields` or filter data, the payload can approach 1β2 MB β ensure your receiver can handle it. Alternatively, use [GET /v1/radars/bulk/\{bulk\_id}](/api-reference/get-radars-bulk-status) to fetch results on demand.
# Create Company Radar
Source: https://docs.tamradar.com/api-reference/create-company-radar
api_specs.yaml POST /v1/radars/companies
Create a company radar with company-specific filters.
**`updates_since`**: If omitted, the radar's creation time is used as the eligibility cutoff β your first run will likely return no results. Set it to 24β48 hours ago to receive immediate data, e.g. `2026-08-26T00:00:00Z`. `new_hires` and `promotions` radars always look from the start of the current calendar month regardless of this value.
# Create Contact Radar
Source: https://docs.tamradar.com/api-reference/create-contact-radar
api_specs.yaml POST /v1/radars/contacts
Create a contact radar for person-level tracking.
**`updates_since`**: If omitted, the radar's creation time is used as the eligibility cutoff β your first run will likely return no results. Set it to 24β48 hours ago to receive immediate data, e.g. `2026-08-26T00:00:00Z`. `contact_job_changes` radars do not use this field β job change detection is state-based, not time-bounded.
# Create Industry Radar
Source: https://docs.tamradar.com/api-reference/create-industry-radar
api_specs.yaml POST /v1/radars/industry
Create an industry radar for keyword-level tracking.
**`updates_since`**: If omitted, the radar's creation time is used as the eligibility cutoff β your first run will likely return no results. Set it to 24β48 hours ago to receive immediate data, e.g. `2026-08-26T00:00:00Z`.
# Bulk Deactivate Radars
Source: https://docs.tamradar.com/api-reference/delete-bulk-radars
api_specs.yaml DELETE /v1/radars/bulk
Synchronously deactivate up to 500 radars in one request.
Bulk deactivation is synchronous: the response already contains the final
outcome for every requested ID. Its `bulk_id` is a correlation value for logs;
it is not stored and cannot be used with the bulk-create status endpoint.
Each item is the same success or error response returned by
[`DELETE /v1/radars/{radar_id}`](/api-reference/delete-radar), with only
`bulk_id`, `item_index`, and the requested `radar_id` added. A mixed request
still returns top-level HTTP `200`; inspect each item's `code` and the summary.
The JSON body must contain only `radar_ids`. Supply 1β500 unique UUIDs.
Malformed JSON, empty strings, invalid UUIDs, duplicate UUIDs, unknown fields,
or an empty/oversized array reject the whole request with `400` before any
radar changes.
Monthly radars enter their paid grace period. Update-based radars deactivate
immediately. Missing, unowned, or already inactive radars appear as item-level
failures without preventing other valid items from being deactivated.
## Correlating items to your request
Every item carries a top-level `radar_id` and `item_index` β on **both**
successful and failed items. Use these two fields for all bulk correlation,
regardless of outcome:
* `radar_id` β the exact ID you requested for this item.
* `item_index` β the zero-based position of this item in your `radar_ids` array.
On a **successful** item, `data` is the unchanged single-DELETE resource
response, so `data.radar_id` repeats the same value. This duplication is
intentional: treat `data` as the returned radar resource, and the top-level
`radar_id` as the request correlation field.
On a **failed** item there is no `data` object, but the top-level `radar_id` is
still present β so you never have to branch on success vs. failure just to find
the identifier.
Always identify items through the top-level `radar_id` and `item_index`. Treat
`data.radar_id` as part of the radar resource, not as the correlation key.
This endpoint accepts a JSON body on a `DELETE` request. Some generated SDKs,
older HTTP clients, and proxies silently drop DELETE bodies β if `radar_ids`
arrives empty you'll get a `400`. Verify your client preserves DELETE bodies
before relying on it in production. (curl, modern `fetch`, and Python `requests` do.)
# Deactivate Radar
Source: https://docs.tamradar.com/api-reference/delete-radar
api_specs.yaml DELETE /v1/radars/{radar_id}
Deactivate a radar by ID.
# Get Radar by ID
Source: https://docs.tamradar.com/api-reference/get-radar
api_specs.yaml GET /v1/radars/{radar_id}
Fetch a single radar configuration by radar ID.
# List Radars
Source: https://docs.tamradar.com/api-reference/list-radars
api_specs.yaml GET /v1/radars
Page through radars on your account and filter by ID, type, status, or creation time.
Results are ordered by `created_at` from newest to oldest, with `radar_id` as
the tie-breaker. The response's `count` is the number of radars on the current
page, not the total number of matching radars.
Use this endpoint to reconcile the radar inventory in your system with
TAMradarβfor example after an interrupted sync, a missed creation response, or
an integration incident. Follow every cursor to complete the traversal. If
radars may have been created while you were paging, start a fresh request
without a cursor to pick them up.
## Pagination
Start without a `cursor`. When `has_more` is `true`, send the returned
`next_cursor` as the next request's `cursor`:
```bash theme={null}
curl "https://api.tamradar.com/v1/radars?limit=50&cursor=CURSOR_FROM_PREVIOUS_RESPONSE" \
-H "x-api-key: YOUR_API_KEY"
```
Repeat the same `radar_id`, `radar_type`, `radar_status`, and `created_since`
filters on every page. If you change a filter, restart without a cursor. Treat
cursors as opaque: do not parse, construct, or modify them.
A traversal is not a frozen snapshot. Radars created after you fetch the first
page are newer than its cursor, so they do not disrupt the remaining pages.
Start a new request without a cursor to discover them.
## Creation-time filtering
Use `created_since` to return only radars created at or after an inclusive ISO
8601 timestamp with a timezone:
```bash theme={null}
curl "https://api.tamradar.com/v1/radars?created_since=2026-09-07T09:00:00Z" \
-H "x-api-key: YOUR_API_KEY"
```
This filters radar creation time only. It does not find radars whose status or
configuration changed after the timestamp.
Unknown query parameters return `400`. In particular, pass the response field
`next_cursor` back using the request parameter `cursor`.
# Poll Updates
Source: https://docs.tamradar.com/api-reference/poll-updates
api_specs.yaml GET /v1/updates
Fetch radar findings and failures via cursor-based polling.
# Changelog
Source: https://docs.tamradar.com/changelog
Product updates and API improvements.
## Easier radar reconciliation
You can now page through your radars and more easily reconcile the inventory in
your system with TAMradar after an interrupted sync, missed creation response,
or integration incident. Follow the cursor through every page to complete the
traversal. If radars may have been created while you were paging, start a fresh
request without a cursor to pick them up.
`GET /v1/radars` now returns radars in deterministic newest-first pages:
* Use `limit` to request 1β500 radars per page; the default remains 50.
* When `has_more` is `true`, pass `next_cursor` back as the `cursor` query parameter.
* Use `created_since` with an ISO 8601 timestamp and timezone to include radars created at or after that time.
* `count` is the number of radars on the current page, not a total count.
### Behavior changes
* The maximum `limit` is now 500. Higher values return `400`.
* Unknown query parameters now return `400` instead of being ignored. This catches mistakes such as sending `next_cursor` as the parameter name instead of `cursor`.
* `radar_status=inactive` now includes every radar that is not active.
Keep all filters unchanged while following a cursor. To discover radars created
during a traversal, start a fresh request without a cursor. `created_since`
filters creation time only, not later status or configuration changes.
This endpoint returns radar records. Use [`GET /v1/updates`](/api-reference/poll-updates)
to retrieve radar findings and failures.
See [List Radars](/api-reference/list-radars).
## Social engagement monitoring window extended to 5 days
`contact_social_engagements` and `company_social_engagements` radars now:
* Monitor each post for up to 5 days for net-new engagement activity (previously 48 hours)
* Per-post engagement cap raised from 200 to unlimited β all engagements within the 5-day window are captured
* Use your `updates_since` timestamp (or radar creation time if not set) as the cutoff for eligible posts. To receive historic or backdated activity, set `updates_since` to the earliest date you care about.
See [Contact Social Engagements](/webhooks/contact/social-engagements) and [Company Social Engagements](/webhooks/company/social-engagements).
## Build more precise job-title filters
Company radar `job_titles` filters now support:
* Combining required groups with `AND`, such as `("sales operations") AND (manager OR VP)`.
* Standalone and grouped exclusions with `NOT`.
* `EXACT("Sales Operations Manager")` for case-insensitive, normalized whole-title equality.
* Nested expressions with standard `NOT`, `AND`, then `OR` precedence.
Existing filters remain compatible. Quoted phrases continue to use containment, while `EXACT("...")` is optional new syntax for whole-title matching.
Malformed expressions return an actionable `job_titles` error. Bulk submissions remain asynchronous, with malformed expressions recorded as item-level failures.
See [Filtering and Personas](/guides/filtering-and-personas) and [Create Company Radar](/api-reference/create-company-radar).
## Contact radar validation changes
New `contact_job_changes` radar requests now:
* Reject role-based, shared, and placeholder email addresses.
* Accept only LinkedIn person profiles under `/in/`; company, school, and legacy `/pub/` URLs are rejected.
* Report duplicate matches as `identifiers.email` or `identifiers.profile_url` in `409` responses.
These changes apply to new single and bulk radar creation requests. Existing radars are unaffected.
See [Create Contact Radar](/api-reference/create-contact-radar) and [Error Handling](/guides/error-handling).
## Bulk deactivate radars
### Features
* Send 1β500 unique radar IDs to `DELETE /v1/radars/bulk`.
* Receive final ordered success or failure details for every ID in one synchronous response.
* Reuse the same per-item success and error contract as single-radar deactivation.
### Behavior
* Monthly radars enter their paid grace period; update-based radars deactivate immediately.
* Expected item failures do not prevent valid items from being deactivated.
* Bulk create and bulk delete share the bulk rate-limit budget.
See [Bulk Deactivate Radars](/api-reference/delete-bulk-radars).
## Set a radar start point with `updates_since`
### Features
* Create company, contact, industry, and bulk radars with an optional `updates_since` timestamp.
* Make matching source events at or after that timestamp eligible for delivery.
* Inspect the stored setting in radar responses as a normalized UTC timestamp or `null`.
### Compatibility
* `include_historical` remains accepted, but `updates_since` is the field to use for new integrations.
* Historical results may continue arriving during the first 24 hours after radar creation.
See [Quickstart](/quickstart), [Getting Started](/getting-started), and [Bulk Create](/guides/bulk-create-guide).
# Getting Started with TAMradar
Source: https://docs.tamradar.com/getting-started
Step-by-step guide to authenticate, create radars, and receive updates.
## What is TAMradar?
TAMradar monitors companies, contacts, and industries by tracking their online activity and sending real-time notifications when something changes.
## Core Concepts
* **Radars** β A monitor you configure. You define what to track (a company, a person, or an industry keyword) and what signal to look for (job openings, new hires, funding rounds, etc.).
* **Webhooks** β How you receive events. TAMradar sends a POST request to your URL when a radar fires.
* **Polling** β Alternative to webhooks. Use `GET /v1/updates` to pull events on your schedule.
* **Balance** β Radars cost balance to create and run. Check yours before creating radars.
***
## Radar Types
TAMradar has three categories of radars, each with its own creation endpoint:
| Category | Endpoint | Tracks |
| -------- | --------------------------- | ------------------ |
| Company | `POST /v1/radars/companies` | A specific company |
| Contact | `POST /v1/radars/contacts` | A specific person |
| Industry | `POST /v1/radars/industry` | A keyword or theme |
**Company radar types:** `company_job_openings`, `company_new_hires`, `company_promotions`, `company_reviews`, `company_mentions`, `company_social_posts`, `company_social_posts_cxo`, `company_social_engagements`
**Contact radar types:** `contact_job_changes`, `contact_social_posts`, `contact_social_engagements`
**Industry radar types:** `industry_mentions`, `industry_funding_rounds`, `industry_new_companies`, `industry_job_openings`
***
## Step 1: Authentication
Every request requires your API key in the `x-api-key` header:
```bash theme={null}
curl -H "x-api-key: YOUR_API_KEY" https://api.tamradar.com/v1/account
```
A `401` means your key is missing or invalid. A `200` means you're authenticated.
***
## Step 2: Check Your Balance
Before creating radars, confirm you have sufficient balance:
```bash theme={null}
curl https://api.tamradar.com/v1/account \
-H "x-api-key: YOUR_API_KEY"
```
**Response:**
```json theme={null}
{
"status": "success",
"code": 200,
"message": "Account summary retrieved",
"timestamp": "2024-03-20T12:00:00Z",
"data": {
"usage": {
"month": "2024-03",
"radars_activated": 14,
"radars_deactivated": 3,
"radars_failed": 0,
"updates_found": 42,
"balance_used_usd": 28.50
},
"account": {
"total_radars": 47,
"active_radars": 12,
"inactive_radars": 34,
"failed_radars": 1,
"total_updates_found": 312
},
"balance_remaining_usd": 971.50
}
}
```
Add `?month=YYYY-MM` to get stats for a specific month (defaults to current UTC month).
***
## Step 3: Create a Radar
> **`webhook_url` is optional.** Omit it to create a poll-only radar. Updates are available via [`GET /v1/updates`](#step-5-poll-for-updates). Add it when you want TAMradar to push events to your server in real-time.
### Choose a data starting point with `updates_since`
`updates_since` is optional. Use it when you want to pull qualifying historical
data or ensure that eligible source events start from a date you choose.
* Supply an RFC 3339 timestamp with a timezone, such as
`2026-07-01T09:00:00-04:00`. TAMradar stores and returns the equivalent UTC
instant.
* The value must represent now or a past instant and may be at most five calendar
years old. A five-second server tolerance exists only for clock skew; it does
not enable future scheduling.
* Omit the field to store `updates_since: null`. The radar's `created_at` is
then the effective cutoff.
* Historical processing is asynchronous. Qualifying findings may continue
arriving during the first 24 hours before the radar settles into its normal
recent-update schedule.
```json theme={null}
{
"domain": "stripe.com",
"radar_type": "company_job_openings",
"updates_since": "2026-07-01T09:00:00-04:00"
}
```
The stored response value for that input is `2026-07-01T13:00:00.000Z`.
For new-hire and promotion radars, source dates are evaluated at calendar-month
precision even though the exact submitted timestamp is preserved in the radar
response. During initial engagement history, the parent post's publication time
sets eligibility; recurring engagement delivery uses when TAMradar first
discovers the engagement.
`include_historical` is deprecated. It remains accepted for compatibility but
has no effect on delivery behavior. Use `updates_since` instead.
### Company Radar
Monitor a company for job openings, with optional department and seniority filters:
```bash theme={null}
curl -X POST https://api.tamradar.com/v1/radars/companies \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"domain": "stripe.com",
"radar_type": "company_job_openings",
"webhook_url": "https://your-domain.com/webhook",
"updates_since": "2024-03-01T09:00:00-05:00",
"departments": ["Engineering"],
"seniorities": ["Senior", "Manager"],
"custom_fields": {
"crm_account_id": "0011U00000TFV7MQAX"
}
}'
```
**Filterable company types:** `company_job_openings`, `company_new_hires`, `company_promotions`, `company_social_posts_cxo`
**Valid departments:** `Engineering`, `Sales`, `Marketing`, `Finance`, `Human Resources`, `Product Management`, `Operations`, `Customer Success`, `Design`, `Legal`, `Information Technology`, `Leadership`, `Research`, `Accounting`, `Administrative`, `Business Development`, `Consulting`, `Education`, `Manufacturing`, `Media and Communication`, `Project Management`, `Purchasing`, `Quality Assurance`, `Real Estate`, `Support`
**Valid seniorities:** `Owner`, `CXO`, `Vice President`, `Director`, `Manager`, `Senior`, `Entry`, `Training`, `Partner`
**Response (201):**
```json theme={null}
{
"status": "success",
"code": 201,
"message": "Radar created successfully",
"timestamp": "2024-03-20T12:05:00Z",
"data": {
"radar_id": "c70813b3-7e87-4ca7-90fd-8c64574d911b",
"radar_type": "company_job_openings",
"domain": "stripe.com",
"webhook_url": "https://your-domain.com/webhook",
"radar_status": "active",
"departments": ["Engineering"],
"seniorities": ["Senior", "Manager"],
"custom_fields": {
"crm_account_id": "0011U00000TFV7MQAX"
},
"created_at": "2024-03-20T12:05:00Z",
"updates_since": "2024-03-01T14:00:00.000Z",
"deactivated_at": null,
"next_charge_at": "2024-04-20T12:05:00Z"
}
}
```
Save the `radar_id`. You'll need it later.
***
### Contact Radar
Track a specific person's job changes or social activity:
```bash theme={null}
# Track job changes for a person
curl -X POST https://api.tamradar.com/v1/radars/contacts \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"radar_type": "contact_job_changes",
"domain": "stripe.com",
"profile_url": "https://www.linkedin.com/in/johndoe",
"webhook_url": "https://your-domain.com/webhook",
"custom_fields": {
"contact_id": "con_abc123"
}
}'
```
```bash theme={null}
# Track social posts from a specific person
curl -X POST https://api.tamradar.com/v1/radars/contacts \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"radar_type": "contact_social_posts",
"profile_url": "https://www.linkedin.com/in/johndoe",
"webhook_url": "https://your-domain.com/webhook"
}'
```
**Rules:**
* `contact_job_changes` β requires `domain` + at least one of: `profile_url`, `email`, `full_name`
* `contact_social_posts` / `contact_social_engagements` β requires `profile_url` (LinkedIn, X, or Twitter only)
* For `contact_job_changes`, email must identify an individual; role-based, shared, and placeholder addresses are rejected
* For `contact_job_changes`, `profile_url` must be a LinkedIn person profile (`linkedin.com/in/...`); company, school, and legacy `/pub/` URLs are rejected
* Within the same company and radar type, duplicate matching uses email or the normalized LinkedIn profile URL; `full_name` is not a duplicate key
***
### Industry Radar
Track a keyword or theme across companies and news:
```bash theme={null}
# Track all funding rounds (no keyword needed)
curl -X POST https://api.tamradar.com/v1/radars/industry \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"radar_type": "industry_funding_rounds",
"webhook_url": "https://your-domain.com/webhook"
}'
```
```bash theme={null}
# Track mentions of a topic
curl -X POST https://api.tamradar.com/v1/radars/industry \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"radar_type": "industry_mentions",
"keyword": "zero trust security",
"countries": ["US", "GB"],
"webhook_url": "https://your-domain.com/webhook"
}'
```
**Rules:**
* `industry_mentions` and `industry_job_openings` β `keyword` is required (min 3 chars)
* `industry_funding_rounds` and `industry_new_companies` β no `keyword` needed
* `countries` β optional, max 5 country codes
***
## Step 4: List and Inspect Radars
```bash theme={null}
# List all active radars
curl "https://api.tamradar.com/v1/radars?radar_status=active&limit=50" \
-H "x-api-key: YOUR_API_KEY"
# Get a specific radar
curl "https://api.tamradar.com/v1/radars/c70813b3-7e87-4ca7-90fd-8c64574d911b" \
-H "x-api-key: YOUR_API_KEY"
# Filter by type
curl "https://api.tamradar.com/v1/radars?radar_type=company_job_openings" \
-H "x-api-key: YOUR_API_KEY"
```
**Query parameters:**
| Parameter | Type | Default | Description |
| --------------- | ---------------------- | ------- | --------------------------------------------------------------- |
| `limit` | number | 50 | Number per page, max 500; higher values return `400` |
| `radar_status` | `active` \| `inactive` | β | Filter by status; `inactive` means any non-active state |
| `radar_type` | string | β | Single radar type value |
| `radar_id` | string | β | Comma-separated IDs, max 50 |
| `created_since` | ISO 8601 timestamp | β | Include radars created at or after this time; timezone required |
| `cursor` | string | β | Opaque `next_cursor` from the previous response |
**Response (200, with `limit=1`):**
```json theme={null}
{
"status": "success",
"code": 200,
"message": "Radars retrieved successfully",
"count": 1,
"limit": 1,
"has_more": true,
"next_cursor": "eyJjIjoiMjAyNC0wMy0yMCAxMjowNTowMCswMCIsImkiOiJjNzA4MTNiMy03ZTg3LTRjYTctOTBmZC04YzY0NTc0ZDkxMWIifQ",
"timestamp": "2024-03-20T12:10:00Z",
"data": [
{
"radar_id": "c70813b3-7e87-4ca7-90fd-8c64574d911b",
"radar_type": "company_job_openings",
"domain": "stripe.com",
"webhook_url": "https://your-domain.com/webhook",
"radar_status": "active",
"departments": ["Engineering"],
"seniorities": ["Senior", "Manager"],
"custom_fields": { "crm_account_id": "0011U00000TFV7MQAX" },
"created_at": "2024-03-20T12:05:00Z",
"updates_since": null,
"deactivated_at": null,
"next_charge_at": "2024-04-20T12:05:00Z"
}
]
}
```
When `has_more` is `true`, repeat the same filters and pass `next_cursor` as
the `cursor` query parameter. The final page returns `has_more: false` and
`next_cursor: null`. The `count` value applies only to the current page.
Unknown query parameters return `400`; use `cursor`, not `next_cursor`, in the
request.
For recovery or reconciliation, follow every cursor to complete the traversal
and compare it with your local radar inventory. If radars may have been created
while you were paging, start a fresh request without a cursor to pick them up.
This lists radar records; use `GET /v1/updates` for findings and failures.
Note: Filter fields (`departments`, `seniorities`, `job_titles`) and identifier fields (`profile_url`, `email`, `full_name`, `keyword`) are returned flat at the top level of each radar object. They are only present when set.
***
## Step 5: Receive Events
When a radar fires, TAMradar POSTs an event to your `webhook_url`.
### Webhook Payload Structure
All webhook events follow this envelope:
```json theme={null}
{
"update_id": "41991828-ed31-4bd9-98d2-44b2763f9f35",
"update_type": "radar_finding",
"record_id": "b77af154-c369-4446-b70a-f328880c3a48",
"discovered_at": "2024-03-20T14:30:00Z",
"data": {
"radar_id": "c70813b3-7e87-4ca7-90fd-8c64574d911b",
"radar_type": "company_job_openings",
"domain": "stripe.com",
"next_charge_at": "2024-04-20T14:30:00Z",
"custom_fields": {
"crm_account_id": "0011U00000TFV7MQAX"
}
},
"content": {
"job_title": "Senior Software Engineer",
"job_url": "https://www.linkedin.com/jobs/view/123456789",
"job_posted_at": "2024-03-20",
"job_description": "We are seeking...",
"job_source": "LinkedIn",
"country": "United States"
}
}
```
* `data` β radar context (always present, same fields regardless of type)
* `content` β the actual finding (structure varies by `radar_type`)
### Your Webhook Must
1. Accept `POST` with a JSON body
2. Return any `2xx` status code within a reasonable timeout
3. Handle duplicates β use `update_id` as an idempotency key
### Polling Alternative
If you prefer pull over push, use `GET /v1/updates`:
```bash theme={null}
# Get the latest 25 updates (radar_finding only by default)
curl "https://api.tamradar.com/v1/updates" \
-H "x-api-key: YOUR_API_KEY"
# Filter by radar and paginate with cursor
curl "https://api.tamradar.com/v1/updates?radar_id=c70813b3-7e87-4ca7-90fd-8c64574d911b&cursor=CURSOR_FROM_PREV_RESPONSE" \
-H "x-api-key: YOUR_API_KEY"
# Include failures too
curl "https://api.tamradar.com/v1/updates?update_type=radar_finding,radar_failure" \
-H "x-api-key: YOUR_API_KEY"
```
**Query parameters:** `radar_id` (comma-sep, max 10), `radar_type` (comma-sep), `domain` (comma-sep, max 10), `since` (ISO 8601), `cursor`, `limit` (1β100, default 25), `update_type` (`radar_finding` | `radar_failure`, default: `radar_finding`)
`updates_since` and the polling query parameter `since` serve different
purposes. Radar `updates_since` is the permanent eligibility boundary selected
at creation. `GET /v1/updates?since=` only filters TAMradar updates that have
already been created; it does not change the radar.
**Response shape:**
```json theme={null}
{
"status": "success",
"code": 200,
"message": "Updates retrieved successfully",
"timestamp": "2024-03-20T12:00:00Z",
"updates": [ ],
"has_more": true,
"next_cursor": "eyJyIjoiMjAyNi0..."
}
```
Pass `next_cursor` as `cursor` in the next request to paginate. `has_more: false` means you've reached the end.
***
## Step 6: Bulk Create Radars
Submit a bulk request for asynchronous processing (up to 1000 items):
```bash theme={null}
curl -X POST https://api.tamradar.com/v1/radars/bulk \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"webhook_url": "https://your-domain.com/webhook",
"radars": [
{
"domain": "stripe.com",
"radar_type": "company_job_openings",
"departments": ["Engineering"],
"custom_fields": { "crm_account_id": "acc_001" }
},
{
"domain": "notion.so",
"radar_type": "company_new_hires"
},
{
"radar_type": "contact_social_posts",
"profile_url": "https://www.linkedin.com/in/someone"
}
]
}'
```
* `webhook_url` is required at the envelope level for async bulk
* Items can mix company, contact, and industry types
* Max 1000 items, max 1MB request body
* Item-level validation and creation happen asynchronously after submission
**Submission response (`202 Accepted`):**
```json theme={null}
{
"status": "success",
"code": 202,
"timestamp": "2024-03-20T12:05:00Z",
"message": "Bulk submission accepted β radars are being created asynchronously",
"bulk_id": "5f9da22e-4778-427c-b7e4-dab2a8d9d709",
"summary": {
"total": 3
}
}
```
Save `bulk_id`, then poll for status:
```bash theme={null}
curl "https://api.tamradar.com/v1/radars/bulk/5f9da22e-4778-427c-b7e4-dab2a8d9d709" \
-H "x-api-key: YOUR_API_KEY"
```
The status endpoint returns:
* `status: "processing"` while items are still running
* `status: "completed"` when all items are terminal
* Per-item outcomes in `radars[]` with `item_index` mapping back to the input order
* `radars[].update_id` as the per-item correlation key (`radar_created` / `radar_failure`)
For webhook idempotency/correlation in async bulk:
* Use top-level `update_id` on `bulk_completed` to deduplicate the batch event itself
* Use `radars[].update_id` inside `bulk_completed` to map each item back to its individual item event
***
## Step 7: Deactivate a Radar
```bash theme={null}
curl -X DELETE "https://api.tamradar.com/v1/radars/c70813b3-7e87-4ca7-90fd-8c64574d911b" \
-H "x-api-key: YOUR_API_KEY"
```
**Response (200):**
```json theme={null}
{
"status": "success",
"code": 200,
"message": "Radar deactivated successfully",
"timestamp": "2024-03-22T14:30:00Z",
"data": {
"radar_id": "c70813b3-7e87-4ca7-90fd-8c64574d911b",
"radar_type": "company_job_openings",
"domain": "stripe.com",
"webhook_url": "https://your-domain.com/webhook",
"radar_status": "inactive",
"departments": ["Engineering"],
"seniorities": ["Senior", "Manager"],
"custom_fields": { "crm_account_id": "0011U00000TFV7MQAX" },
"created_at": "2024-03-20T12:05:00Z",
"updates_since": "2024-03-01T14:00:00.000Z",
"deactivated_at": "2024-03-22T14:30:00Z",
"next_charge_at": null
}
}
```
Deactivation is a soft delete. The radar record is retained for history.
***
## Error Responses
All errors follow the same structure:
```json theme={null}
{
"status": "error",
"code": 400,
"message": "Invalid request body",
"errors": [
{ "field": "domain", "reason": "Domain is required" },
{ "field": "radar_type", "reason": "Invalid radar type provided" }
],
"error_id": "7f8d6e5c-4b3a-2a1b-0c9d-8e7f6d5c4b3a",
"timestamp": "2024-03-20T10:15:30Z"
}
```
| Code | Meaning | Retryable |
| ---- | -------------------------- | ------------------ |
| 400 | Validation error | No |
| 401 | Missing or invalid API key | No |
| 402 | Insufficient balance | Yes (top up first) |
| 404 | Radar not found | No |
| 409 | Radar already exists | No |
| 429 | Rate limited | Yes (back off) |
| 500 | Internal server error | Yes |
| 503 | Database unavailable | Yes |
Use `error_id` when contacting support. It traces the exact request in our logs.
***
## Key Constraints
| Rule | Limit |
| ---------------------- | -------------------------------------------------------- |
| Webhook URL | Optional. Max 2048 chars, must be HTTP/HTTPS if provided |
| Domain | Standard format, max 253 chars |
| Custom fields | Max 20 keys; key β€ 50 chars; value β€ 500 chars |
| `countries` (industry) | Max 5 |
| `keyword` (industry) | Min 3 chars |
| Bulk size | Max 1000 items, 1MB body |
| List `limit` | Max 500 per page; higher values return `400` |
| List `radar_id` filter | Max 50 IDs |
# Authentication & Rate Limits
Source: https://docs.tamradar.com/guides/authentication-rate-limits
Authentication model, request headers, and rate-limit behavior.
# Authentication & Rate Limits
## API Key Authentication
All requests to TAMradar API must include your API key. This key uniquely identifies your account and authorizes access to our services.
### Using Your API Key
Include your API key in the `x-api-key` header with every request:
```bash theme={null}
# Example GET request
curl "https://api.tamradar.com/v1/radars" \
-H "x-api-key: YOUR_API_KEY"
# Example POST request
curl -X POST "https://api.tamradar.com/v1/radars" \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"domain": "example.com",
"radar_type": "company_job_openings"
}'
```
### Authentication Errors
If authentication fails, you'll receive a 401 response:
```json theme={null}
{
"status": "error",
"code": 401,
"message": "Invalid or inactive API key",
"errors": [{ "field": "x-api-key", "reason": "Invalid or inactive API key" }],
"timestamp": "2024-01-01T12:00:00Z",
"error_id": "f72ac8d3-4bca-4f4c-b14a-30af90e3eac2"
}
```
## Rate Limits
### Standard Limits
| Bucket | Default |
| -------------------------- | ----------------------------- |
| Write (create radars) | 100 per minute, 10 per second |
| Read (GET radars, updates) | 200 per minute, 10 per second |
Higher limits are available on enterprise plans. Contact [support@tamradar.com](mailto:support@tamradar.com).
### How limits are counted
There are two separate budgets, write and read, so a heavy polling client doesn't eat into your creation budget.
| Method | Endpoints | Budget used |
| -------- | ------------------------------------------------------------ | ------------------------------------------------ |
| `POST` | `/v1/radars` | Write (100/min, 10/sec) |
| `POST` | `/v1/radars/bulk` | Bulk (10/min default, per account) |
| `DELETE` | `/v1/radars/bulk` | Shared bulk bucket (10/min default, per account) |
| `GET` | `/v1/radars`, `/v1/radars/:id`, `/v1/updates`, `/v1/account` | Read (200/min, 10/sec) |
| `DELETE` | `/v1/radars/:id` | Read (200/min, 10/sec) |
**Single creates** (`POST /v1/radars`) deduct 1 token from your per-minute write budget and 1 from your per-second burst budget.
**Bulk operations** (`POST` or `DELETE /v1/radars/bulk`) share a **dedicated bulk bucket** (default 10 operations per minute per account). Each request consumes **1 bulk token** regardless of item count.
### Async bulk behavior
Bulk requests are queued asynchronously (`202 Accepted` with `bulk_id`). Item outcomes are produced later through:
* `GET /v1/radars/bulk/:bulk_id`
* Webhook events (`radar_created`, `radar_failure`, `bulk_completed`)
Because item processing is async, you do not receive synchronous per-item `201/207/402/409` bulk responses.
### Synchronous bulk deactivation
`DELETE /v1/radars/bulk` returns final per-item outcomes in the same response.
It shares the bulk rate-limit bucket with bulk create, but it does not create a
pollable job or consume an additional generic DELETE token.
### Response headers
Successful requests include rate-limit headers when applicable. For bulk submissions, `Retry-After`, `X-RateLimit-Limit`, and `X-RateLimit-Remaining` are guaranteed on `429`.
| Header | Meaning |
| ----------------------- | ---------------------------------------- |
| `X-RateLimit-Limit` | Your per-minute budget for this endpoint |
| `X-RateLimit-Remaining` | Tokens remaining after this request |
| `X-RateLimit-Reset` | Seconds until the bucket fully refills |
### Handling Rate Limits
When you exceed the rate limit you'll receive a `429` with three headers:
| Header | Meaning |
| ----------------------- | --------------------------------------------- |
| `Retry-After` | Seconds until you have enough budget to retry |
| `X-RateLimit-Limit` | Your per-minute budget |
| `X-RateLimit-Remaining` | Tokens available right now |
The `reason` field tells you which bucket fired:
* `minute` β per-minute budget exhausted, wait `Retry-After` seconds
* `burst` β per-second burst limit hit, retry in 1 second
```json theme={null}
{
"status": "error",
"code": 429,
"message": "Per-minute rate limit exceeded. Retry after 51 seconds.",
"errors": [{ "field": "rate_limit", "reason": "minute" }],
"timestamp": "2024-01-01T12:00:00Z"
}
```
### Bulk sizing limits
Async bulk currently allows:
* up to 1000 items per request
* request body up to 1MB
If either limit is exceeded, the request is rejected with `400`.
## Enterprise Options
For higher volume needs, we offer customized plans with:
* Increased rate limits
* Priority support
Contact [support@tamradar.com](mailto:support@tamradar.com) to discuss your requirements.
### Need Help?
* Check our [Error Handling](/guides/error-handling) guide
* Contact [support@tamradar.com](mailto:support@tamradar.com)
# Monthly Subscription Billing
Source: https://docs.tamradar.com/guides/balance-monthly-subscription
Monthly subscription billing flow, grace period behavior, and failure handling.
Monthly subscription radars are billed at creation, then every 30 days while active.
## Billing Mechanics
* Charge happens at radar creation.
* The next renewal timestamp is returned as `next_charge_at`.
* Renewal runs every 30 days while the radar remains active.
## Deactivation Behavior
* User-triggered deactivation keeps the radar running until the current billing cycle ends.
* After the cycle ends, the radar transitions to `inactive`.
* At cycle end, the radar transitions to `inactive`.
## Failure and Refund Behavior
* If setup fails during initial creation, a `radar_failure` webhook is sent.
* For monthly subscription, setup failures can include a refund (`refund_amount_usd > 0`).
* If renewal fails due to insufficient funds, the radar is deactivated and no refund is applied for prior usage.
## What to Monitor
* `next_charge_at` for upcoming renewal windows.
* `radar_failure` with `code: 402` for renewal failures.
* `radar_status` in API responses (`active` or `inactive`) for operational state.
# Balance and Pricing
Source: https://docs.tamradar.com/guides/balance-system-explained
Overview of pricing models, balance lifecycle, and billing states.
This page gives a high-level view of TAMradar billing and lifecycle behavior.
For model-specific implementation details:
* [Monthly Subscription Billing](/guides/balance-monthly-subscription)
* [Update-Based Billing](/guides/balance-update-based)
## Overview
TAMradar uses a **USD credit balance** system to bill for radar usage. Two pricing models exist, and which one applies to a radar depends on your account configuration per radar type.
***
## Pricing Models
### Monthly Subscription
* **Charge at creation** β balance is deducted when the radar is first created.
* **Recurring charge** β deducted automatically every 30 days while the radar is active. `next_charge_at` in the API response shows the next billing date.
* **Refund on setup failure** β if the radar fails during initial setup (missing prerequisites, source inaccessible), the creation charge is automatically reversed and you receive a `radar_failure` webhook with `refund_amount_usd > 0`.
* **User cancellation** β radar transitions to `inactive`. For monthly subscription, service can continue until the current billing period ends.
### Update-Based
* **No charge at creation** β `next_charge_at` is `null`. Nothing is deducted when the radar is set up.
* **Charged per update found** β balance is deducted each time TAMradar finds and approves a new update for your radar (a new hire, job change, funding round, etc.).
* **No refund on setup failure** β if the radar fails during setup, you receive a `radar_failure` webhook with `refund_amount_usd: 0` (nothing was charged, so nothing to refund).
* **Auto-reactivation on top-up** β if a radar was deactivated due to insufficient funds, it automatically reactivates when a new payment is added to your account. No manual action needed.
***
## Radar States
`radar_status` in public API responses is intentionally simplified to two values:
| Public `radar_status` | Meaning |
| --------------------- | ---------------------------------------------------------------------------------------------- |
| `active` | Running normally. Balance is deducted per the pricing model. |
| `inactive` | Any non-active lifecycle state (cancelled, failed, insufficient funds, or grace-period state). |
Internal lifecycle enums are implementation details and are not part of the public API contract.
For external handling, use:
* `radar_status` (`active` / `inactive`)
* `radar_failure` payload fields: `code`, `message`, and `refund_amount_usd`
***
## Monitoring Balance Events
### Webhook Notifications
Your application receives `radar_failure` webhook events whenever a radar is deactivated due to a billing or setup issue.
**Monthly subscription β insufficient funds during renewal**
Radar is deactivated when the 30-day charge cannot be covered. No refund (the original creation charge was for time already used).
```json theme={null}
{
"update_id": "550e8400-e29b-41d4-a716-446655440000",
"update_type": "radar_failure",
"record_id": null,
"discovered_at": "2025-03-20T14:10:38Z",
"completed_at": "2025-03-20T14:10:38Z",
"data": {
"radar_id": "54e32291-25a4-459f-8bd6-4f79489ea931",
"radar_type": "company_new_hires",
"domain": "example.com",
"next_charge_at": null,
"custom_fields": {}
},
"status": "error",
"code": 402,
"message": "Your radar was deactivated due to insufficient funds. Please add more credits to reactivate.",
"errors": [{ "field": "radar", "reason": "insufficient_funds" }],
"refund_amount_usd": 0,
"timestamp": "2025-03-20T14:10:38Z",
"error_id": "550e8400-e29b-41d4-a716-446655440000"
}
```
**Monthly subscription β setup failure (with refund)**
Radar failed during initial setup. Creation charge is reversed automatically.
```json theme={null}
{
"update_id": "41991828-ed31-4bd9-98d2-44b2763f9f35",
"update_type": "radar_failure",
"record_id": null,
"discovered_at": "2025-03-20T08:30:00Z",
"completed_at": "2025-03-20T08:30:00Z",
"data": {
"radar_id": "c70813b3-7e87-4ca7-90fd-8c64574d911b",
"radar_type": "industry_job_openings",
"domain": null,
"next_charge_at": null,
"custom_fields": {}
},
"status": "error",
"code": 400,
"message": "Radar creation failed: missing prerequisites.",
"errors": [{ "field": "radar", "reason": "missing_prerequisites" }],
"refund_amount_usd": 0.10,
"timestamp": "2025-03-20T08:30:00Z",
"error_id": "41991828-ed31-4bd9-98d2-44b2763f9f35"
}
```
**Update-based β setup failure (no refund)**
Same webhook shape, but `refund_amount_usd` is always `0` because nothing was charged at creation.
```json theme={null}
{
"update_id": "72bc9a00-1234-4abc-b000-aabbccddeeff",
"update_type": "radar_failure",
"record_id": null,
"discovered_at": "2025-03-21T10:00:00Z",
"completed_at": "2025-03-21T10:00:00Z",
"data": {
"radar_id": "a1b2c3d4-0000-0000-0000-000000000001",
"radar_type": "contact_job_changes",
"domain": "example.com",
"next_charge_at": null,
"custom_fields": {}
},
"status": "error",
"code": 400,
"message": "Radar creation failed: missing prerequisites.",
"errors": [{ "field": "radar", "reason": "missing_prerequisites" }],
"refund_amount_usd": 0,
"timestamp": "2025-03-21T10:00:00Z",
"error_id": "72bc9a00-1234-4abc-b000-aabbccddeeff"
}
```
### Failure Reasons
Failure type is identified by `code` and `message` in the `radar_failure` payload.
| Failure type | `code` | Description | Refund |
| --------------------- | ------ | ------------------------------------------------------------------------ | ----------------------------------- |
| Missing prerequisites | 400 | Required public data not found (e.g. no LinkedIn profile, no public RSS) | Monthly sub: yes. Update-based: no. |
| Source inaccessible | 400 | Data source exists but cannot be accessed | Monthly sub: yes. Update-based: no. |
| Setup failure | 400 | Generic failure during radar initialisation | Monthly sub: yes. Update-based: no. |
| Insufficient funds | 402 | Radar deactivated β balance too low for renewal charge | No refund |
***
## Bulk Endpoint β Async Outcomes
`POST /v1/radars/bulk` is asynchronous. The submission endpoint returns `202` with `bulk_id`, and item outcomes are emitted later.
For balance-related failures in bulk:
* the affected item ends in a failure state during async processing
* the failure appears in:
* `GET /v1/radars/bulk/:bulk_id` item results, and
* webhook delivery (`radar_failure` for item-level failures, plus final `bulk_completed`)
Treat bulk as a queued workflow rather than a synchronous per-item response.
***
## Best Practices
1. **Monitor balance** β poll `GET /v1/account` for `balance_remaining_usd` and alert before it hits zero.
2. **Handle `radar_failure` webhooks** β persist `code`, `message`, and `refund_amount_usd` for reconciliation.
3. **Update-based: top up proactively** β radars auto-reactivate on top-up, but there's a gap while they're inactive. Schedule automatic top-ups to avoid missed updates.
4. **Monthly subscription: budget for renewals** β check `next_charge_at` on your active radars and ensure balance covers the next 30 days.
5. **Bulk retries are workflow-based** β top up balance, then resubmit the failed items from your async bulk status/webhook outcomes.
# Update-Based Billing
Source: https://docs.tamradar.com/guides/balance-update-based
Update-based billing flow, insufficient-funds behavior, and auto-reactivation.
Update-based radars are charged only when TAMradar produces approved updates.
## Billing Mechanics
* No charge happens at creation.
* `next_charge_at` is `null`.
* Balance is deducted per approved finding.
## Deactivation and Reactivation
* If balance reaches zero, the radar transitions to `inactive`.
* When balance is topped up, affected update-based radars can auto-reactivate.
* User-triggered deactivation sets the radar to `inactive` immediately (no grace period).
## Failure and Refund Behavior
* Setup failures still emit `radar_failure` webhooks.
* For update-based setup failures, `refund_amount_usd` is typically `0` because no creation charge was taken.
* Failure `code` is `400` for setup failures (`missing_prerequisites`, `source_inaccessible`, `setup_failure`) and `402` for insufficient funds. See `message` for details.
## What to Monitor
* `balance_remaining_usd` from `GET /v1/account`.
* `radar_failure` events for funding and setup issues.
* Bulk workflows (`POST /v1/radars/bulk` + `GET /v1/radars/bulk/{bulk_id}`) for async item-level failures.
# Bulk Create Guide
Source: https://docs.tamradar.com/guides/bulk-create-guide
End-to-end setup for asynchronous bulk radar creation and result handling.
Use this guide when you want to create many radars at once using `POST /v1/radars/bulk`.
To deactivate multiple existing radars, use the separate synchronous
[`DELETE /v1/radars/bulk`](/api-reference/delete-bulk-radars) endpoint. It
returns final per-item results immediately and does not use bulk status polling.
Bulk creation is asynchronous:
* The submission request returns `202 Accepted` with a `bulk_id`.
* Item-level outcomes are produced later as `radar_created` or `radar_failure`.
* A final `bulk_completed` event indicates that all items are done.
Two delivery modes are supported:
* **With `webhook_url`** β per-item and final results are pushed to your endpoint, with `GET /v1/radars/bulk/{bulk_id}` available as a reliability fallback.
* **Without `webhook_url` (poll-only)** β omit the field entirely; track the batch with `GET /v1/radars/bulk/{bulk_id}`. See [Polling without a webhook](#polling-without-a-webhook).
For request and response schemas, use:
* [Bulk Create Radars](/api-reference/post-radars-bulk)
* [Get Bulk Create Status](/api-reference/get-radars-bulk-status)
## Bulk flow and responsibilities
| Component | Responsibility |
| -------------------------------- | ---------------------------------------------------------------------------------------------------------------- |
| Your application | Build the bulk payload, submit it, store `bulk_id`, reconcile outcomes |
| `POST /v1/radars/bulk` | Validates envelope-level shape and queues items |
| Bulk processor | Validates and processes each queued item asynchronously |
| Your webhook endpoint (optional) | Receives per-item `radar_created` / `radar_failure`, then `bulk_completed` β only when `webhook_url` is provided |
| `GET /v1/radars/bulk/{bulk_id}` | Polls live status and per-item results |
## Step 1: Prepare your webhook consumer (skip for poll-only)
`webhook_url` is optional. If you omit it, skip this step and use
[Polling without a webhook](#polling-without-a-webhook) instead. If you provide
one, point it to an HTTPS endpoint that:
* accepts `POST`
* returns `2xx` quickly
* processes payloads asynchronously in your own worker queue
You should handle these update types:
* `radar_created`
* `radar_failure`
* `bulk_completed`
See [Webhook Payloads](/webhooks/overview) for payload details and examples.
## Step 2: Build a valid bulk request
The top-level body:
* `radars` (required): array of radar payloads
* `webhook_url` (optional): single callback URL for all items in this bulk submission
To disable webhooks, **omit `webhook_url` entirely**. An explicit
`"webhook_url": null` is rejected with `400` β the field accepts a valid URL or
no key at all, never `null`.
Each item in `radars[]` uses the same field structure as the corresponding single-radar endpoint. Before building a bulk payload, make sure you're familiar with the per-type schemas:
* [Create Company Radar](/api-reference/post-radars-companies) β `domain`, `radar_type`, optional filters
* [Create Contact Radar](/api-reference/post-radars-contacts) β `profile_url`, `radar_type`, `domain`
* [Create Industry Radar](/api-reference/post-radars-industry) β `radar_type`, optional `keyword`
Each item may also provide its own `updates_since`. Use it when you need
historical findings or want to enforce a specific earliest event date. The value
must be an RFC 3339 timestamp with a timezone and must represent now or a past
instant. Omit it to use that radar's creation time. A five-second server
tolerance exists only for clock skew and cannot be used to schedule a future
start.
We recommend adding `custom_fields` to every item. If you skip it, you can still reconcile with `item_index`, but item-level tracing in downstream systems is harder.
```json theme={null}
{
"webhook_url": "https://your-domain.com/webhooks/tamradar",
"radars": [
{
"radar_type": "company_job_openings",
"domain": "stripe.com",
"updates_since": "2026-05-01T00:00:00Z",
"custom_fields": { "crm_account_id": "acc_001" }
},
{
"radar_type": "contact_job_changes",
"domain": "openai.com",
"profile_url": "https://www.linkedin.com/in/example",
"custom_fields": { "owner": "sdr_team" }
}
]
}
```
## Step 3: Submit and persist `bulk_id`
On success, the API returns `202` with a `bulk_id` and summary. Persist `bulk_id` immediately. It is your primary key for reconciliation and polling.
```bash theme={null}
curl -X POST https://api.tamradar.com/v1/radars/bulk \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"webhook_url": "https://your-domain.com/webhooks/tamradar",
"radars": [
{
"radar_type": "company_job_openings",
"domain": "stripe.com",
"updates_since": "2026-05-01T00:00:00Z",
"custom_fields": { "crm_account_id": "acc_001" }
},
{
"radar_type": "industry_funding_rounds",
"custom_fields": { "segment": "fintech" }
}
]
}'
```
```json theme={null}
{
"status": "success",
"code": 202,
"message": "Bulk submission accepted - 2 radars queued for processing. Poll GET /v1/radars/bulk/5f9da22e-4778-427c-b7e4-dab2a8d9d709 for status or wait for the bulk_completed webhook.",
"bulk_id": "5f9da22e-4778-427c-b7e4-dab2a8d9d709",
"summary": {
"total": 2
},
"timestamp": "2026-05-25T11:17:12Z"
}
```
Typical immediate failure cases:
* `400`: invalid JSON or invalid envelope (`webhook_url`, `radars[]`)
* `401`: missing or invalid API key
* `429`: bulk rate limit exceeded
* `500`: queueing failed
For error format details, see [Error Handling](/guides/error-handling).
`400` example:
```json theme={null}
{
"status": "error",
"code": 400,
"message": "Invalid request body",
"errors": [
{
"field": "webhook_url",
"reason": "Invalid webhook URL format"
}
],
"timestamp": "2026-05-25T11:18:10Z",
"error_id": "245df4c2-3ab8-4c96-a60d-42f6fc59f9b8"
}
```
`429` example:
```json theme={null}
{
"status": "error",
"code": 429,
"message": "Rate limit exceeded β wait and retry.",
"errors": [
{
"field": "rate_limit",
"reason": "minute"
}
],
"timestamp": "2026-05-25T11:18:44Z",
"error_id": "29a4ff58-f4a8-4ebe-8b2d-1544ee43a99c"
}
```
For async bulk, `reason` is always `minute` because this endpoint enforces a dedicated per-minute bulk bucket.
## Step 4: Process per-item events
Items do not complete at the same time. You can receive outcomes in any order.
`radar_created` means the item created successfully.
`radar_failure` means that item failed (validation, conflict, billing, or processing error). The `code`, `message`, and `errors[]` fields are identical to what the sync single-create endpoint would return for the same failure β so any error handling you already have for the sync API works here without changes.
Use `item_index`, `bulk_id`, and `custom_fields` to map each event back to the original request item.
For successful items, `data` matches the same `data` shape returned by single-create endpoints:
[Create Company Radar](/api-reference/post-radars-companies), [Create Contact Radar](/api-reference/post-radars-contacts), and [Create Industry Radar](/api-reference/post-radars-industry).
`radar_created` example:
```json theme={null}
{
"update_id": "efbf7c77-c1ae-4a0d-85b6-116674e9e0b1",
"update_type": "radar_created",
"record_id": null,
"code": 201,
"message": "Radar created successfully",
"completed_at": "2026-05-25T11:17:20Z",
"bulk_id": "5f9da22e-4778-427c-b7e4-dab2a8d9d709",
"item_index": 0,
"custom_fields": { "crm_account_id": "acc_001" },
"status": "success",
"data": {
"radar_id": "2ffb1a07-ac95-4a6d-bc34-39f479f1c856",
"radar_type": "company_job_openings",
"domain": "stripe.com",
"radar_status": "active",
"created_at": "2026-05-25T11:17:18Z",
"updates_since": "2026-05-01T00:00:00.000Z"
}
}
```
When an item omits `updates_since`, successful `radar_created` and bulk-status
results return `"updates_since": null`. The effective cutoff is that radar's
`created_at`.
`radar_failure` example:
```json theme={null}
{
"update_id": "a1b2c3d4-0001-4000-b000-000000000001",
"update_type": "radar_failure",
"record_id": null,
"completed_at": "2026-05-25T11:17:26Z",
"discovered_at": "2026-05-25T11:17:26Z",
"bulk_id": "5f9da22e-4778-427c-b7e4-dab2a8d9d709",
"item_index": 1,
"custom_fields": { "segment": "fintech" },
"status": "error",
"code": 409,
"message": "Conflict: Radar already exists",
"errors": [
{
"field": "domain",
"reason": "A radar with this domain and type already exists for your account. Conflicting radar_id: 3f1d8e8a-39c4-47e8-a4b4-f7db6a2e50f0. You can view it using GET /radars/{radar_id}",
"conflicting_radar_id": "3f1d8e8a-39c4-47e8-a4b4-f7db6a2e50f0"
}
],
"refund_amount_usd": 0,
"timestamp": "2026-05-25T11:17:26Z",
"error_id": "45ed35e5-986c-4496-afd8-9a84f6baa410"
}
```
`discovered_at` is included for backwards compatibility and equals `completed_at`. Prefer `completed_at` β `discovered_at` will be removed in a future version.
### Possible failure codes
The `code`, `message`, and `errors[]` below are spread directly into the `radar_failure` envelope above β identical to what the sync single-create endpoint returns for the same failure. Your existing sync error handling works for bulk without changes.
Returned when a field is missing, invalid, or the radar type is not supported. `errors[]` may contain multiple entries, one per failing field.
```json theme={null}
{
"status": "error",
"code": 400,
"message": "Validation error",
"errors": [
{
"field": "domain",
"reason": "Invalid domain format"
}
],
"timestamp": "2026-05-25T11:17:26Z",
"error_id": "245df4c2-3ab8-4c96-a60d-42f6fc59f9b8"
}
```
Returned when your account does not have enough credits to create the radar. The item is not created and no credits are deducted.
```json theme={null}
{
"status": "error",
"code": 402,
"message": "Insufficient balance",
"errors": [
{
"field": "balance",
"reason": "You do not have enough balance to create this radar."
}
],
"timestamp": "2026-05-25T11:17:26Z",
"error_id": "29a4ff58-f4a8-4ebe-8b2d-1544ee43a99c"
}
```
Returned when a radar with the same configuration already exists on your account. Always includes `conflicting_radar_id` so you can look it up directly.
The `field` name varies by radar type:
* **Company** radars β `domain`
* **Contact** radars β `identifiers.email` when email matched, or `identifiers.profile_url` when the profile URL matched
* **Industry** radars β `keyword` (for `industry_mentions` / `industry_job_openings`) or `radar_type` (for other industry types)
```json theme={null}
{
"status": "error",
"code": 409,
"message": "Conflict: Radar already exists",
"errors": [
{
"field": "domain",
"reason": "A radar with this domain and type already exists for your account. Conflicting radar_id: 3f1d8e8a-39c4-47e8-a4b4-f7db6a2e50f0. You can view it using GET /radars/{radar_id}",
"conflicting_radar_id": "3f1d8e8a-39c4-47e8-a4b4-f7db6a2e50f0"
}
],
"timestamp": "2026-05-25T11:17:26Z",
"error_id": "45ed35e5-986c-4496-afd8-9a84f6baa410"
}
```
Rare. Indicates a transient infrastructure failure. The item will be retried automatically β no action needed on your side. If a `radar_failure` with `code: 500` is delivered, it means all retries were exhausted.
```json theme={null}
{
"status": "error",
"code": 500,
"message": "Internal server error",
"errors": [
{
"field": "request",
"reason": "Create failed"
}
],
"timestamp": "2026-05-25T11:17:26Z",
"error_id": "f9ab0e2d-8246-4465-868f-922add7fbeab"
}
```
## Step 5: Finalize on `bulk_completed`
When all items reach terminal state, you receive one `bulk_completed` event. Use it to:
* mark the bulk job complete in your system
* compare final counts (`created`, `failed`) with your local ledger
* trigger any retry workflow for failed items
`bulk_completed` carries the full per-item result array. At 1,000 items with rich `custom_fields` or filter data, the payload can approach 1β2 MB β ensure your receiver can handle it. Alternatively, use `GET /v1/radars/bulk/{bulk_id}` to fetch results on demand instead of relying on this webhook.
`bulk_completed.radars[]` contains per-item results for the submission.
Each `radars[]` entry is identical to the per-item webhook payload already delivered for that item (`radar_created` or `radar_failure`).
```json theme={null}
{
"update_id": "9a2f0f77-5c8b-4b3e-b636-3ad539f4de65",
"update_type": "bulk_completed",
"bulk_id": "5f9da22e-4778-427c-b7e4-dab2a8d9d709",
"completed_at": "2026-05-25T11:17:33Z",
"summary": {
"total": 2,
"created": 1,
"failed": 1
},
"radars": [
{
"update_type": "radar_created",
"item_index": 0,
"...": "same fields as radar_created (including custom_fields and data)"
},
{
"update_type": "radar_failure",
"item_index": 1,
"...": "same fields as radar_failure (including code, errors, error_id, timestamp)"
}
]
}
```
## Step 6: Add polling as a reliability path
If webhook delivery is delayed or your consumer is unavailable, poll:
`GET /v1/radars/bulk/{bulk_id}`
The response `status` is:
* `processing`: at least one item is still in flight
* `completed`: all items are terminal
Poll until `completed`, then stop.
Both polling responses include `radars[]`:
* In `processing`, in-flight items appear as `{ item_index, status: "processing", custom_fields }`.
* In `completed`, each item is the full terminal payload (same shape as per-item webhook delivery).
```bash theme={null}
curl https://api.tamradar.com/v1/radars/bulk/5f9da22e-4778-427c-b7e4-dab2a8d9d709 \
-H "x-api-key: YOUR_API_KEY"
```
`processing` example:
```json theme={null}
{
"status": "processing",
"code": 200,
"timestamp": "2026-05-25T12:19:17Z",
"message": "Bulk submission is being processed",
"bulk_id": "5d089cd9-239d-4de4-af1d-8d19da889d00",
"summary": {
"total": 3,
"processing": 3,
"created": 0,
"failed": 0
},
"radars": [
{
"item_index": 0,
"status": "processing",
"custom_fields": {}
},
{
"item_index": 1,
"status": "processing",
"custom_fields": {}
},
{
"item_index": 2,
"status": "processing",
"custom_fields": {}
}
]
}
```
`completed` example:
```json theme={null}
{
"status": "completed",
"code": 200,
"timestamp": "2026-05-25T12:19:46Z",
"message": "Bulk submission completed",
"bulk_id": "5d089cd9-239d-4de4-af1d-8d19da889d00",
"summary": {
"total": 3,
"processing": 0,
"created": 0,
"failed": 3
},
"radars": [
{
"item_index": 0,
"custom_fields": {},
"code": 409,
"errors": [
{
"field": "domain",
"reason": "A radar with this domain and type already exists for your account. Conflicting radar_id: 351d5644-3d75-416e-b8a5-eb0e1b16b602. You can view it using GET /radars/{radar_id}",
"conflicting_radar_id": "351d5644-3d75-416e-b8a5-eb0e1b16b602"
}
],
"status": "error",
"message": "Conflict: Radar already exists",
"error_id": "f9ab0e2d-8246-4465-868f-922add7fbeab",
"timestamp": "2026-05-25T12:19:45.783Z"
},
{
"...": "additional items with the same terminal shape"
}
]
}
```
## Polling without a webhook
Omit `webhook_url` to run the whole batch in poll-only mode β no HTTP callback
is attempted, and `GET /v1/radars/bulk/{bulk_id}` is your single tracking
surface for every outcome, including failures.
```bash theme={null}
curl -X POST https://api.tamradar.com/v1/radars/bulk \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"radars": [
{
"radar_type": "company_job_openings",
"domain": "stripe.com",
"custom_fields": { "crm_account_id": "acc_001" }
}
]
}'
```
The `202` response is the same as webhook mode, except the message directs you
to polling only:
```json theme={null}
{
"status": "success",
"code": 202,
"message": "Bulk submission accepted β 1 radars queued for processing. Poll GET /v1/radars/bulk/5f9da22e-4778-427c-b7e4-dab2a8d9d709 for status.",
"bulk_id": "5f9da22e-4778-427c-b7e4-dab2a8d9d709",
"summary": { "total": 1 },
"timestamp": "2026-08-02T11:17:12Z"
}
```
Poll-only workflow:
1. Submit without `webhook_url` and store `bulk_id`.
2. Poll `GET /v1/radars/bulk/{bulk_id}` until `status` is `completed`
(see Step 6 for both response shapes).
3. Read every terminal item from `radars[]` β successes carry the created
radar's `data`, failures carry the same `code`/`message`/`errors[]` shown in
Step 4.
4. Retry only failed items with a new bulk request.
Remember: omit the key entirely β `"webhook_url": null` returns `400`.
## Recommended production pattern
1. Submit bulk and store `bulk_id`.
2. Process webhook events and persist item outcomes by `bulk_id + item_index`
(webhook mode), or rely on the status endpoint alone (poll-only mode).
3. Poll status endpoint on a fallback interval until completed.
4. Reconcile webhook-driven and poll-driven outcomes.
5. Retry only failed items with a new bulk request.
This gives you real-time processing through webhooks and deterministic recovery through polling β or, without a webhook, a single deterministic polling surface.
# Error Handling
Source: https://docs.tamradar.com/guides/error-handling
Standard error envelope, common failure scenarios, and recovery patterns.
## Overview
The TAMradar API uses standard HTTP status codes and a consistent error response format.
### Error Response Envelope (400, 401, 403, 404, 409, 429, 500, 503)
* `status`: Always "error" for error responses
* `code`: HTTP status code
* `message`: Human-readable error description
* `errors`: Array of field-level errors (`field` + `reason`). May be empty (`[]`) for some server-side failures.
* `timestamp`: When the error occurred
* `error_id`: Unique identifier for error tracking
## Error Response Formats
### Error Format Example (400)
```json theme={null}
{
"status": "error",
"code": 400,
"message": "Invalid parameters",
"errors": [
{
"field": "id",
"reason": "Must be a valid UUID"
}
],
"timestamp": "2026-05-24T19:04:43Z",
"error_id": "a559c1de-216b-46e6-977c-f9ceda7d79f7"
}
```
### Server Error Format (500)
```json theme={null}
{
"status": "error",
"code": 500,
"message": "An internal server error occurred.",
"errors": [],
"timestamp": "2024-03-20T10:15:30Z",
"error_id": "f72ac8d3-4bca-4f4c-b14a-30af90e3eac2"
}
```
## Error Types
| Status | Description | Common Causes |
| ------ | --------------------- | -------------------------------------------------------------- |
| 400 | Bad Request | Missing required fields, invalid format |
| 402 | Payment Required | Insufficient balance |
| 401 | Unauthorized | Missing or invalid API key |
| 403 | Forbidden | Valid API key but insufficient permissions |
| 404 | Not Found | Invalid radar ID or resource not found |
| 409 | Conflict | Duplicate radar (includes existing radar ID for direct access) |
| 429 | Rate Limit Exceeded | Per-minute budget exhausted or per-second burst limit exceeded |
| 500 | Internal Server Error | Application bugs, unexpected code issues |
| 503 | Service Unavailable | Database outages, external service downtime |
## Common Error Examples
### Validation Error (400)
```json theme={null}
{
"status": "error",
"code": 400,
"message": "Invalid request body",
"errors": [
{
"field": "domain",
"reason": "Invalid domain format. Please provide a valid domain (e.g., example.com)"
}
],
"timestamp": "2024-03-20T10:15:30Z",
"error_id": "f72ac8d3-4bca-4f4c-b14a-30af90e3eac2"
}
```
### `updates_since` Validation Errors (400)
`updates_since` must be an RFC 3339 timestamp with an explicit timezone. It must
represent now or a past instant and cannot be more than five calendar years old.
The API allows up to five seconds of clock skew, but this is not future
scheduling.
Timezone missing:
```json theme={null}
{
"status": "error",
"code": 400,
"message": "Invalid updates_since",
"errors": [
{
"field": "updates_since",
"reason": "must be an RFC 3339 timestamp with explicit timezone (e.g. 2026-01-01T00:00:00Z or +05:30 offset)"
}
],
"timestamp": "2026-08-12T10:00:00Z",
"error_id": "f72ac8d3-4bca-4f4c-b14a-30af90e3eac2"
}
```
Future timestamp beyond clock-skew tolerance:
```json theme={null}
{
"status": "error",
"code": 400,
"message": "Invalid updates_since",
"errors": [
{
"field": "updates_since",
"reason": "must not be more than 5 seconds in the future"
}
],
"timestamp": "2026-08-12T10:00:00Z",
"error_id": "f72ac8d3-4bca-4f4c-b14a-30af90e3eac2"
}
```
Older than the five-calendar-year limit:
```json theme={null}
{
"status": "error",
"code": 400,
"message": "Invalid updates_since",
"errors": [
{
"field": "updates_since",
"reason": "is too far in the past. Earliest accepted: 2021-08-12T10:00:00.000Z"
}
],
"timestamp": "2026-08-12T10:00:00Z",
"error_id": "f72ac8d3-4bca-4f4c-b14a-30af90e3eac2"
}
```
### Insufficient Balance Error (402)
```json theme={null}
{
"status": "error",
"code": 402,
"message": "Insufficient balance",
"errors": [
{
"field": "balance",
"reason": "You do not have enough balance to create this radar."
}
],
"timestamp": "2024-03-20T10:15:30Z",
"error_id": "f72ac8d3-4bca-4f4c-b14a-30af90e3eac2"
}
```
### Authentication Error (401)
```json theme={null}
{
"status": "error",
"code": 401,
"message": "Missing API key",
"errors": [
{
"field": "x-api-key",
"reason": "API key is required"
}
],
"timestamp": "2024-03-20T10:15:30Z",
"error_id": "f72ac8d3-4bca-4f4c-b14a-30af90e3eac2"
}
```
### Permission Error (403)
```json theme={null}
{
"status": "error",
"code": 403,
"message": "Insufficient permissions",
"errors": [
{
"field": "permission",
"reason": "Your API key does not have permission to create radars"
}
],
"timestamp": "2024-03-20T10:15:30Z",
"error_id": "f72ac8d3-4bca-4f4c-b14a-30af90e3eac2"
}
```
### Duplicate Resource Error (409)
When creating a radar that already exists, the API returns the existing radar's ID so you can fetch it directly using `GET /v1/radars/{radar_id}`.
```json theme={null}
{
"status": "error",
"code": 409,
"message": "Conflict: Radar already exists",
"errors": [
{
"field": "domain",
"reason": "A radar with this domain and type already exists for your account. Conflicting radar_id: 7e3dda58-a046-4746-8247-258b6fd9c3ac. You can view it using GET /v1/radars/{radar_id}"
}
],
"timestamp": "2024-03-20T10:15:30Z",
"error_id": "f72ac8d3-4bca-4f4c-b14a-30af90e3eac2"
}
```
#### Different Radar Types Have Specific Error Messages:
**Company Radars:**
```json theme={null}
{
"field": "domain",
"reason": "A radar with this domain and type already exists for your account. Conflicting radar_id: 7e3dda58-a046-4746-8247-258b6fd9c3ac. You can view it using GET /v1/radars/{radar_id}"
}
```
**Contact Radars:**
The field identifies which hard identifier matched the existing radar.
Email match:
```json theme={null}
{
"field": "identifiers.email",
"reason": "A radar for this person at this company already exists. Each person can only be tracked once per domain. Conflicting radar_id: 2f5fb18f-f9cf-4411-bbef-10f5ef73324f. You can view it using GET /radars/{radar_id}",
"conflicting_radar_id": "2f5fb18f-f9cf-4411-bbef-10f5ef73324f"
}
```
Profile URL match:
```json theme={null}
{
"field": "identifiers.profile_url",
"reason": "A radar for this person at this company already exists. Each person can only be tracked once per domain. Conflicting radar_id: 2f5fb18f-f9cf-4411-bbef-10f5ef73324f. You can view it using GET /radars/{radar_id}",
"conflicting_radar_id": "2f5fb18f-f9cf-4411-bbef-10f5ef73324f"
}
```
**Industry Radars:**
```json theme={null}
{
"field": "keyword",
"reason": "An industry mentions radar for this configuration (keyword \"artificial intelligence\") already exists. Conflicting radar_id: 7e3dda58-a046-4746-8247-258b6fd9c3ac. You can view it using GET /v1/radars/{radar_id}"
}
```
#### Using the Returned Radar ID:
Read `errors[0].conflicting_radar_id` and fetch the existing radar:
```bash theme={null}
# Read conflicting_radar_id from the 409 response, then fetch that radar
curl -X GET "https://api.tamradar.com/v1/radars/7e3dda58-a046-4746-8247-258b6fd9c3ac" \
-H "x-api-key: your-api-key"
```
**Platform accounts: duplicate creation can be enabled.** For platform use cases
(e.g. managing radars on behalf of your own customers), duplicate radar creation
can be enabled account-wide on request β contact support. When enabled:
* Creating the same radar definition again returns `201` with a **new, independent
`radar_id`** instead of `409` β every successful create is a new radar.
* TAMradar does not deduplicate create requests for your account. **You own retry
idempotency**: store each returned `radar_id`, and don't blind-retry timeouts β
a request that timed out may still have succeeded and created a radar.
* Each radar bills independently, including duplicates.
* This applies to single and bulk creation alike.
## Rate Limiting
Rate limits are enforced per API key. When rate limited, you'll receive:
```json theme={null}
{
"status": "error",
"code": 429,
"message": "Per-minute rate limit exceeded. Retry after 51 seconds.",
"errors": [
{
"field": "rate_limit",
"reason": "minute"
}
],
"timestamp": "2024-03-20T10:15:30Z",
"error_id": "f72ac8d3-4bca-4f4c-b14a-30af90e3eac2"
}
```
For `POST /v1/radars/bulk`, the 429 message is `Rate limit exceeded β wait and retry.` with `reason: "minute"`.
### Server Error (500)
```json theme={null}
{
"status": "error",
"code": 500,
"message": "An internal server error occurred.",
"errors": [],
"timestamp": "2024-03-20T10:15:30Z",
"error_id": "f72ac8d3-4bca-4f4c-b14a-30af90e3eac2"
}
```
### Service Unavailable (503)
```json theme={null}
{
"status": "error",
"code": 503,
"message": "Service temporarily unavailable",
"errors": [],
"timestamp": "2024-03-20T10:15:30Z",
"error_id": "f72ac8d3-4bca-4f4c-b14a-30af90e3eac2"
}
```
## Best Practices
1. **Handle Consistent Structure**: Parse the `errors` array for all non-2xx responses.
2. **Log Error IDs**: Store `error_id` for support requests and debugging.
3. **Handle Rate Limits**: Implement exponential backoff when rate limited
4. **Validate Input**: Pre-validate requests to minimize validation errors
5. **Check Balance**: Monitor account balance to avoid insufficient balance errors
## Error Recovery
For server errors (500):
* Implement retry logic with exponential backoff
* Maximum 3 retry attempts recommended
* Add jitter to prevent thundering herd
For service unavailable (503):
* Implement retry logic with exponential backoff
* Maximum 5 retry attempts recommended (infrastructure usually recovers)
* Start with longer delays (30s, 60s, 120s...)
* Check our status page for ongoing incidents
For validation errors (400):
* Log the specific validation failures
* Fix the request payload based on the `reason` field
* Resubmit with corrected data
For authentication errors (401):
* Verify API key is correctly formatted
* Check API key is included in `x-api-key` header
* Ensure API key is active and valid
For insufficient balance errors (400):
* Check current balance via account dashboard
* Top up balance as needed
* Retry the operation
## Support
If you need help resolving errors:
1. Note the `error_id`
2. Check our [status page](https://status.tamradar.com)
3. Contact support at [support@tamradar.com](mailto:support@tamradar.com)
Include the following in support requests:
* Error ID
* Timestamp
* Request details (endpoint, method)
* Steps to reproduce
# Persona-Based Filtering
Source: https://docs.tamradar.com/guides/filtering-and-personas
How to apply persona-based filters for high-signal radar results.
Persona-based filtering allows you to receive only the most relevant records for your needs. By specifying filters when creating a radar, you can narrow down the data sent to your webhooks (or available via `GET /v1/updates`), reducing noise and focusing on the signals that matter most.
> **Note:** `webhook_url` is optional in all examples below. Omit it to create a poll-only radar.
Filtering is supported for the following radar types:
* `company_new_hires`
* `company_job_openings`
* `company_promotions`
* `company_social_posts_cxo`
For other radar types, the filter fields (`departments`, `seniorities`, `job_titles`) will be ignored.
## How to Use Filters
To apply a filter, include the filter fields directly in the body of your `POST /v1/radars/companies` request.
```json theme={null}
{
"domain": "example.com",
"radar_type": "company_job_openings",
"webhook_url": "https://your-webhook.com/endpoint",
"departments": ["Engineering"],
"seniorities": ["Senior", "Manager"]
}
```
If you omit the filter fields (`departments`, `seniorities`, `job_titles`), TAMradar will return all updates without any filtering. This is useful when you want to receive all events for a given radar type.
```json theme={null}
{
"domain": "example.com",
"radar_type": "company_job_openings",
"webhook_url": "https://your-webhook.com/endpoint"
// Will receive all job openings updates
}
```
## Filtering by Department and Seniority
You can filter records by `departments` and/or `seniorities`.
* **`departments`**: An array of strings specifying the departments to include.
* **`seniorities`**: An array of strings specifying the seniority levels to include.
When you provide both `departments` and `seniorities`, they are combined with **`AND`** logic. A record must match **at least one** value from each specified filter (departments AND seniorities) to be included.
If you provide only one key (e.g., only `departments`), only that filter will be applied.
### Accepted Values
You must use one of the predefined values for these fields.
#### Departments
```
'Accounting', 'Administrative', 'Business Development', 'Consulting', 'Customer Success', 'Design', 'Education', 'Engineering', 'Finance', 'Human Resources', 'Information Technology', 'Leadership', 'Legal', 'Manufacturing', 'Marketing', 'Media and Communication', 'Operations', 'Product Management', 'Project Management', 'Purchasing', 'Quality Assurance', 'Real Estate', 'Research', 'Sales', 'Support'
```
> **Note:** `Other` is an output-only value returned by the classifier when no specific department matches. It cannot be used as a filter value.
#### Seniorities
```
'Owner', 'CXO', 'Vice President', 'Director', 'Manager', 'Senior', 'Entry', 'Training', 'Partner'
```
> **Note:** `Other` is an output-only value returned when no seniority matches. It cannot be used as a filter value.
### Example: Department & Seniority
This request will create a radar that only sends webhook notifications for new hires who are at the `Senior` or `Manager` level within the `Engineering` or `Product Management` departments.
```json theme={null}
{
"domain": "ramp.com",
"radar_type": "company_new_hires",
"webhook_url": "https://your-webhook.com/endpoint",
"departments": ["Engineering", "Product Management"],
"seniorities": ["Senior", "Manager"]
}
```
## Advanced Filtering with `job_titles`
Use `job_titles` to filter results with a Boolean expression.
**Important:** When `job_titles` is provided, it takes precedence over `departments` and `seniorities`. Those fields do not affect matching, so we recommend omitting them.
### Matching types
| Syntax | Meaning |
| ----------------------------------- | -------------------------------------------------------------------------- |
| `sales` | Matches a single job-title term using the existing term-matching behavior. |
| `"Sales Operations"` | Matches when the complete phrase appears within the title. |
| `EXACT("Sales Operations Manager")` | Matches only when the complete normalized title equals this value. |
Quotes mean containment: `"Sales Operations"` matches `Senior Sales Operations Manager`, but not `Sales and Operations Manager`. `EXACT("Sales Operations Manager")` matches only that complete normalized title, regardless of case. It does not match `Senior Sales Operations Manager` and does not expand aliases: `EXACT("CEO")` does not match `Chief Executive Officer`.
### Boolean operators
Use `OR` for alternatives, `AND` to require multiple conditions, and `NOT` to exclude the term or group that follows it. `AND NOT` is `AND` followed by `NOT`, not a separate operator.
One practical way to build a query is in three layers:
1. Choose seniorities or leadership terms: `(VP OR director OR manager OR head)`
2. Choose departments or roles: `(marketing OR sales OR "revenue operations")`
3. Add exclusions: `NOT (junior OR assistant)`
Combine the layers with `AND`:
```text theme={null}
(VP OR director OR manager OR head) AND (marketing OR sales OR "revenue operations") AND NOT (junior OR assistant)
```
In plain language, this query says:
* The title must match `VP`, `director`, `manager`, or `head`.
* It must also match `marketing`, `sales`, or the phrase `revenue operations`.
* It must not match `junior` or `assistant`.
| Candidate title | Match? | Why |
| ---------------------------- | ------ | ------------------------------------------------------------------------ |
| `VP of Marketing` | Yes | Contains `VP` and `marketing`. |
| `VP Marketing Operations` | Yes | Contains `VP` and `marketing`. |
| `VP Product Marketing` | Yes | Contains `VP` and `marketing`. |
| `Head of Revenue Operations` | Yes | Contains `head` and the phrase `revenue operations`. |
| `Assistant VP of Marketing` | No | `assistant` is excluded. |
| `Junior Sales Manager` | No | `junior` is excluded. |
| `Revenue Operations Analyst` | No | It has the functional phrase, but none of the required leadership terms. |
If you already know the titles you want, list them directly with `OR`:
```text theme={null}
"Marketing Manager" OR "Sales Director" OR "Head of Revenue Operations"
```
Because these are quoted phrases, they use containment. For example, `"Marketing Manager"` also matches `Senior Marketing Manager`. Use `EXACT("Marketing Manager")` when only the complete title should match.
Standalone exclusions such as `NOT interim` are also valid. The API evaluates `NOT`, then `AND`, then `OR`, but we recommend parentheses whenever you mix operator types.
### Syntax rules
* **Operators** (`AND`, `OR`, `NOT`) must be **uppercase**. For example, use `CEO OR CTO`; `CEO or CTO` returns HTTP 400.
* **Single-word terms** may be unquoted: `CEO`, `lead`, `sales`.
* **Multi-word phrases must use double quotes**: `"Marketing Manager"`, `"Head of Product"`. Unquoted multi-word input returns a 400 error.
* **Phrase matching is containment, not exact equality**: `"Marketing Manager"` matches `Senior Marketing Manager` and `Marketing Manager, EMEA`, but not `Marketing Operations Manager`.
* **`EXACT` must be uppercase** and must contain exactly one double-quoted title: `EXACT("Marketing Manager")`.
* **Matching is case-insensitive** for title terms. Boolean operators and `EXACT` must still be uppercase.
* **Operator precedence**: `NOT` binds tightest, then `AND`, then `OR`. Use parentheses whenever you mix different operator types to make intent explicit.
* Each term must contain at least two searchable characters and may contain at most 100 characters.
* The complete expression may contain at most 9,000 characters, with up to 32 nested Boolean groups.
### Invalid expressions
These expressions return a validation error:
```text theme={null}
Senior Manager # Multi-word phrases require double quotes
CEO or CTO # Boolean operators must be uppercase
CEO OR # Missing right operand
exact("CEO") # EXACT must be uppercase
EXACT(CEO) # The argument must be double-quoted
EXACT("CEO" OR "CTO") # EXACT accepts one quoted title only
EXACT("CEO" # Missing closing parenthesis
```
### Request example
```json theme={null}
{
"domain": "stripe.com",
"radar_type": "company_job_openings",
"webhook_url": "https://your-webhook.com/endpoint",
"job_titles": "(VP OR director OR manager OR head) AND (marketing OR sales OR \"revenue operations\") AND NOT (junior OR assistant)"
}
```
### Validation behavior
Malformed `job_titles` expressions submitted through `POST /v1/radars/companies` return HTTP `400` with a `job_titles` error explaining what to correct.
Bulk submissions are asynchronous. A structurally valid `POST /v1/radars/bulk` request is initially accepted with HTTP `202`. If an item contains a malformed expression, the bulk processor later records an item-level failure with code `400` and a `job_titles` error.
## Validation
All filter fields and values are validated upon submission. If your request contains invalid field values, unknown field names, or incorrectly formatted values, you will receive a `400 Bad Request` response with a detailed error message explaining the issue. For more information about error responses, see our [Error Handling guide](/guides/error-handling#/).
For detailed information about the API endpoint and request/response formats, see our [Create Radar API Reference](/api-reference/post-radars-companies#/).
# API Overview
Source: https://docs.tamradar.com/introduction
Overview of TAMradar APIs, radar types, delivery modes, and integration model.
## What is TAMradar API?
The TAMradar API gives developers a single integration surface for company, contact, and industry monitoring. The API:
* Continuously monitors publicly available web data across 15 radar types
* Structures findings into consistent, typed webhook payloads
* Delivers updates via push (webhooks) or pull (`GET /v1/updates`), based on your integration model
* Provides a REST API with JSON responses and API key authentication
The API lets developers integrate company, contact, and industry monitoring into their applications, from sales intelligence to market research to competitive tracking.
## How TAMradar Works
TAMradar operates on a simple concept:
1. **Create a radar** β specify a domain (or contact/industry) and the signal type to monitor
2. **We scan continuously** β our pipelines monitor sources on a recurring schedule
3. **We detect findings** β when a match is found, it's processed and structured
4. **You receive updates** β delivered to your webhook and/or available to poll
```
You βββΊ Create Radar βββΊ TAMradar monitors βββΊ Finding detected
β
βββββββββββββββββββββββββ΄βββββββββββββββββββββββ
β Push (webhook) Pull (poll) β
β POST to webhook_url GET /v1/updates β
β (if webhook_url was set) (always works) β
βββββββββββββββββββββββββββββββββββββββββββββββββ
```
Both delivery modes return identical payload structures.
## Core Components
### Radars
Radars are the fundamental monitoring units. Each radar monitors a specific target and signal type and runs continuously until deactivated.
**Company radars** β monitor a company by domain:
| Radar Type | What it tracks |
| ---------------------------- | ------------------------------------ |
| `company_job_openings` | New job postings |
| `company_new_hires` | Recently hired employees |
| `company_promotions` | Internal promotions |
| `company_reviews` | Employer reviews |
| `company_social_posts` | Company social media posts |
| `company_social_posts_cxo` | Executive-level social posts |
| `company_mentions` | Web mentions of the company |
| `company_social_engagements` | Engagement activity on company posts |
**Contact radars** β monitor a specific person:
| Radar Type | What it tracks |
| ---------------------------- | -------------------------------- |
| `contact_job_changes` | Career moves / employer changes |
| `contact_social_posts` | Individual's social media posts |
| `contact_social_engagements` | Individual's engagement activity |
**Industry radars** β monitor signals across an industry or keyword set:
| Radar Type | What it tracks |
| ------------------------- | ---------------------------------------------------------------------- |
| `industry_mentions` | Mentions of your keywords or brands across the web and social channels |
| `industry_funding_rounds` | New funding announcements |
| `industry_new_companies` | Newly founded companies |
| `industry_job_openings` | Job openings matching criteria |
### Delivery
TAMradar supports two delivery modes. You can use one or both:
**Push β Webhooks**
Set a `webhook_url` when creating a radar and TAMradar will POST each finding to your endpoint in real-time. Your endpoint must return a 2xx response to acknowledge receipt.
**Pull β Polling**
`GET /v1/updates` returns findings on demand for any radar, including radars created without a `webhook_url`. Supports cursor-based pagination and filters by `radar_id`, `radar_type`, `domain`, and `since` (ISO 8601 date).
> `webhook_url` is optional on single-radar creation endpoints. For async bulk (`POST /v1/radars/bulk`), `webhook_url` is required.
See **[Update Payloads](/webhooks/overview)** for the full payload format per radar type.
### Contact-Based Radars
Contact radars track specific individuals rather than company-wide events:
* `contact_job_changes` accepts an individual email, a LinkedIn person profile (`/in/`), or a full name; role-based/shared emails and non-person LinkedIn URLs are rejected
* Multiple contacts can be tracked per company domain
* Different contact radar types for the same person are allowed (e.g., both `contact_job_changes` and `contact_social_posts`)
* Within the same company and radar type, duplicate matching uses email or the normalized profile URL; full name alone is not a duplicate key
* Contact identifiers are used for creation but not included in webhook payloads for privacy
### Persona-Based Filtering
Reduce noise by filtering findings to your target persona. Provide a `filters` object when creating a radar:
* Filter by `departments` and `seniorities`
* Use boolean queries for `job_titles` (`AND`, `OR`, `NOT`, quoted phrases)
* Supported radar types: `company_new_hires`, `company_job_openings`, `company_promotions`, `company_social_posts_cxo`
See **[Persona-Based Filtering Guide](/guides/filtering-and-personas)** for full details.
### Balance & Pricing
TAMradar uses a USD-based balance system with two pricing models:
**Monthly Subscription**
* Charged at radar creation
* Recharged automatically every 30 days while active
* If balance is insufficient at renewal, the radar is automatically deactivated
**Update-Based**
* No charge at creation
* Charged per approved finding only β you pay for results
* If a charge fails, all active radars of that type are paused until balance is restored
Rates are per radar type and customer-specific. See **[Balance System Explained](/guides/balance-system-explained)** for full details including refunds and balance webhook events.
## The TAMradar Lifecycle
1. **Create** β single radar or submit an async bulk (up to 1000 items) via `POST /v1/radars/bulk`
2. **Monitor** β TAMradar scans sources continuously on a schedule
3. **Detect** β findings are processed, structured, and stored
4. **Deliver** β pushed to your webhook and/or available via `GET /v1/updates`
5. **Charge** β monthly at renewal (subscription) or per finding (update-based)
6. **Deactivate** β manually via `DELETE /v1/radars/:id`, in bulk via `DELETE /v1/radars/bulk`, or automatically on insufficient balance
## Use Cases
* **Sales Intelligence** β trigger outreach on hiring signals, leadership changes, funding rounds
* **TAM Prioritization** β surface accounts showing intent (social posts, job openings, new hires)
* **TAM Expansion** β discover companies mentioning target keywords or hiring for specific roles
* **Competitive Analysis** β monitor competitor activities and executive messaging
* **Recruitment** β track hiring patterns and new joiners at target companies
* **Investor Intelligence** β monitor portfolio companies or track funding activity by sector
## Technical Foundation
* **RESTful API** β standard HTTP methods, consistent JSON responses
* **API Key Auth** β `x-api-key` header on all `/v1/` routes
* **Typed creation endpoints** β `POST /v1/radars/companies`, `/contacts`, `/industry`
* **Async bulk creation** β `POST /v1/radars/bulk` β queue up to 1000 items (returns `202` + `bulk_id`)
* **Bulk status polling** β `GET /v1/radars/bulk/{bulk_id}` β track progress and final per-item outcomes
* **Poll endpoint** β `GET /v1/updates` β cursor-based, up to 100 per page, filterable
* **Account summary** β `GET /v1/account` β balance, radar counts, optional `?month=YYYY-MM`
* **Webhook schema** β `GET /v1/webhooks/schema` β runtime payload schema per radar type
* **Rate limiting** β fair-use limits enforced per API key
## Getting Started
1. **[Get Started Guide](/getting-started)** β step-by-step: create your first radar
2. **[API Definitions](/api-reference/post-radars-companies#/)** β full OpenAPI spec
3. **[Authentication](/guides/authentication-rate-limits#/)** β API key usage and rate limits
4. **[Update Payloads](/webhooks/overview)** β payload structure per radar type (webhooks + poll)
5. **[Error Handling](/guides/error-handling#/)** β error codes and troubleshooting
***
Need personalized assistance? [Contact our support team](mailto:support@tamradar.com)
# Quickstart Guide
Source: https://docs.tamradar.com/quickstart
Fast end-to-end integration walkthrough with practical API examples.
This guide walks through a complete TAMradar integration from scratch. We'll use a concrete scenario throughout: **monitoring OpenAI**, including job openings, executive moves, and AI funding activity across the industry.
By the end you'll have three active radars, a working webhook handler, and know how to poll for updates programmatically.
**Prerequisites**
* A TAMradar API key (UUID format, e.g. `a1b2c3d4-...`)
* A publicly accessible HTTPS endpoint that accepts POST requests β use [webhook.site](https://webhook.site) for testing
* Any HTTP client (`curl`, Axios, `fetch`, etc.)
**Base URL:** `https://api.tamradar.com`
**Auth header:** `x-api-key: YOUR_API_KEY` (required on every request)
***
## Step 1: Verify Your Account & Balance
Before creating anything, confirm your key works and you have enough balance.
```bash theme={null}
curl https://api.tamradar.com/v1/account \
-H "x-api-key: YOUR_API_KEY"
```
```json theme={null}
{
"status": "success",
"code": 200,
"message": "Account summary retrieved",
"data": {
"balance_remaining_usd": 29.85,
"account": {
"total_radars": 12,
"active_radars": 8,
"inactive_radars": 4,
"failed_radars": 0,
"total_updates_found": 541
},
"usage": {
"month": "2026-04",
"balance_used_usd": 3.20,
"radars_activated": 4,
"radars_deactivated": 1,
"radars_failed": 0,
"updates_found": 63
}
},
"timestamp": "2026-04-30T09:00:00Z"
}
```
`balance_remaining_usd` is what you have to spend. Each radar type has its own price. If you don't have enough to cover a radar creation, you'll get a `402` before anything is charged.
***
## Step 2: Test Your Webhook Endpoint
Send a sample payload to your endpoint before committing balance. This is fire-and-forget. It won't create a radar or charge anything.
```bash theme={null}
curl -X POST https://api.tamradar.com/v1/webhooks/test \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"webhook_url": "https://webhook.site/your-unique-id",
"radar_type": "company_job_openings"
}'
```
Check your endpoint. You should receive a sample `radar_finding` payload. If it arrives, your endpoint is reachable and returning 2xx. Proceed.
> You can also test a failure payload: add `"update_type": "radar_failure"` to the body.
***
## Step 3: Create a Company Radar (Job Openings at OpenAI)
Track new engineering job openings posted by OpenAI, filtered to Senior-level and above.
> **`webhook_url` is optional.** Include it to receive real-time push notifications. Omit it to poll for updates via `GET /v1/updates` instead.
```bash theme={null}
curl -X POST https://api.tamradar.com/v1/radars/companies \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"domain": "openai.com",
"radar_type": "company_job_openings",
"webhook_url": "https://webhook.site/your-unique-id",
"updates_since": "2026-04-01T00:00:00-04:00",
"departments": ["Engineering", "Research"],
"seniorities": ["Senior", "Director", "Vice President", "CXO"],
"custom_fields": {
"account_id": "openai-001",
"priority": "high"
}
}'
```
**201 β Created:**
```json theme={null}
{
"status": "success",
"code": 201,
"message": "Radar created successfully",
"data": {
"radar_id": "c70813b3-7e87-4ca7-90fd-8c64574d911b",
"radar_type": "company_job_openings",
"domain": "openai.com",
"webhook_url": "https://webhook.site/your-unique-id",
"radar_status": "active",
"created_at": "2026-04-30T09:05:00Z",
"updates_since": "2026-04-01T04:00:00.000Z",
"deactivated_at": null,
"next_charge_at": "2026-05-30T09:05:00Z",
"departments": ["Engineering", "Research"],
"seniorities": ["Senior", "Director", "Vice President", "CXO"],
"custom_fields": {
"account_id": "openai-001",
"priority": "high"
}
},
"timestamp": "2026-04-30T09:05:00Z"
}
```
**Save `radar_id`.** You'll use it to check status or deactivate the radar.
`next_charge_at` is your next billing date, 30 days from creation.
`updates_since` asks TAMradar to include eligible historical source events from
the chosen past instant. It cannot schedule a future start. Omit it to receive
`updates_since: null` and use the radar's `created_at` as the effective cutoff.
### What can go wrong
| Code | Reason | Fix |
| ----- | ------------------------------------------------------------------------- | --------------------------------------------------------------------- |
| `402` | Insufficient balance | Top up your account, then retry |
| `409` | Radar already exists for `openai.com + company_job_openings` | Use the `radar_id` in the error response to manage the existing radar |
| `400` | Invalid domain, bad department value, invalid `webhook_url` (if provided) | Check `errors[].reason` for the exact field and issue |
| `401` | Missing or invalid API key | Verify `x-api-key` header is present and correct |
***
## Step 4: Create a Contact Radar (Track Sam Altman)
Monitor job changes for a specific person. Requires `domain` + at least one identifier (`email`, `profile_url`, or `full_name`).
```bash theme={null}
curl -X POST https://api.tamradar.com/v1/radars/contacts \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"radar_type": "contact_job_changes",
"domain": "openai.com",
"profile_url": "https://www.linkedin.com/in/samaltman/",
"full_name": "Sam Altman",
"webhook_url": "https://webhook.site/your-unique-id",
"custom_fields": {
"account_id": "openai-001",
"contact_type": "executive"
}
}'
```
**201 β Created:**
```json theme={null}
{
"status": "success",
"code": 201,
"message": "Radar created successfully",
"data": {
"radar_id": "a4f28c91-3b12-4e70-9d55-7e3dda58f100",
"radar_type": "contact_job_changes",
"domain": "openai.com",
"webhook_url": "https://webhook.site/your-unique-id",
"radar_status": "active",
"created_at": "2026-04-30T09:08:00Z",
"updates_since": null,
"deactivated_at": null,
"next_charge_at": "2026-05-30T09:08:00Z",
"custom_fields": {
"account_id": "openai-001",
"contact_type": "executive"
}
},
"timestamp": "2026-04-30T09:08:00Z"
}
```
**Contact radar type requirements:**
| `radar_type` | Required fields |
| ---------------------------- | ----------------------------------------------------------------------- |
| `contact_job_changes` | `domain` + one of (`email`, `profile_url`, `full_name`) |
| `contact_social_engagements` | `profile_url` (must be `linkedin.com/in/`, `x.com/`, or `twitter.com/`) |
| `contact_social_posts` | `profile_url` (same as above) |
For `contact_job_changes`, `email` must identify an individual; role-based,
shared, and placeholder addresses are rejected. A supplied `profile_url` must
be a LinkedIn person profile (`linkedin.com/in/...`); company, school, and
legacy `/pub/` URLs are rejected. Duplicate matching within the same company
and radar type uses the email or normalized LinkedIn profile URL. `full_name`
is accepted as an identifier but is not a duplicate key.
***
## Step 5: Create an Industry Radar (AI Funding Rounds)
Track funding events across the AI industry, with no specific company or keyword needed.
```bash theme={null}
curl -X POST https://api.tamradar.com/v1/radars/industry \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"radar_type": "industry_funding_rounds",
"webhook_url": "https://webhook.site/your-unique-id",
"custom_fields": {
"segment": "ai"
}
}'
```
**201 β Created:**
```json theme={null}
{
"status": "success",
"code": 201,
"message": "Radar created successfully",
"data": {
"radar_id": "f9d01c55-82ba-4f3e-b461-2a9e7c11d830",
"radar_type": "industry_funding_rounds",
"domain": null,
"webhook_url": "https://webhook.site/your-unique-id",
"radar_status": "active",
"created_at": "2026-04-30T09:10:00Z",
"updates_since": null,
"deactivated_at": null,
"next_charge_at": "2026-05-30T09:10:00Z",
"custom_fields": {
"segment": "ai"
}
},
"timestamp": "2026-04-30T09:10:00Z"
}
```
`domain` is always `null` for industry radars.
**Industry radar type requirements:**
| `radar_type` | Requires `keyword`? |
| ------------------------- | -------------------------------------------------- |
| `industry_funding_rounds` | No |
| `industry_new_companies` | No |
| `industry_mentions` | Yes (min 3 chars) |
| `industry_job_openings` | Yes (min 3 chars) + optional `countries[]` (max 5) |
***
## Step 6: Scale Up with Bulk Creation
Submit up to 1000 radars in one async bulk request. Useful when onboarding large account lists.
```bash theme={null}
curl -X POST https://api.tamradar.com/v1/radars/bulk \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"webhook_url": "https://webhook.site/your-unique-id",
"radars": [
{
"domain": "openai.com",
"radar_type": "company_new_hires",
"updates_since": "2026-04-01T00:00:00Z",
"departments": ["Engineering"],
"seniorities": ["Senior", "Director"],
"custom_fields": { "account_id": "openai-001" }
},
{
"domain": "anthropic.com",
"radar_type": "company_job_openings",
"custom_fields": { "account_id": "anthropic-001" }
},
{
"domain": "openai.com",
"radar_type": "company_job_openings"
}
]
}'
```
`webhook_url` is required for async bulk submissions. Bulk processing runs in the background and emits item outcomes (`radar_created` / `radar_failure`) plus a final `bulk_completed`.
**202 β Accepted** (request queued for async processing):
```json theme={null}
{
"status": "success",
"code": 202,
"message": "Bulk submission accepted β radars are being created asynchronously",
"bulk_id": "5f9da22e-4778-427c-b7e4-dab2a8d9d709",
"summary": { "total": 3 },
"timestamp": "2026-04-30T09:15:00Z"
}
```
Track progress with the returned `bulk_id`:
```bash theme={null}
curl "https://api.tamradar.com/v1/radars/bulk/5f9da22e-4778-427c-b7e4-dab2a8d9d709" \
-H "x-api-key: YOUR_API_KEY"
```
The status endpoint returns:
* `status: "processing"` while there are in-flight items
* `status: "completed"` when all items are terminal
* `summary` counts (`processing`, `created`, `failed`)
* `radars[]` with `item_index` so you can map outcomes back to input order
* `radars[].update_id` so you can correlate each item with its `radar_created` / `radar_failure` event
For webhook idempotency/correlation in async bulk:
* Use top-level `update_id` on `bulk_completed` to deduplicate the whole batch event
* Use `radars[].update_id` inside `bulk_completed` to correlate each item-level event
***
## Step 7: Handle Failure Webhooks
When a radar fails during setup, TAMradar sends a `radar_failure` webhook to your endpoint and refunds the charge.
**`missing_prerequisites`** β The company has no trackable public data source for this radar type. Balance is refunded.
```json theme={null}
{
"update_id": "41991828-ed31-4bd9-98d2-44b2763f9f35",
"update_type": "radar_failure",
"record_id": null,
"discovered_at": "2026-04-30T10:00:00Z",
"completed_at": "2026-04-30T10:00:00Z",
"data": {
"radar_id": "c70813b3-7e87-4ca7-90fd-8c64574d911b",
"radar_type": "company_job_openings",
"domain": "openai.com",
"next_charge_at": null,
"custom_fields": { "account_id": "openai-001", "priority": "high" }
},
"status": "error",
"code": 400,
"message": "Radar creation failed because the domain does not have required data sources. Credits have been refunded.",
"errors": [{ "field": "radar", "reason": "missing_prerequisites" }],
"refund_amount_usd": 0.10,
"timestamp": "2026-04-30T10:00:00Z",
"error_id": "41991828-ed31-4bd9-98d2-44b2763f9f35"
}
```
**`insufficient_funds`** β Recurring billing failed; radar was deactivated. No refund (the radar ran; it just couldn't renew).
```json theme={null}
{
"update_type": "radar_failure",
"data": { "radar_id": "...", "radar_type": "company_job_openings", "next_charge_at": null },
"status": "error",
"code": 402,
"message": "Your radar was deactivated due to insufficient funds.",
"errors": [{ "field": "radar", "reason": "insufficient_funds" }],
"refund_amount_usd": 0
}
```
**Failure reference** β identified by `code` and `message`:
| Failure type | `code` | Refund? | What to do |
| --------------------- | ------ | ------- | ------------------------------------------------- |
| Missing prerequisites | 400 | Yes | Verify the company has accessible public profiles |
| Setup failure | 400 | Yes | Contact support |
| Insufficient funds | 402 | No | Top up balance; create a new radar to resume |
| Source inaccessible | 400 | Yes | Target profile is private or has been removed |
***
## Step 8: Receive Webhook Findings
When a radar detects something, it POSTs to your `webhook_url`. Every payload shares the same base structure. Only `content` varies by radar type.
### Company job opening at OpenAI
```json theme={null}
{
"update_id": "efbf7c77-c1ae-4a0d-85b6-116674e9e0b1",
"update_type": "radar_finding",
"record_id": "b77af154-c369-4446-b70a-f328880c3a48",
"discovered_at": "2026-04-30T14:30:00Z",
"data": {
"radar_id": "c70813b3-7e87-4ca7-90fd-8c64574d911b",
"radar_type": "company_job_openings",
"domain": "openai.com",
"next_charge_at": "2026-05-30T09:05:00Z",
"custom_fields": { "account_id": "openai-001", "priority": "high" }
},
"content": {
"job_title": "Senior Research Engineer, Alignment",
"job_url": "https://openai.com/careers/senior-research-engineer-alignment",
"job_posted_at": "2026-04-29",
"job_description": "We are looking for a Senior Research Engineer to work on alignment and safety...",
"job_source": "Company Website",
"country": "United States"
}
}
```
### New hire at OpenAI
```json theme={null}
{
"update_id": "a1b2c3d4-0001-4000-b000-000000000001",
"update_type": "radar_finding",
"record_id": "d99bf154-1234-4446-b70a-abc123456789",
"discovered_at": "2026-04-30T15:00:00Z",
"data": {
"radar_id": "...",
"radar_type": "company_new_hires",
"domain": "openai.com",
"next_charge_at": "2026-05-30T09:05:00Z",
"custom_fields": { "account_id": "openai-001" }
},
"content": {
"full_name": "Jane Doe",
"first_name": "Jane",
"last_name": "Doe",
"profile_url": "https://www.linkedin.com/in/janedoe/",
"title": "Senior Software Engineer",
"start_date": "2026-04-15",
"country": "United States"
}
}
```
**Key fields for your handler:**
* `update_id` β use for idempotency. If you receive the same `update_id` twice, skip it.
* `custom_fields` β your metadata, echoed back exactly as set on the radar.
* `discovered_at` β use for ordering and incremental polling (see Step 9).
* Return any `2xx` status within **10 seconds**. TAMradar will retry on failure (up to 3 retries: 1m, 5m, 15m).
### Handling webhooks in your server
Acknowledge first, process after. TAMradar waits up to 10 seconds for a 2xx. If you do slow work (database writes, third-party calls) synchronously before responding, you risk a timeout and a retry.
```javascript theme={null}
// Node.js + Express
app.post('/webhooks/tamradar', express.json(), async (req, res) => {
// Acknowledge immediately β processing happens after
res.sendStatus(200);
const { update_id, update_type, data, code, message } = req.body;
// Deduplicate β TAMradar delivers at-least-once, so duplicates are possible
const alreadySeen = await db.processedUpdates.exists(update_id);
if (alreadySeen) return;
await db.processedUpdates.insert(update_id);
if (update_type === 'radar_failure') {
// Alert your team β the radar needs attention
await alertOps(`Radar ${data.radar_id} failed (${code}): ${message}`);
return;
}
// Route by radar type β custom_fields carry your own identifiers
switch (data.radar_type) {
case 'company_job_openings':
await notifySlack(`New role at ${data.domain}: ${content.job_title} β ${content.job_url}`);
break;
case 'company_new_hires':
await updateCRM(data.custom_fields.account_id, { new_hire: content });
break;
case 'contact_job_changes':
await triggerSalesSequence(data.custom_fields.contact_id, content);
break;
case 'industry_funding_rounds':
await logFundingEvent(content);
break;
}
});
```
The pattern applies in any language. The key constraints are to respond 2xx before doing work and deduplicate on `update_id`.
***
## Step 9: Poll for Updates (Alternative to Webhooks)
`GET /v1/updates` returns the same payloads that webhooks deliver. Use this to backfill missed events, audit history, or as your primary data retrieval method.
Do not confuse `GET /v1/updates?since=` with radar `updates_since`.
`updates_since` is the radar's permanent source-event eligibility boundary;
the polling `since` parameter only filters TAMradar updates already created.
**Fetch all updates for your OpenAI radars since yesterday:**
```bash theme={null}
curl "https://api.tamradar.com/v1/updates?domain=openai.com&since=2026-04-29T00:00:00Z" \
-H "x-api-key: YOUR_API_KEY"
```
**Fetch a specific radar type, paginated:**
```bash theme={null}
curl "https://api.tamradar.com/v1/updates?radar_type=company_job_openings&limit=50" \
-H "x-api-key: YOUR_API_KEY"
```
**Response:**
```json theme={null}
{
"status": "success",
"code": 200,
"message": "Updates retrieved successfully",
"updates": [ /* same shape as webhook payloads */ ],
"has_more": true,
"next_cursor": "eyJyIjoiMjAyNi0wNC0xM1QyMToyMjo1OVoiLCJpIjoiZWZiZjdjNzctYzFhZS00YTBkLTg1YjYtMTE2Njc0ZTllMGIxIn0=",
"timestamp": "2026-04-30T10:00:00Z"
}
```
**Paginate:**
```bash theme={null}
curl "https://api.tamradar.com/v1/updates?cursor=eyJyIjoiMjAyNi0wNC0x..." \
-H "x-api-key: YOUR_API_KEY"
```
Keep fetching with `next_cursor` until `has_more: false`.
**Incremental polling loop:**
```
1. Call GET /v1/updates?since={last_discovered_at}
2. Process updates in order
3. Store the highest discovered_at you've seen
4. While has_more: pass next_cursor to get next page
5. Sleep or repeat on schedule
```
> `radar_failure` events are excluded by default. To include them: `?update_type=radar_finding,radar_failure`
> Data retention: **60 days**. Poll at least that often if you don't use webhooks.
***
## Step 10: Monitor Your Radars
**List all active radars:**
```bash theme={null}
curl "https://api.tamradar.com/v1/radars" \
-H "x-api-key: YOUR_API_KEY"
```
**Get a specific radar:**
```bash theme={null}
curl "https://api.tamradar.com/v1/radars/c70813b3-7e87-4ca7-90fd-8c64574d911b" \
-H "x-api-key: YOUR_API_KEY"
```
Key fields to watch: `radar_status` (active/inactive), `updates_since` (stored
eligibility boundary or null), `next_charge_at` (next billing date), and
`deactivated_at`.
***
## Step 11: Deactivate a Radar
When you no longer need a radar, delete it to stop billing.
```bash theme={null}
curl -X DELETE "https://api.tamradar.com/v1/radars/c70813b3-7e87-4ca7-90fd-8c64574d911b" \
-H "x-api-key: YOUR_API_KEY"
```
```json theme={null}
{
"status": "success",
"code": 200,
"message": "Radar deactivation initiated - service will continue until the end of the current billing cycle",
"data": {
"radar_id": "c70813b3-7e87-4ca7-90fd-8c64574d911b",
"radar_status": "inactive",
"updates_since": "2026-04-01T04:00:00.000Z",
"deactivated_at": "2026-04-30T10:30:00Z",
"next_charge_at": null
},
"timestamp": "2026-04-30T10:30:00Z"
}
```
Deactivation is **irreversible**. Create a new radar to resume monitoring.
On monthly billing, the radar continues running until the end of the current billing cycle. On update-based billing, deactivation is immediate.
To deactivate 2β500 known radar IDs at once, use
[`DELETE /v1/radars/bulk`](/api-reference/delete-bulk-radars). It returns final,
ordered per-item outcomes synchronously; its `bulk_id` is for correlation, not
status polling.
***
## Before You Ship
* [ ] **Deduplicate on `update_id`** β webhooks are at-least-once. Store processed IDs and skip duplicates.
* [ ] **Respond 2xx within 10 seconds** β acknowledge the request before doing any slow work.
* [ ] **Handle `radar_failure` webhooks** β alert your team when a radar fails so it doesn't silently stop.
* [ ] **Monitor `balance_remaining_usd`** β set up an alert when it drops below your comfort threshold. A zero balance deactivates radars without warning.
* [ ] **Store `radar_id` per account** β you'll need it to list, inspect, or deactivate a radar later.
* [ ] **Test your endpoint with `/v1/webhooks/test`** before creating your first paid radar.
* [ ] **Use `since` for incremental polling** β if you use `/v1/updates`, store the last `discovered_at` and pass it on the next call. Don't re-fetch everything on every poll.
***
## Quick Reference
| Method | Endpoint | What it does |
| -------- | -------------------------- | -------------------------------------- |
| `GET` | `/v1/account` | Check balance and radar counts |
| `POST` | `/v1/webhooks/test` | Send a test payload to a URL |
| `POST` | `/v1/radars/companies` | Create a company radar |
| `POST` | `/v1/radars/contacts` | Create a contact radar |
| `POST` | `/v1/radars/industry` | Create an industry radar |
| `POST` | `/v1/radars/bulk` | Queue up to 1000 radars asynchronously |
| `GET` | `/v1/radars/bulk/:bulk_id` | Poll async bulk processing status |
| `GET` | `/v1/radars` | List all radars |
| `GET` | `/v1/radars/:id` | Get a single radar |
| `DELETE` | `/v1/radars/:id` | Deactivate a radar |
| `GET` | `/v1/updates` | Poll for radar findings |
**Rate limits:** TAMradar uses separate write/read budgets plus a dedicated bulk budget.
| Header | Description |
| ----------------------- | ------------------------------------------- |
| `X-RateLimit-Limit` | Per-minute budget for the matched bucket |
| `X-RateLimit-Remaining` | Remaining tokens in that bucket |
| `X-RateLimit-Reset` | Seconds until bucket reset (when available) |
On `429`, honor `Retry-After` before retrying.
**Error codes:**
| Code | Meaning |
| ----- | ------------------------------------------------------------------- |
| `400` | Validation error β check `errors[].field` and `errors[].reason` |
| `401` | Missing or invalid `x-api-key` |
| `402` | Insufficient balance β top up and retry |
| `409` | Radar already exists β use the `radar_id` in the error to manage it |
| `429` | Rate limit exceeded β back off and retry |
| `5xx` | Server error β retry with exponential backoff |
Every error response includes `error_id`. Include it when contacting support.
# Base Webhook Payload Example
Source: https://docs.tamradar.com/webhooks/base
Base Webhook Payload Example documentation for TAMradar API.
```json theme={null}
{
"update_id": "41991828-ed31-4bd9-98d2-44b2763f9f35",
"update_type": "radar_finding",
"record_id": "b77af154-c369-4446-b70a-f328880c3a48",
"discovered_at": "2024-03-20T12:30:00Z",
"data": {
"radar_id": "c70813b3-7e87-4ca7-90fd-8c64574d911b",
"radar_type": "company_job_openings",
"domain": "domain.com",
"custom_fields": {
"priority": "high",
"account_id": "0011U00000TFV7MQAX"
}
},
"content": {}
}
```
# Company Job Openings
Source: https://docs.tamradar.com/webhooks/company/job-openings
Company Job Openings documentation for TAMradar API.
Notifies you when new job postings are detected from your tracked companies. This radar type helps you monitor hiring trends and growth indicators.
### Content Structure
The `content` object contains the job posting information:
| Field | Type | Required | Nullable | Description |
| ---------------- | --------- | -------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| job\_title | string | Yes | No | Title of the job posting |
| job\_url | string | Yes | No | URL to the original job posting |
| job\_posted\_at | string | Yes | Yes | Best estimated posting date (YYYY-MM-DD) |
| job\_description | string | Yes | Yes | Full or partial job description |
| job\_source | string | Yes | No | Source of the job posting (e.g., LinkedIn, Company Website) |
| country | string | Yes | Yes | Country where the job is located (extracted from job location data) |
| departments | string\[] | Yes | No | Department categories (1β3 values). Values: `Accounting`, `Administrative`, `Business Development`, `Consulting`, `Customer Success`, `Design`, `Education`, `Engineering`, `Finance`, `Human Resources`, `Information Technology`, `Leadership`, `Legal`, `Manufacturing`, `Marketing`, `Media and Communication`, `Operations`, `Product Management`, `Project Management`, `Purchasing`, `Quality Assurance`, `Real Estate`, `Research`, `Sales`, `Support`, `Other` |
| seniority | string | Yes | No | Seniority level. One of: `Owner`, `CXO`, `Vice President`, `Director`, `Manager`, `Senior`, `Entry`, `Training`, `Partner`, `Other` |
### Example
This example includes [base webhook fields](/webhooks/overview) along with job-opening-specific content:
```json theme={null}
{
"update_id": "41991828-ed31-4bd9-98d2-44b2763f9f35",
"update_type": "radar_finding",
"record_id": "b77af154-c369-4446-b70a-f328880c3a48",
"discovered_at": "2024-03-20T14:30:00Z",
"data": {
"radar_id": "c70813b3-7e87-4ca7-90fd-8c64574d911b",
"radar_type": "company_job_openings",
"domain": "domain.com",
"next_charge_at": "2024-04-20T14:30:00Z",
"custom_fields": {
"priority": "high",
"account_id": "0011U00000TFV7MQAX"
}
},
"content": {
"job_title": "Senior Software Engineer",
"job_url": "https://www.linkedin.com/jobs/view/123456789",
"job_posted_at": "2024-03-20",
"job_description": "We are seeking a Senior Software Engineer to join our growing team. The ideal candidate will have strong experience in distributed systems and cloud architecture...",
"job_source": "LinkedIn",
"country": "United Kingdom",
"departments": ["Engineering"],
"seniority": "Senior"
}
}
```
# Company Mentions
Source: https://docs.tamradar.com/webhooks/company/mentions
Company Mentions documentation for TAMradar API.
Notifies you when social media posts, articles, or other content mentions your tracked companies. This radar type helps you monitor brand presence and sentiment across various platforms.
### Content Structure
The `content` object for company mentions contains:
> **Note**: The author object now includes detailed structured fields. The `name` field is maintained for backward compatibility and is automatically constructed from `first_name + last_name` when available.
| Field | Type | Required | Description |
| ------------------- | ------ | -------- | ----------------------------------------------- |
| mention\_content | string | Yes | Text content of the post or article |
| mention\_url | string | Yes | URL to the original post or article |
| mention\_channel | string | Yes | Platform or source (e.g., Twitter, LinkedIn) |
| mention\_posted\_at | string | Yes | Best estimated date of the mention (YYYY-MM-DD) |
| author | object | No | Information about the author of the mention |
#### Author Object
| Field | Type | Required | Description |
| --------------- | ------ | -------- | ---------------------------------------------------------------------------------- |
| name | string | No | Full name of the author (constructed from first\_name + last\_name when available) |
| profile\_type | string | No | Type of profile: `"contact"` or `"company"` |
| first\_name | string | No | Author's first name - `null` if `profile_type` is `"company"` |
| last\_name | string | No | Author's last name - `null` if `profile_type` is `"company"` |
| profile\_url | string | No | URL to the author's profile |
| title | string | No | Author's job title - `null` if `profile_type` is `"company"` |
| company\_domain | string | No | Domain of the author's company |
| company\_name | string | No | Name of the author's company |
| country | string | No | Author's country location |
### Example
This example includes [base webhook fields](/webhooks/overview) along with company-mentions-specific content:
```json theme={null}
{
"update_id": "41991828-ed31-4bd9-98d2-44b2763f9f35",
"update_type": "radar_finding",
"record_id": "b77af154-c369-4446-b70a-f328880c3a48",
"discovered_at": "2024-03-19T10:15:30Z",
"data": {
"radar_id": "c70813b3-7e87-4ca7-90fd-8c64574d911b",
"radar_type": "company_mentions",
"domain": "domain.com",
"next_charge_at": "2024-04-20T16:10:00Z",
"custom_fields": {
"priority": "high",
"account_id": "0011U00000TFV7MQAX"
}
},
"content": {
"mention_content": "@PrifWeinidog @DomainAI Can you confirm that you are not going to knock down statues of white men that have greatly contributed to history because it's has been reported on X that this is in your government's plan. If this is true it is an utter disgrace",
"mention_url": "https://twitter.com/Catheri86555691/status/1902104771068125631",
"mention_channel": "Twitter",
"mention_posted_at": "2024-03-18",
"author": {
"name": "Joe Doe",
"profile_type": "contact",
"first_name": "Joe",
"last_name": "Doe",
"profile_url": "https://www.linkedin.com/in/joedoe",
"title": "Director of Sales",
"company_domain": "abc.com",
"company_name": "ABC Corporation",
"country": "United States"
}
}
}
```
# Company New Hires
Source: https://docs.tamradar.com/webhooks/company/new-hires
Company New Hires documentation for TAMradar API.
Notifies you when new employees are detected joining your tracked companies. This radar type helps you monitor team growth and talent acquisition trends.
### Content Structure
The `content` object for new hires contains:
| Field | Type | Required | Nullable | Description |
| ------------ | --------- | -------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| full\_name | string | Yes | No | Full name of the new hire |
| first\_name | string | Yes | No | First name of the new hire |
| last\_name | string | Yes | No | Last name of the new hire |
| profile\_url | string | Yes | No | URL to the person's professional profile |
| title | string | Yes | No | Job title of the new hire |
| start\_date | string | Yes | No | Best estimated start date (YYYY-MM-DD) |
| country | string | Yes | Yes | Country of employment |
| departments | string\[] | Yes | No | Department categories (1β3 values). Values: `Accounting`, `Administrative`, `Business Development`, `Consulting`, `Customer Success`, `Design`, `Education`, `Engineering`, `Finance`, `Human Resources`, `Information Technology`, `Leadership`, `Legal`, `Manufacturing`, `Marketing`, `Media and Communication`, `Operations`, `Product Management`, `Project Management`, `Purchasing`, `Quality Assurance`, `Real Estate`, `Research`, `Sales`, `Support`, `Other` |
| seniority | string | Yes | No | Seniority level. One of: `Owner`, `CXO`, `Vice President`, `Director`, `Manager`, `Senior`, `Entry`, `Training`, `Partner`, `Other` |
### Example
This example includes [base webhook fields](/webhooks/overview) along with new-hire-specific content:
```json theme={null}
{
"update_id": "41991828-ed31-4bd9-98d2-44b2763f9f35",
"update_type": "radar_finding",
"record_id": "b77af154-c369-4446-b70a-f328880c3a48",
"discovered_at": "2024-03-20T12:30:00Z",
"data": {
"radar_id": "c70813b3-7e87-4ca7-90fd-8c64574d911b",
"radar_type": "company_new_hires",
"domain": "domain.com",
"next_charge_at": "2024-04-20T12:30:00Z",
"custom_fields": {
"priority": "high",
"account_id": "0011U00000TFV7MQAX"
}
},
"content": {
"full_name": "Joe Doe",
"first_name": "Joe",
"last_name": "Doe",
"profile_url": "https://www.linkedin.com/in/joedoe/",
"title": "Senior Product Designer",
"start_date": "2024-03-15",
"country": "United States",
"departments": ["Design"],
"seniority": "Senior"
}
}
```
# Company Promotions
Source: https://docs.tamradar.com/webhooks/company/promotions
Company Promotions documentation for TAMradar API.
Notifies you when employees are promoted at your tracked companies. This radar type helps you monitor career progression and organizational changes.
### Content Structure
The `content` object for promotions contains:
| Field | Type | Required | Nullable | Description |
| --------------- | --------- | -------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| full\_name | string | Yes | No | Full name of the promoted person |
| first\_name | string | Yes | No | First name of the promoted person |
| last\_name | string | Yes | No | Last name of the promoted person |
| profile\_url | string | Yes | No | URL to the person's professional profile |
| previous\_title | string | Yes | No | Job title before the promotion |
| current\_title | string | Yes | No | New job title after the promotion |
| promotion\_date | string | Yes | No | Best estimated date of promotion (YYYY-MM-DD) |
| country | string | Yes | Yes | Country of employment |
| departments | string\[] | Yes | No | Department categories (1β3 values). Values: `Accounting`, `Administrative`, `Business Development`, `Consulting`, `Customer Success`, `Design`, `Education`, `Engineering`, `Finance`, `Human Resources`, `Information Technology`, `Leadership`, `Legal`, `Manufacturing`, `Marketing`, `Media and Communication`, `Operations`, `Product Management`, `Project Management`, `Purchasing`, `Quality Assurance`, `Real Estate`, `Research`, `Sales`, `Support`, `Other` |
| seniority | string | Yes | No | Seniority level. One of: `Owner`, `CXO`, `Vice President`, `Director`, `Manager`, `Senior`, `Entry`, `Training`, `Partner`, `Other` |
### Example
This example includes [base webhook fields](/webhooks/overview) along with promotion-specific content:
```json theme={null}
{
"update_id": "41991828-ed31-4bd9-98d2-44b2763f9f35",
"update_type": "radar_finding",
"record_id": "b77af154-c369-4446-b70a-f328880c3a48",
"discovered_at": "2024-03-17T08:30:00Z",
"data": {
"radar_id": "c70813b3-7e87-4ca7-90fd-8c64574d911b",
"radar_type": "company_promotions",
"domain": "domain.com",
"next_charge_at": "2024-04-20T15:45:00Z",
"custom_fields": {
"priority": "high",
"account_id": "0011U00000TFV7MQAX"
}
},
"content": {
"full_name": "Joe Doe",
"first_name": "Joe",
"last_name": "Doe",
"profile_url": "https://www.linkedin.com/in/joe-doe-755717101/",
"previous_title": "Global Prospecting Specialist",
"current_title": "Analyst, Global Service Center",
"promotion_date": "2024-03-01",
"country": "United States",
"departments": ["Sales"],
"seniority": "Manager"
}
}
```
# Company Reviews
Source: https://docs.tamradar.com/webhooks/company/reviews
Company Reviews documentation for TAMradar API.
Notifies you when new customer reviews are detected for your tracked companies. This radar type helps you monitor customer feedback and sentiment across various review platforms.
### Content Structure
The `content` object contains the review information:
| Field | Type | Required | Description |
| --------------- | ------ | -------- | -------------------------------------------- |
| review\_content | string | Yes | Text content of the review |
| review\_url | string | Yes | URL to the original review |
| review\_source | string | Yes | Platform where reviewed (e.g., G2, Capterra) |
| review\_rating | string | Yes | Rating as string ("5", "4.5", etc.) |
| reviewed\_at | string | Yes | Best estimated date of review (YYYY-MM-DD) |
### Example
This example includes [base webhook fields](/webhooks/overview) along with review-specific content:
```json theme={null}
{
"update_id": "41991828-ed31-4bd9-98d2-44b2763f9f35",
"update_type": "radar_finding",
"record_id": "b77af154-c369-4446-b70a-f328880c3a48",
"discovered_at": "2024-03-18T09:15:00Z",
"data": {
"radar_id": "c70813b3-7e87-4ca7-90fd-8c64574d911b",
"radar_type": "company_reviews",
"domain": "domain.com",
"next_charge_at": "2024-04-18T09:15:00Z",
"custom_fields": {
"priority": "high",
"account_id": "0011U00000TFV7MQAX"
}
},
"content": {
"review_content": "Great product with excellent customer support. The onboarding process was smooth and the team was very responsive to our needs. Would highly recommend!",
"review_url": "https://www.g2.com/products/domain/reviews/123456",
"review_source": "G2",
"review_rating": "5",
"reviewed_at": "2024-03-18"
}
}
```
# Company Social Engagements
Source: https://docs.tamradar.com/webhooks/company/social-engagements
Company Social Engagements documentation for TAMradar API.
Notifies you when people engage with posts from companies you're tracking on social channels. This includes reactions (like, celebrate, support, etc.), comments, and reposts on the tracked company's posts.
### Content Structure
The `content` object contains the engagement information:
| Field | Type | Required | Description |
| ------------------------ | ------ | -------- | ----------------------------------------------------------------------------- |
| post\_url | string | Yes | URL of the social post that received engagement |
| post\_content | string | Yes | Text content of the original post |
| post\_channel | string | Yes | Social media channel |
| post\_posted\_at | string | Yes | Date when the post was published (`YYYY-MM-DD`) |
| engagement\_type | string | Yes | Type of engagement: `"reaction"`, `"comment"`, or `"repost"` |
| engagement\_content | string | No | Content of the engagement (comment text or repost text). Null for reactions. |
| engagement\_content\_url | string | No | URL of the engagement content (for reposts). Null for reactions and comments. |
| author | object | Yes | Details of the person who engaged with the post |
#### Author Object Structure
The `author` object contains details about the person who engaged:
| Field | Type | Nullable | Description |
| --------------- | ------ | -------- | ------------------------------------------------------------------------------------------------------ |
| profile\_type | string | Yes | Type of profile: `"contact"` or `"company"` |
| first\_name | string | Yes | First name of the person who engaged - `null` if `profile_type` is `"company"` |
| last\_name | string | Yes | Last name of the person - `null` if `profile_type` is `"company"` |
| profile\_url | string | No | Social profile URL of the person or company who engaged |
| title | string | Yes | Job title or position - `null` if `profile_type` is `"company"` |
| company\_name | string | Yes | Company name where the person works, or the engaging company name if `profile_type` is `"company"` |
| company\_domain | string | Yes | Company domain where the person works, or the engaging company domain if `profile_type` is `"company"` |
| country | string | Yes | Country location |
### Examples
These examples include [base webhook fields](/webhooks/overview) along with company-social-engagement-specific content:
#### Contact Engagement Example
When an individual person engages with a company's post:
```json theme={null}
{
"update_id": "fe00b8e8-ae51-4e63-b9af-fc45dbf9517b",
"update_type": "radar_finding",
"record_id": "ae3929d4-d2c6-4008-a756-d5669c919451",
"discovered_at": "2025-10-11T23:20:16Z",
"data": {
"radar_id": "05c4db36-3d28-4475-a4e8-52e0fcf58477",
"radar_type": "company_social_engagements",
"domain": "stripe.com",
"next_charge_at": "2025-11-10T23:21:41Z",
"custom_fields": {
"campaign": "enterprise-accounts",
"priority": "high"
}
},
"content": {
"post_url": "https://www.linkedin.com/posts/stripe_activity-123456789",
"post_content": "Excited to announce our new payment infrastructure for global enterprises! #Payments #Fintech",
"post_channel": "LinkedIn",
"post_posted_at": "2025-10-10",
"engagement_type": "Comment",
"engagement_content": "This looks great! We've been looking for exactly this kind of solution.",
"engagement_content_url": null,
"author": {
"profile_type": "contact",
"first_name": "Sarah",
"last_name": "Johnson",
"profile_url": "https://www.linkedin.com/in/sarahjohnson",
"title": "VP of Finance",
"company_name": "TechCorp Inc",
"company_domain": "techcorp.com",
"country": "United States"
}
}
}
```
#### Company Engagement Example
When a company account engages with another company's post:
```json theme={null}
{
"update_id": "8a1c3f2e-9d4b-4e7f-a5c8-1b3e5f7g9h2j",
"update_type": "radar_finding",
"record_id": "c2f8a9d1-4e6b-4a8c-9f7e-3d5a7b9c1e2f",
"discovered_at": "2025-10-11T15:30:45Z",
"data": {
"radar_id": "05c4db36-3d28-4475-a4e8-52e0fcf58477",
"radar_type": "company_social_engagements",
"domain": "stripe.com",
"next_charge_at": "2025-11-10T23:21:41Z",
"custom_fields": {
"campaign": "enterprise-accounts",
"priority": "high"
}
},
"content": {
"post_url": "https://www.linkedin.com/posts/stripe_activity-987654321",
"post_content": "Stripe continues to lead in payment innovation with our latest security features!",
"post_channel": "LinkedIn",
"post_posted_at": "2025-10-10",
"engagement_type": "reaction",
"engagement_content": null,
"engagement_content_url": null,
"author": {
"profile_type": "company",
"first_name": null,
"last_name": null,
"profile_url": "https://www.linkedin.com/company/techcorp",
"title": null,
"company_name": "TechCorp Inc",
"company_domain": "techcorp.com",
"country": "United States"
}
}
}
```
### Use Cases
**Competitor Intelligence**\
When executives from target accounts engage with your competitors' posts about new product launches or company updates, this signals interest in specific solutions. The engagement data reveals which competitors are getting attention from your prospects and what messaging resonates.
**Market Validation**\
Track how your target market responds to competitor announcements, funding news, or product updates. High engagement from your ideal customer profile validates market demand for specific solutions and helps inform your product strategy.
**Lead Scoring & Timing**\
When someone from a target account actively engages with competitor content through comments and reactions, they're signaling active buying interest. These engaged prospects become high-priority leads for competitive positioning and timely outreach.
**Content Strategy Intelligence**\
Analyze which types of competitor content (product launches, thought leadership, company updates) generate the most engagement from valuable prospects. This intelligence guides your own content strategy and competitive messaging.
### Technical Notes
* **Post Monitoring Window**: We monitor each post for up to 5 days, sending webhooks only for net-new activity detected during that window
* **Incremental Updates**: We continuously check for new engagements during the monitoring window
* **Activity Cutoff**: We only surface engagements on posts published after your `updates_since` timestamp, or after radar creation if `updates_since` wasn't set. To receive historic or backdated activity, set `updates_since` to the earliest date you care about.
# Company Social Posts
Source: https://docs.tamradar.com/webhooks/company/social-posts
Company Social Posts documentation for TAMradar API.
Notifies you when new social media posts are detected from your tracked companies. This radar type helps you monitor company announcements, marketing campaigns, and social media presence.
### Content Structure
The `content` object contains the social media post information:
| Field | Type | Required | Description |
| ---------------- | ------ | -------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
| post\_text | string | No | Text content of the social media post. `null` when `post_type` is `Repost` and the company reshared without adding their own commentary. |
| post\_url | string | Yes | URL to the original social media post |
| post\_channel | string | Yes | Platform where posted (e.g., Twitter, LinkedIn) |
| post\_posted\_at | string | Yes | Best estimated date of posting (YYYY-MM-DD) |
| post\_type | string | Yes | Type of post (e.g., Post, Repost) |
### Example
This example includes [base webhook fields](/webhooks/overview) along with social-posts-specific content:
```json theme={null}
{
"update_id": "41991828-ed31-4bd9-98d2-44b2763f9f35",
"update_type": "radar_finding",
"record_id": "b77af154-c369-4446-b70a-f328880c3a48",
"discovered_at": "2024-03-22T15:30:00Z",
"data": {
"radar_id": "c70813b3-7e87-4ca7-90fd-8c64574d911b",
"radar_type": "company_social_posts",
"domain": "domain.com",
"next_charge_at": "2024-04-20T08:00:00Z",
"custom_fields": {
"priority": "high",
"account_id": "0011U00000TFV7MQAX"
}
},
"content": {
"post_text": "We're excited to announce our new partnership with @TechCorp! Together, we'll be revolutionizing the way companies handle their data analytics. Stay tuned for more updates! #Innovation #Partnership",
"post_url": "https://twitter.com/domain/status/1234567890",
"post_channel": "Twitter",
"post_posted_at": "2024-03-22",
"post_type": "Post"
}
}
```
# Company Social Posts - CXO
Source: https://docs.tamradar.com/webhooks/company/social-posts-cxo
Company Social Posts - CXO documentation for TAMradar API.
Notifies you when new social media posts are detected from executives (CXO level) of your tracked companies. This radar type helps you monitor leadership communication and strategic announcements.
### Content Structure
The `content` object contains the social media post information:
| Field | Type | Nullable | Description |
| ---------------- | ------ | -------- | ----------------------------------------------- |
| post\_text | string | No | Text content of the social media post |
| post\_url | string | No | URL to the original social media post |
| post\_channel | string | No | Platform where posted (e.g., Twitter, LinkedIn) |
| post\_posted\_at | string | No | Best estimated date of posting (YYYY-MM-DD) |
| post\_type | string | No | Type of post (e.g., Post, Repost) |
| author | object | No | Information about the executive who posted |
#### Author Object
| Field | Type | Nullable | Description |
| --------------- | ------ | -------- | ------------------------------------------------------------------------------ |
| first\_name | string | No | First name of the executive (may be the full profile name from some platforms) |
| last\_name | string | Yes | Last name of the executive (can be null if platform provides only full name) |
| profile\_url | string | No | URL to the executive's professional profile |
| title | string | Yes | Current job title (can be null if unavailable on certain platforms) |
| company\_name | string | Yes | Name of the company where the executive works |
| company\_domain | string | Yes | Domain of the company where the executive works |
| country | string | Yes | Country of the executive (can be null if location unavailable) |
### Example
This example includes [base webhook fields](/webhooks/overview) along with CXO-posts-specific content:
```json theme={null}
{
"update_id": "41991828-ed31-4bd9-98d2-44b2763f9f35",
"update_type": "radar_finding",
"record_id": "b77af154-c369-4446-b70a-f328880c3a48",
"discovered_at": "2024-03-21T09:45:00Z",
"data": {
"radar_id": "c70813b3-7e87-4ca7-90fd-8c64574d911b",
"radar_type": "company_social_posts_cxo",
"domain": "domain.com",
"next_charge_at": "2024-04-20T11:20:00Z",
"custom_fields": {
"priority": "high",
"account_id": "0011U00000TFV7MQAX"
}
},
"content": {
"post_text": "Thrilled to announce our latest AI breakthrough! Our team has developed a groundbreaking algorithm that will revolutionize how businesses handle data processing. This is just the beginning of our AI journey. #Innovation #AI #TechLeadership",
"post_url": "https://twitter.com/JohnDoeCEO/status/1234567890",
"post_channel": "Twitter",
"post_posted_at": "2024-03-21",
"post_type": "Post",
"author": {
"first_name": "John",
"last_name": "Doe",
"profile_url": "https://www.linkedin.com/in/johndoe/",
"title": "Chief Executive Officer",
"company_name": "Tech Innovations Inc",
"company_domain": "domain.com",
"country": "United States"
}
}
}
```
# Contact Job Changes
Source: https://docs.tamradar.com/webhooks/contact/job-changes
Contact Job Changes documentation for TAMradar API.
Notifies you when tracked individuals experience career changes such as job departures, promotions, additional roles, or moves to new companies. This radar type helps you monitor contact movements and relationship changes.
### Content Structure
The `content` object contains the job change information:
| Field | Type | Nullable | Description |
| -------------- | ------ | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
| change\_type | string | No | Type of change: "departure", "promotion", "additional\_role", "new\_company\_role" |
| profile\_url | string | No | LinkedIn profile URL of the contact for identification |
| new\_role | object | No | Details of the new role. Fields contain null values for 'departure' type changes. |
| previous\_role | object | No | Details of the previous role at the tracked domain. Fields contain null values for 'additional\_role' type changes and when no previous role exists. |
#### Change Types
| Type | Description |
| ------------------ | ------------------------------------------------------------------------------------------ |
| `departure` | Contact left the tracked company; no new role detected yet |
| `promotion` | Contact received a promotion or role change within the same company |
| `additional_role` | Contact gained an additional role (advisor, board member) while staying at tracked company |
| `new_company_role` | Contact left the tracked company and joined another company |
#### Change Type Examples
| Change Type | new\_role | previous\_role | Use Case |
| ------------------ | ------------------------------------- | ----------------------------------------- | ---------------------------------------- |
| `departure` | All fields are null | Populated (last role at tracked company) | Contact left your tracked company |
| `promotion` | Populated (new role at same company) | Populated (previous role at same company) | Title/role change within tracked company |
| `additional_role` | Populated (role at different company) | All fields are null | Added advisor/board role while staying |
| `new_company_role` | Populated (role at different company) | Populated (last role at tracked company) | Left tracked company for new position |
#### Role Object Structure
Both `new_role` and `previous_role` objects contain:
| Field | Type | Nullable | Description |
| --------------- | ------ | -------- | ------------------------------------------------ |
| title | string | Yes | Job title or position |
| company\_name | string | Yes | Company name |
| company\_domain | string | Yes | Company domain |
| start\_date | string | Yes | Role start date (YYYY-MM-DD) |
| end\_date | string | Yes | Role end date (YYYY-MM-DD), null if current role |
Both role objects are always present in the payload with a consistent structure.
**Structural nulls**: When a change type leaves an entire object empty (e.g. `departure` β all `new_role` fields null, `additional_role` β all `previous_role` fields null), this is indicated in the Change Type Examples table above.
**Data nulls**: Within a populated object, `end_date` is null when the role is current. `company_domain` may be null if the company has no public domain. Other fields are populated whenever the data is available from the source.
### Examples
These examples include [base webhook fields](/webhooks/overview) along with contact-job-change-specific content. Select a change type to see its payload shape.
```json new_company_role theme={null}
{
"update_id": "15c83d05-f81a-4b6c-a64d-e594b13d11a7",
"update_type": "radar_finding",
"record_id": "b8b9c213-4ed2-4b7f-8e46-7a1398d4e2fa",
"discovered_at": "2025-07-02T19:03:43Z",
"data": {
"radar_id": "4e8f0a7b-6c52-4a4b-b15c-3a7f9b5c1d9d",
"radar_type": "contact_job_changes",
"domain": "acme.com",
"next_charge_at": "2025-08-02T19:03:43Z",
"custom_fields": {
"contact_tier": "enterprise",
"account_id": "0011U00000ABCDEF"
},
"profile_url": "https://www.linkedin.com/in/jane-smith-marketing/",
"email": "jane.smith@acme.com",
"full_name": "Jane Smith"
},
"content": {
"change_type": "new_company_role",
"profile_url": "https://www.linkedin.com/in/jane-smith-marketing/",
"new_role": {
"title": "Director of Marketing Operations",
"company_name": "ExampleCorp",
"company_domain": "examplecorp.com",
"start_date": "2024-12-01",
"end_date": null
},
"previous_role": {
"title": "Senior Marketing Manager",
"company_name": "ACME Inc",
"company_domain": "acme.com",
"start_date": "2021-07-01",
"end_date": "2024-12-01"
}
}
}
```
```json departure theme={null}
{
"update_id": "a3c72f01-88d4-4e3b-b921-7d3f2a1c9e45",
"update_type": "radar_finding",
"record_id": "d4e5f678-1234-4abc-9def-0a1b2c3d4e5f",
"discovered_at": "2025-07-15T10:22:00Z",
"data": {
"radar_id": "4e8f0a7b-6c52-4a4b-b15c-3a7f9b5c1d9d",
"radar_type": "contact_job_changes",
"domain": "acme.com",
"next_charge_at": "2025-08-15T10:22:00Z",
"custom_fields": {
"contact_tier": "enterprise",
"account_id": "0011U00000ABCDEF"
},
"profile_url": "https://www.linkedin.com/in/jane-smith-marketing/",
"email": "jane.smith@acme.com",
"full_name": "Jane Smith"
},
"content": {
"change_type": "departure",
"profile_url": "https://www.linkedin.com/in/jane-smith-marketing/",
"new_role": {
"title": null,
"company_name": null,
"company_domain": null,
"start_date": null,
"end_date": null
},
"previous_role": {
"title": "Senior Marketing Manager",
"company_name": "ACME Inc",
"company_domain": "acme.com",
"start_date": "2021-07-01",
"end_date": "2025-07-01"
}
}
}
```
```json promotion theme={null}
{
"update_id": "f9e8d7c6-b5a4-4321-8765-1a2b3c4d5e6f",
"update_type": "radar_finding",
"record_id": "c1d2e3f4-5678-4abc-bdef-9a8b7c6d5e4f",
"discovered_at": "2025-06-01T08:15:00Z",
"data": {
"radar_id": "4e8f0a7b-6c52-4a4b-b15c-3a7f9b5c1d9d",
"radar_type": "contact_job_changes",
"domain": "acme.com",
"next_charge_at": "2025-07-01T08:15:00Z",
"custom_fields": {
"contact_tier": "enterprise",
"account_id": "0011U00000ABCDEF"
},
"profile_url": "https://www.linkedin.com/in/jane-smith-marketing/",
"email": "jane.smith@acme.com",
"full_name": "Jane Smith"
},
"content": {
"change_type": "promotion",
"profile_url": "https://www.linkedin.com/in/jane-smith-marketing/",
"new_role": {
"title": "Director of Marketing",
"company_name": "ACME Inc",
"company_domain": "acme.com",
"start_date": "2025-06-01",
"end_date": null
},
"previous_role": {
"title": "Senior Marketing Manager",
"company_name": "ACME Inc",
"company_domain": "acme.com",
"start_date": "2021-07-01",
"end_date": "2025-06-01"
}
}
}
```
```json additional_role theme={null}
{
"update_id": "b2c3d4e5-f6a7-4890-abcd-ef1234567890",
"update_type": "radar_finding",
"record_id": "e5f6a7b8-9012-4cde-f345-6789abcdef01",
"discovered_at": "2025-05-10T14:30:00Z",
"data": {
"radar_id": "4e8f0a7b-6c52-4a4b-b15c-3a7f9b5c1d9d",
"radar_type": "contact_job_changes",
"domain": "acme.com",
"next_charge_at": "2025-06-10T14:30:00Z",
"custom_fields": {
"contact_tier": "enterprise",
"account_id": "0011U00000ABCDEF"
},
"profile_url": "https://www.linkedin.com/in/jane-smith-marketing/",
"email": "jane.smith@acme.com",
"full_name": "Jane Smith"
},
"content": {
"change_type": "additional_role",
"profile_url": "https://www.linkedin.com/in/jane-smith-marketing/",
"new_role": {
"title": "Advisor",
"company_name": "TechVentures Fund",
"company_domain": "techventuresfund.com",
"start_date": "2025-05-01",
"end_date": null
},
"previous_role": {
"title": null,
"company_name": null,
"company_domain": null,
"start_date": null,
"end_date": null
}
}
}
```
### Important Notes
**Contact Identification**: Radar creation accepts an individual email, a LinkedIn person profile URL (`linkedin.com/in/...`), or a full name. Role-based/shared emails and company, school, or legacy `/pub/` LinkedIn URLs are rejected. Within the same company and radar type, duplicate matching uses email or the normalized LinkedIn profile URL; full name alone is not a duplicate key.
**Multiple Contacts**: You can track multiple individuals at the same company with separate radars.
**Contact Identifiers in Payload**: The `data` object echoes back the identifiers you supplied at radar creation (`profile_url`, `email`, `full_name`). Only the fields you originally provided are included β if you created the radar with a LinkedIn URL only, `email` and `full_name` will be absent from the payload.
**Tracking Period**: Changes are detected based on publicly available professional information.
**Previous Role Context**: The `previous_role` object contains the role at the company you originally tracked, not necessarily the most recent role the person held. For example, if you're tracking someone at Microsoft and they move to Meta, then later move to Google, the `previous_role` will show their Microsoft role (your tracked company), not their Meta role.
**Tracking Continuity**: If you do not want to track contacts at new companies that TAMradar identifies, you should deactivate the radar. For example, if you're tracking someone at Microsoft and they move to Meta, TAMradar will automatically start tracking their Meta role. Deactivate the radar if you only want to monitor the original company.
# Contact Social Engagements
Source: https://docs.tamradar.com/webhooks/contact/social-engagements
Contact Social Engagements documentation for TAMradar API.
Notifies you when people engage with posts from contacts you're tracking on social channels. This includes reactions (like, celebrate, support, etc.), comments, and reposts on the tracked contact's posts.
### Content Structure
The `content` object contains the engagement information:
| Field | Type | Required | Description |
| ------------------------ | ------ | -------- | ----------------------------------------------------------------------------- |
| post\_url | string | Yes | URL of the social post that received engagement |
| post\_content | string | Yes | Text content of the original post |
| post\_channel | string | Yes | Social media channel (always "linkedin") |
| post\_posted\_at | string | Yes | Date when the post was published (YYYY-MM-DD) |
| engagement\_type | string | Yes | Type of engagement: "reaction", "comment", or "repost" |
| engagement\_content | string | No | Content of the engagement (comment text or repost text). Null for reactions. |
| engagement\_content\_url | string | No | URL of the engagement content (for reposts). Null for reactions and comments. |
| author | object | Yes | Details of the person who engaged with the post |
#### Author Object Structure
The `author` object contains details about the person who engaged:
| Field | Type | Nullable | Description |
| --------------- | ------ | -------- | --------------------------------------------------------------- |
| profile\_type | string | Yes | Type of profile: "contact" or "company" |
| first\_name | string | No | First name of the person who engaged, or company name |
| last\_name | string | Yes | Last name of the person - `null` if `profile_type` is `company` |
| profile\_url | string | No | Social profile URL of the person who engaged |
| title | string | Yes | Job title or position |
| company\_name | string | Yes | Company name where they work |
| company\_domain | string | Yes | Company domain where they work |
| country | string | Yes | Country location |
### Example
This example includes [base webhook fields](/webhooks/overview) along with contact-social-engagement-specific content:
```json theme={null}
{
"update_id": "fe00b8e8-ae51-4e63-b9af-fc45dbf9517b",
"update_type": "radar_finding",
"record_id": "ae3929d4-d2c6-4008-a756-d5669c919451",
"discovered_at": "2025-10-11T23:20:16Z",
"data": {
"radar_id": "05c4db36-3d28-4475-a4e8-52e0fcf58477",
"radar_type": "contact_social_engagements",
"domain": null,
"next_charge_at": "2025-11-10T23:21:41Z",
"custom_fields": {
"account_manager": "sarah.smith",
"priority": "high"
}
},
"content": {
"post_url": "https://www.linkedin.com/posts/someone_activity-123456789",
"post_content": "Just launched our new AI product! #AI #ProductLaunch",
"post_channel": "LinkedIn",
"post_posted_at": "2025-10-10",
"engagement_type": "Reaction",
"engagement_content": null,
"engagement_content_url": null,
"author": {
"profile_type": "contact",
"first_name": "John",
"last_name": "Smith",
"profile_url": "https://www.linkedin.com/in/johnsmith",
"title": "VP of Engineering",
"company_name": "Tech Corp",
"company_domain": "techcorp.com",
"country": "United States"
}
}
}
```
### Use Cases
**Sales Intelligence**\
When a VP or C-level executive from a target account engages with your tracked contact's post about industry trends, this signals interest and creates a warm introduction opportunity. The engagement data provides context for outreach - you know what content resonated with them.
**Relationship Mapping**\
By tracking who engages with your key contacts' posts, you can identify hidden connections and influencers within target organizations. If multiple people from the same company engage with content, it reveals the internal network and decision-making structure.
**Engagement Pattern Analysis**\
Track which types of content (product launches, thought leadership, company updates) generate the most engagement from valuable contacts. This helps understand what messaging resonates with your target audience and when they're most active.
**Warm Lead Identification**\
When someone consistently engages with your tracked contact's content through comments and reactions, they're demonstrating active interest. These engaged individuals become warm leads for outreach, as they're already familiar with your contact's expertise and perspectives.
### Technical Notes
* **Post Monitoring Window**: We monitor each post for up to 5 days, sending webhooks only for net-new activity detected during that window
* **Incremental Updates**: We continuously check for new engagements during the monitoring window
* **Activity Cutoff**: We only surface engagements on posts published after your `updates_since` timestamp, or after radar creation if `updates_since` wasn't set. To receive historic or backdated activity, set `updates_since` to the earliest date you care about.
# Contact Social Posts
Source: https://docs.tamradar.com/webhooks/contact/social-posts
Contact Social Posts documentation for TAMradar API.
Notifies you when new social media posts are published BY specific contacts (individuals) you're tracking. This radar type helps you monitor personal communication and updates from key contacts at target companies or industry professionals.
**Key difference**: This radar tracks posts authored BY your contacts, while company social radars track posts ABOUT companies.
### Content Structure
The `content` object contains the social media post information:
| Field | Type | Nullable | Description |
| ---------------- | ------ | -------- | --------------------------------------------------- |
| post\_text | string | No | Text content of the social media post |
| post\_url | string | No | URL to the original social media post |
| post\_channel | string | No | Platform where posted (e.g., "LinkedIn", "Twitter") |
| post\_posted\_at | string | No | Best estimated date of posting (YYYY-MM-DD) |
| post\_type | string | No | Type of post (e.g., Post, Repost) |
| author | object | No | Information about the contact who posted |
#### Author Object
| Field | Type | Nullable | Description |
| --------------- | ------ | -------- | ---------------------------------------------------------------------------- |
| first\_name | string | No | First name of the contact (may be the full profile name from some platforms) |
| last\_name | string | Yes | Last name of the contact (can be null if platform provides only full name) |
| profile\_url | string | No | URL to the contact's professional profile |
| title | string | Yes | Current job title (can be null if unavailable on certain platforms) |
| company\_name | string | Yes | Name of the company where the contact works |
| company\_domain | string | Yes | Domain of the company where the contact works |
| country | string | Yes | Country of the contact (can be null if location unavailable) |
### Example
This example includes [base webhook fields](/webhooks/overview) along with contact-social-posts-specific content:
```json theme={null}
{
"update_id": "41991828-ed31-4bd9-98d2-44b2763f9f35",
"update_type": "radar_finding",
"record_id": "b77af154-c369-4446-b70a-f328880c3a48",
"discovered_at": "2024-03-21T09:45:00Z",
"data": {
"radar_id": "c70813b3-7e87-4ca7-90fd-8c64574d911b",
"radar_type": "contact_social_posts",
"domain": "stripe.com",
"next_charge_at": "2024-04-20T11:20:00Z",
"custom_fields": {
"priority": "high",
"account_id": "0011U00000TFV7MQAX"
},
"profile_url": "https://www.linkedin.com/in/sarahjohnson/"
},
"content": {
"post_text": "Just wrapped up an amazing quarter! Our team's dedication to innovation continues to drive incredible results. Excited to share what we're building next. #TeamWork #Innovation #Growth",
"post_url": "https://www.linkedin.com/feed/update/activity:1234567890123456789/",
"post_channel": "LinkedIn",
"post_posted_at": "2024-03-21",
"post_type": "Post",
"author": {
"first_name": "Sarah",
"last_name": "Johnson",
"profile_url": "https://www.linkedin.com/in/sarahjohnson/",
"title": "VP of Engineering",
"company_name": "Stripe",
"company_domain": "stripe.com",
"country": "United States"
}
}
}
```
# Failure Webhook Payload Example
Source: https://docs.tamradar.com/webhooks/failure
Failure Webhook Payload Example documentation for TAMradar API.
```json theme={null}
{
"update_id": "550e8400-e29b-41d4-a716-446655440000",
"update_type": "radar_failure",
"record_id": null,
"discovered_at": "2024-03-20T12:30:00Z",
"data": {
"radar_id": "54e32291-25a4-459f-8bd6-4f79489ea931",
"radar_type": "company_job_openings",
"domain": "example.com",
"next_charge_at": null,
"custom_fields": {
"priority": "high"
}
},
"status": "error",
"code": 400,
"message": "Radar creation failed: missing prerequisites.",
"errors": [
{
"field": "radar",
"reason": "missing_prerequisites"
}
],
"refund_amount_usd": 0.1,
"completed_at": "2024-03-20T12:30:00Z",
"timestamp": "2024-03-20T12:30:00Z",
"error_id": "550e8400-e29b-41d4-a716-446655440000"
}
```
# Industry Funding Rounds
Source: https://docs.tamradar.com/webhooks/industry/funding-rounds
Industry Funding Rounds documentation for TAMradar API.
The Industry Funding Rounds radar tracks venture capital and private equity investments globally. Unlike company-specific radars, this monitors ALL funding activity and delivers it to your webhook endpoint. You can activate a `industry_funding_rounds`radar via the industry radar [endpoint](/api-reference/post-radars-industry).
### Content Structure
The `content` object for industry funding rounds contains:
| Field | Type | Required | Nullable | Description |
| ------------- | ------ | -------- | -------- | ------------------------------------------- |
| amount\_usd | number | Yes | No | Funding amount in USD |
| announced\_at | string | Yes | No | Date announced (YYYY-MM-DD format) |
| round | string | Yes | No | Funding round type (see Round Types below) |
| company | object | Yes | No | Information about the company raising funds |
| references | array | Yes | No | Source URLs for the funding announcement |
#### Company Object
| Field | Type | Required | Nullable | Description |
| ------------ | ------ | -------- | -------- | ---------------------------- |
| name | string | Yes | No | Company name |
| domain | string | Yes | No | Company website domain |
| profile\_url | string | No | Yes | Company social media profile |
| hq\_country | string | No | Yes | Company headquarters country |
#### Round Types
The `round` field can contain one of the following values:
* `Pre-seed`
* `Seed`
* `Series A`
* `Series B`
* `Series C`
* `Series D`
* `Series E`
* `Series F`
* `Series G`
* `Bridge`
* `Bridge Financing`
* `Debt Financing`
* `Growth Financing`
* `Unknown`
* `Crowdfunding`
* `Initial Public Offering`
* `Initial Coin Offering`
* `Private Equity`
### Example
This example includes base webhook fields along with industry-funding-rounds-specific content.
```json theme={null}
{
"update_id": "123e4567-e89b-12d3-a456-426614174000",
"update_type": "radar_finding",
"record_id": "456e7890-e89b-12d3-a456-426614174001",
"discovered_at": "2025-09-29T14:30:00Z",
"data": {
"radar_id": "789e0123-e89b-12d3-a456-426614174002",
"radar_type": "industry_funding_rounds",
"domain": null,
"next_charge_at": "2025-10-29T14:30:00Z",
"custom_fields": {
"client_ref": "ACME-001",
"region": "north-america"
}
},
"content": {
"amount_usd": 50000000,
"announced_at": "2025-09-28",
"round": "Series B",
"company": {
"name": "TechStartup Inc",
"domain": "techstartup.com",
"profile_url": "https://linkedin.com/company/techstartup-inc",
"hq_country": "United States"
},
"references": [
"https://techcrunch.com/2025/09/28/techstartup-raises-50m-series-b",
"https://venturebeat.com/2025/09/28/techstartup-funding"
]
}
}
```
# Industry Job Openings
Source: https://docs.tamradar.com/webhooks/industry/job-openings
Industry Job Openings documentation for TAMradar API.
Notifies you when job postings matching your keyword and location filters are discovered across the job market. This radar type helps you identify companies using certain technologies (e.g. Ramp), working on specific projects (e.g. cost cutting), or track hiring patterns based on job titles (e.g. GTM engineer).
**Breaking change:** The `department` field (string) has been renamed to `departments` (string array). Update your integration to read `departments` as an array. The old `department` field is no longer present in the payload.
### Content Structure
The `content` object contains the job opening and company information:
| Field | Type | Required | Description |
| ------------ | ------ | -------- | ---------------------------------------- |
| job\_opening | object | Yes | Details about the discovered job posting |
| company | object | Yes | Information about the hiring company |
#### Job Opening Object Structure
| Field | Type | Required | Nullable | Description |
| ----------- | --------- | -------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| title | string | Yes | No | Job title or position name |
| url | string | Yes | No | Direct URL to the job posting |
| posted\_at | string | Yes | Yes | Date when job was posted (YYYY-MM-DD format), null when the exact job posting date cannot be reliably determined |
| description | string | Yes | No | Full job description text |
| source | string | Yes | No | Platform where job was found (e.g., "LinkedIn", "Indeed") |
| country | string | Yes | Yes | Country where job is located, null when location cannot be determined |
| seniority | string | Yes | No | Seniority level of the role. One of: `Owner`, `CXO`, `Vice President`, `Director`, `Manager`, `Senior`, `Entry`, `Training`, `Partner`, `Other` |
| departments | string\[] | Yes | No | Department categories for the role (1β3 values). Values: `Accounting`, `Administrative`, `Business Development`, `Consulting`, `Customer Success`, `Design`, `Education`, `Engineering`, `Finance`, `Human Resources`, `Information Technology`, `Leadership`, `Legal`, `Manufacturing`, `Marketing`, `Media and Communication`, `Operations`, `Product Management`, `Project Management`, `Purchasing`, `Quality Assurance`, `Real Estate`, `Research`, `Sales`, `Support`, `Other` |
#### Company Object Structure
| Field | Type | Required | Description |
| ------------ | ------ | -------- | -------------------------------------------------------------------------------------------------------------- |
| name | string | Yes | Company name |
| domain | string | Yes | Company website domain |
| hq\_country | string | No | Company headquarters country |
| profile\_url | string | No | LinkedIn company profile URL |
| size | string | No | Company size range: "1", "2-10", "11-50", "51-200", "201-500", "501-1000", "1001-5000", "5001-10000", "10001+" |
| industry | string | No | Company industry category |
### Filtering Behavior
* **Keyword Matching**: Jobs are filtered by the keyword appearing in the job title or description
* **Location Filtering**: When countries are specified, only jobs in those locations are included
* **Deduplication**: Same jobs found on different platforms or with slight variations are automatically deduplicated
* **Freshness**: Jobs older than 2 days are still processed but marked with their original posting date
### Breaking Change: `department` β `departments`
The `department` field (scalar string) has been renamed to `departments` (array). Update your integration accordingly.
**Before (old contract):**
```json theme={null}
{
"content": {
"job_opening": {
"title": "Senior Growth Marketing Manager",
"url": "https://www.acme.com/jobs/123456789",
"posted_at": "2024-03-20",
"description": "...",
"source": "Company Website",
"country": "United States",
"department": "Marketing",
"seniority": "Senior"
}
}
}
```
**After (new contract):**
```json theme={null}
{
"content": {
"job_opening": {
"title": "Senior Growth Marketing Manager",
"url": "https://www.acme.com/jobs/123456789",
"posted_at": "2024-03-20",
"description": "...",
"source": "Company Website",
"country": "United States",
"seniority": "Senior",
"departments": ["Marketing"]
}
}
}
```
Key changes:
* `department` (string) β `departments` (string\[]) β always an array, 1β3 values
* `seniority` is now always required and non-null
### Example
This example includes [base webhook fields](/webhooks/overview) along with industry-job-openings-specific content:
```json theme={null}
{
"update_id": "7f2f1b8e-2c3d-4e91-b6a5-9f0d1a2b3c4d",
"update_type": "radar_finding",
"record_id": "3a9b1b0e-8d3a-4f0b-9237-1f2c3d4e5f6a",
"discovered_at": "2024-03-19T10:15:30Z",
"data": {
"radar_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"radar_type": "industry_job_openings",
"keyword": "growth marketing",
"countries": ["United States", "Canada"],
"domain": null,
"next_charge_at": "2024-04-20T12:30:00Z",
"custom_fields": {
"campaign_id": "q4-2024-hiring",
"priority": "high"
}
},
"content": {
"job_opening": {
"title": "Senior Growth Marketing Manager",
"url": "https://www.acme.com/jobs/123456789",
"posted_at": "2024-03-20",
"description": "We are seeking a Senior Growth Marketing Manager to drive user acquisition and retention strategies for our SaaS platform.",
"source": "Company Website",
"country": "United States",
"seniority": "Senior",
"departments": ["Marketing"]
},
"company": {
"name": "Acme Inc",
"domain": "acme.com",
"hq_country": "United States",
"profile_url": "https://linkedin.com/company/acme",
"size": "201-500",
"industry": "Computer Software"
}
}
}
```
### Use Cases
* **Technology Adoption Tracking**: Monitor companies hiring for specific technologies ("React developer", "Salesforce admin", "Snowflake engineer") to identify potential customers
* **Project Signal Detection**: Discover companies working on specific initiatives through job descriptions ("cost optimization", "digital transformation", "AI implementation")
* **Emerging Role Monitoring**: Track new or specialized roles that indicate market trends ("GTM engineer", "Developer Relations", "Revenue Operations")
* **Sales Prospecting**: Find companies hiring roles that indicate budget/growth/pain points relevant to your solution
### Important Notes
* **Company Domain Identification**: Company website domains are identified through best-effort matching and enrichment. In some cases, the domain may be estimated or unavailable, particularly for newer companies or those without strong online presence
* **Data Timing**: Job posting dates are sourced from the platform when available, or estimated based on discovery timing when not provided by the source. Rest assured these are recent postings - we focus on fresh job opportunities and share the most accurate timing information availablewa
# Industry Mentions
Source: https://docs.tamradar.com/webhooks/industry/mentions
Industry Mentions documentation for TAMradar API.
### Content Structure
The `content` object for industry mentions contains:
| Field | Type | Nullable | Description |
| ------------------- | ------ | -------- | -------------------------------------------------- |
| mention\_content | string | No | Content of the post or article |
| mention\_url | string | No | URL to the original post or article |
| mention\_channel | string | No | Platform or source (e.g., Twitter, LinkedIn, News) |
| mention\_posted\_at | string | Yes | Best estimated date of the mention (YYYY-MM-DD) |
| author | object | Yes | Information about the author of the mention |
#### Author Object
| Field | Type | Nullable | Description |
| --------------- | ------ | -------- | --------------------------- |
| first\_name | string | No | Author's first name |
| last\_name | string | Yes | Author's last name |
| profile\_url | string | No | URL to the author's profile |
| title | string | Yes | Author's current title |
| company\_domain | string | Yes | Author's company domain |
| company\_name | string | Yes | Author's company name |
| country | string | Yes | Author's country |
### Example
This example includes base webhook fields along with industry-mentions-specific content.
```json theme={null}
{
"update_id": "7f2f1b8e-2c3d-4e91-b6a5-9f0d1a2b3c4d",
"update_type": "radar_finding",
"record_id": "3a9b1b0e-8d3a-4f0b-9237-1f2c3d4e5f6a",
"discovered_at": "2024-03-19T10:15:30Z",
"data": {
"radar_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"radar_type": "industry_mentions",
"keyword": "artificial intelligence",
"domain": null,
"next_charge_at": "2024-04-20T12:30:00Z",
"custom_fields": {
"priority": "high",
"campaign": "ai-trends"
}
},
"content": {
"mention_content": "The latest advances in artificial intelligence are transforming enterprise software, particularly in areas of automation and predictive analytics.",
"mention_url": "https://www.linkedin.com/posts/joedoe-top-7360626072800903169-s4NE",
"mention_channel": "LinkedIn",
"mention_posted_at": "2024-03-18",
"author": {
"first_name": "Joe",
"last_name": "Doe",
"profile_url": "https://www.linkedin.com/in/joedoe",
"title": "Director of Sales",
"company_domain": "abc.com",
"company_name": "ABC Corporation",
"country": "United States"
}
}
}
```
# Industry New Companies
Source: https://docs.tamradar.com/webhooks/industry/new-companies
Industry New Companies documentation for TAMradar API.
The Industry New Companies radar tracks newly launched startups and entrepreneurs coming out of stealth mode globally. Unlike company-specific radars, this monitors ALL new company formations and delivers them to your webhook endpoint. You can activate an `industry_new_companies` radar via the industry radar [endpoint](/api-reference/post-radars-industry).
### Content Structure
The `content` object for industry new companies contains:
| Field | Type | Required | Description |
| ------- | ------ | -------- | -------------------------------------------- |
| person | object | Yes | Information about the founder/entrepreneur |
| company | object | Yes | Information about the newly launched company |
#### Person Object
| Field | Type | Required | Description |
| ------------------- | ------- | -------- | ---------------------------------------------------------- |
| first\_name | string | Yes | Founder's first name |
| last\_name | string | Yes | Founder's last name |
| about | string | No | Brief description or bio of the founder |
| profile\_url | string | Yes | URL to founder's professional profile |
| country | string | No | Founder's country |
| ex\_founder | boolean | Yes | Whether this person was a founder before |
| became\_founder\_at | string | Yes | Date when they became founder of this company (YYYY-MM-DD) |
| roles | array | Yes | Array of role objects (current and previous) |
#### Role Object
| Field | Type | Required | Description |
| --------------- | ------- | -------- | -------------------------------- |
| title | string | Yes | Job title |
| location | string | No | Job location |
| company\_name | string | No | Company name |
| company\_domain | string | No | Company website domain |
| start\_date | string | No | Role start date (YYYY-MM-DD) |
| end\_date | string | No | Role end date (YYYY-MM-DD) |
| is\_current | boolean | Yes | Whether this is the current role |
| description | string | No | Role description |
#### Company Object
| Field | Type | Required | Description |
| ------------- | ------- | -------- | ---------------------------------------- |
| name | string | Yes | Company name |
| domain | string | No | Company website domain |
| profile\_url | string | No | Company social media profile |
| founded\_at | number | No | Year company was founded |
| description | string | No | Company description |
| industry | string | No | Industry category |
| categories | array | No | Array of business category strings |
| hq\_country | string | No | Company headquarters country |
| stealth\_mode | boolean | Yes | Whether company is still in stealth mode |
### Example
This example includes base webhook fields along with industry-new-companies-specific content.
```json theme={null}
{
"update_id": "123e4567-e89b-12d3-a456-426614174000",
"update_type": "radar_finding",
"record_id": "456e7890-e89b-12d3-a456-426614174001",
"discovered_at": "2025-09-29T14:30:00Z",
"data": {
"radar_id": "789e0123-e89b-12d3-a456-426614174002",
"radar_type": "industry_new_companies",
"domain": null,
"next_charge_at": "2025-10-29T14:30:00Z",
"custom_fields": {
"client_ref": "ACME-001",
"region": "north-america"
}
},
"content": {
"person": {
"first_name": "Sarah",
"last_name": "Chen",
"about": "Former Google ML engineer building the future of autonomous logistics",
"profile_url": "https://linkedin.com/in/sarahchen",
"country": "United States",
"ex_founder": false,
"became_founder_at": "2025-09-15",
"roles": [
{
"title": "Founder & CEO",
"location": null,
"company_name": "LogiFlow AI",
"company_domain": "logiflow.ai",
"start_date": "2025-09-15",
"end_date": null,
"is_current": true,
"description": "Building AI-powered autonomous logistics platform"
},
{
"title": "Senior ML Engineer",
"location": "Mountain View, CA",
"company_name": "Google",
"company_domain": "google.com",
"start_date": "2020-01-01",
"end_date": "2025-08-31",
"is_current": false,
"description": "Led machine learning initiatives for search algorithms"
}
]
},
"company": {
"name": "LogiFlow AI",
"domain": "logiflow.ai",
"profile_url": "https://linkedin.com/company/logiflow-ai",
"founded_at": 2025,
"description": "AI-powered autonomous logistics platform for last-mile delivery",
"industry": "Computer Software",
"categories": ["B2B", "SaaS", "AI/ML"],
"hq_country": "United States",
"stealth_mode": false
}
}
}
```
## Stealth Mode Behavior
When `stealth_mode: true`, company data is intentionally limited as the startup is hiding details:
**Typically null:**
* `domain` - No public website yet
* `profile_url` - No LinkedIn company page
* `description` - Not revealing business details
* `industry` - Keeping sector secret
* `categories` - Empty array `[]`, no classification
**Still available:**
* `name` - Some company name (may be generic)
* `founded_at` - Incorporation year
* `hq_country` - Legal incorporation location
**Founder data remains complete** since it comes from public professional profiles, not company disclosure.
# Webhook Payloads
Source: https://docs.tamradar.com/webhooks/overview
Webhook delivery model, payload envelope, update types, and testing.
## How TAMradar delivers updates
TAMradar supports two ways to receive radar findings:
* **Webhooks (push)** β TAMradar POSTs events to your `webhook_url` in real-time. Set `webhook_url` when creating a radar to enable this.
* **Polling (pull)** β Fetch findings on demand via `GET /v1/updates`. Works for all radars, including those created without a `webhook_url`.
Both delivery methods use the same payload format documented below.
## Payload Libraries by Category
### Company Payloads
* [Company Mentions](/webhooks/company/mentions)
* [Company Social Posts](/webhooks/company/social-posts)
* [Company Social Posts (CXO)](/webhooks/company/social-posts-cxo)
* [Company Social Engagements](/webhooks/company/social-engagements)
* [Company Job Openings](/webhooks/company/job-openings)
* [Company New Hires](/webhooks/company/new-hires)
* [Company Promotions](/webhooks/company/promotions)
* [Company Reviews](/webhooks/company/reviews)
### Contact Payloads
* [Contact Job Changes](/webhooks/contact/job-changes)
* [Contact Social Posts](/webhooks/contact/social-posts)
* [Contact Social Engagements](/webhooks/contact/social-engagements)
### Industry Payloads
* [Industry Mentions](/webhooks/industry/mentions)
* [Industry Funding Rounds](/webhooks/industry/funding-rounds)
* [Industry New Companies](/webhooks/industry/new-companies)
* [Industry Job Openings](/webhooks/industry/job-openings)
## Setting Up Webhook Delivery (optional)
If you want real-time push delivery, set up an HTTPS endpoint:
1. Create an HTTPS endpoint on your server that can receive POST requests
2. Pass `webhook_url` when creating your radar
3. Ensure your endpoint returns a 2xx status code to acknowledge receipt
> **No webhook?** Skip `webhook_url` entirely to create a poll-only radar. Findings are always available via `GET /v1/updates` regardless.
## Webhook Delivery & Reliability
### Timeouts
TAMradar waits **10 seconds** for your endpoint to respond. If no response is received within that window, the attempt is treated as a failure and retried.
### Retries & backoff
If your endpoint returns a non-2xx status (including 5xx) or times out, TAMradar retries up to **3 times** with exponential backoff:
| Attempt | Delay before retry |
| --------- | ------------------ |
| 1st retry | 1 minute |
| 2nd retry | 5 minutes |
| 3rd retry | 15 minutes |
After 4 total attempts (1 initial + 3 retries), delivery is permanently abandoned.
### Data is never lost
Push delivery failure does not mean data loss. Every event is written to our internal store before delivery is attempted β findings are always retrievable via `GET /v1/updates`, regardless of webhook delivery outcome. Use polling as a fallback if your endpoint experiences downtime.
### Deduplication
On retry, the same `update_id` is resent. Use `update_id` as your idempotency key to deduplicate events safely.
## Webhook Payload Format
All webhook payloads follow this structure:
```json theme={null}
{
"update_id": "550e8400-e29b-41d4-a716-446655440000",
"update_type": "radar_finding",
"record_id": "c4d5e6f7-a8b9-4c0d-b1e2-f3a4b5c6d7e8",
"discovered_at": "2024-03-20T12:30:00Z",
"data": {
"radar_id": "54e32291-25a4-459f-8bd6-4f79489ea931",
"radar_type": "company_job_openings",
"domain": "example.com",
"custom_fields": {
"priority": "high",
"target_account_id": "0011U00000TFV7MQAX"
}
},
"content": {
// Event-specific data
}
}
```
### Base Fields
| Field | Type | Required | Description |
| -------------- | ------ | -------- | ------------------------------------------------ |
| update\_id | string | Yes | Unique identifier for this webhook update |
| update\_type | string | Yes | Type of update (see Update Types below) |
| record\_id | string | No | Unique identifier for the detected record |
| radar\_type | string | Yes | Type of radar that generated the event |
| discovered\_at | string | Yes | ISO timestamp (UTC) when the update was detected |
### Update Types
| Update Type | Description |
| ---------------- | ------------------------------------------------------------ |
| `radar_finding` | New data detected by a radar (default) |
| `radar_failure` | Radar setup or billing failure (may include refund metadata) |
| `radar_created` | Async bulk item was created successfully |
| `bulk_completed` | Async bulk request finished processing all items |
### Per-item bulk webhook events
Before the final `bulk_completed`, each item delivers its own webhook as it finishes:
* **`radar_created`** β item was created successfully. Includes `code: 201`, `message: "Radar created successfully"`, `completed_at`, and a full `data` block matching the single-radar creation response.
* **`radar_failure`** β item failed validation, conflict, or billing. Includes `completed_at`, `code`, `errors[]`, and `message` matching the sync API error shape. Also emits `discovered_at` (same value as `completed_at`) for backwards compatibility β prefer `completed_at` going forward.
`radar_created` example:
```json theme={null}
{
"update_id": "efbf7c77-c1ae-4a0d-85b6-116674e9e0b1",
"update_type": "radar_created",
"record_id": null,
"code": 201,
"message": "Radar created successfully",
"completed_at": "2026-05-25T11:17:20Z",
"bulk_id": "5f9da22e-4778-427c-b7e4-dab2a8d9d709",
"item_index": 0,
"custom_fields": { "crm_account_id": "acc_001" },
"status": "success",
"data": {
"radar_id": "2ffb1a07-ac95-4a6d-bc34-39f479f1c856",
"radar_type": "company_job_openings",
"domain": "stripe.com",
"radar_status": "active",
"webhook_url": "https://your-domain.com/webhooks/tamradar",
"created_at": "2026-05-25T11:17:18Z",
"updates_since": null,
"deactivated_at": null,
"next_charge_at": "2026-06-25T11:17:18Z",
"custom_fields": { "crm_account_id": "acc_001" }
}
}
```
`radar_failure` example:
```json theme={null}
{
"update_id": "a1b2c3d4-0001-4000-b000-000000000001",
"update_type": "radar_failure",
"record_id": null,
"completed_at": "2026-05-25T11:17:26Z",
"discovered_at": "2026-05-25T11:17:26Z",
"bulk_id": "5f9da22e-4778-427c-b7e4-dab2a8d9d709",
"item_index": 1,
"custom_fields": {},
"status": "error",
"code": 409,
"message": "Conflict: Radar already exists",
"errors": [
{
"field": "domain",
"reason": "A radar with this domain and type already exists for your account. Conflicting radar_id: 3f1d8e8a-39c4-47e8-a4b4-f7db6a2e50f0. You can view it using GET /radars/{radar_id}",
"conflicting_radar_id": "3f1d8e8a-39c4-47e8-a4b4-f7db6a2e50f0"
}
],
"refund_amount_usd": 0,
"error_id": "45ed35e5-986c-4496-afd8-9a84f6baa410",
"timestamp": "2026-05-25T11:17:26.000Z"
}
```
### Bulk `update_id` semantics
For `bulk_completed` events, two ID levels can appear and they serve different purposes:
* Top-level `update_id`: idempotency key for the whole `bulk_completed` webhook event.
* Per-item `radars[].update_id`: correlation key for each item's earlier `radar_created` or `radar_failure` event.
Store both when you consume bulk webhooks:
* Deduplicate `bulk_completed` retries by top-level `update_id`.
* Correlate each `radars[]` item back to its individual item event with `radars[].update_id`.
`bulk_completed` payloads use a batch envelope with `completed_at`, `summary`, and `radars[]`. Each entry in `radars[]` is the verbatim payload from that item's per-item webhook:
```json theme={null}
{
"update_id": "9a2f0f77-5c8b-4b3e-b636-3ad539f4de65",
"update_type": "bulk_completed",
"bulk_id": "5f9da22e-4778-427c-b7e4-dab2a8d9d709",
"completed_at": "2026-05-25T11:17:33Z",
"summary": {
"total": 3,
"created": 2,
"failed": 1
},
"radars": [
{
"update_id": "efbf7c77-c1ae-4a0d-85b6-116674e9e0b1",
"update_type": "radar_created",
"code": 201,
"message": "Radar created successfully",
"completed_at": "2026-05-25T11:17:20Z",
"bulk_id": "5f9da22e-4778-427c-b7e4-dab2a8d9d709",
"item_index": 0,
"custom_fields": { "crm_account_id": "acc_001" },
"status": "success",
"data": { "radar_id": "2ffb1a07-ac95-4a6d-bc34-39f479f1c856" }
},
{
"update_id": "a1b2c3d4-0001-4000-b000-000000000001",
"update_type": "radar_failure",
"record_id": null,
"completed_at": "2026-05-25T11:17:26Z",
"discovered_at": "2026-05-25T11:17:26Z",
"bulk_id": "5f9da22e-4778-427c-b7e4-dab2a8d9d709",
"item_index": 1,
"custom_fields": {},
"status": "error",
"code": 409,
"message": "Conflict: Radar already exists",
"errors": [
{
"field": "domain",
"reason": "A radar with this domain and type already exists for your account. Conflicting radar_id: 3f1d8e8a-39c4-47e8-a4b4-f7db6a2e50f0.",
"conflicting_radar_id": "3f1d8e8a-39c4-47e8-a4b4-f7db6a2e50f0"
}
],
"refund_amount_usd": 0,
"error_id": "45ed35e5-986c-4496-afd8-9a84f6baa410",
"timestamp": "2026-05-25T11:17:26Z"
}
]
}
```
### Radar Types
| Radar Type | Description |
| ---------------------------- | ------------------------------------------------------------------------ |
| `company_mentions` | Social media and article mentions of your company |
| `company_social_posts` | Company's own social media activity |
| `company_social_posts_cxo` | Social media posts from company executives |
| `company_social_engagements` | Social media engagements (likes, comments) on company posts |
| `company_job_openings` | New job postings from tracked companies |
| `company_new_hires` | New employee detections |
| `company_promotions` | Employee promotions and role changes |
| `company_reviews` | Customer reviews across platforms |
| `contact_job_changes` | Individual contact career changes (departures, promotions, role changes) |
| `contact_social_posts` | Social media posts from tracked contacts |
| `contact_social_engagements` | Social media engagements (likes, comments) by tracked contacts |
| `industry_mentions` | Industry-wide mentions and news articles |
| `industry_funding_rounds` | Funding rounds across companies in your industry |
| `industry_job_openings` | Job openings across companies in your industry |
| `industry_new_companies` | New companies emerging in your industry |
## Radar Failure Notifications
When a radar fails setup or is deactivated due to billing, you'll receive a `radar_failure` notification:
```json theme={null}
{
"update_id": "550e8400-e29b-41d4-a716-446655440000",
"update_type": "radar_failure",
"record_id": null,
"discovered_at": "2024-03-20T12:30:00Z",
"completed_at": "2024-03-20T12:30:00Z",
"data": {
"radar_id": "54e32291-25a4-459f-8bd6-4f79489ea931",
"radar_type": "company_job_openings",
"domain": "example.com",
"custom_fields": {
"priority": "high"
}
},
"status": "error",
"code": 402,
"message": "Radar was deactivated due to insufficient credits.",
"errors": [{ "field": "radar", "reason": "insufficient_funds" }],
"refund_amount_usd": 0,
"timestamp": "2024-03-20T12:30:00Z",
"error_id": "550e8400-e29b-41d4-a716-446655440000"
}
```
When setup fails, the same `radar_failure` shape is used with a different `code` and refund value:
```json theme={null}
{
"update_id": "550e8400-e29b-41d4-a716-446655440001",
"update_type": "radar_failure",
"record_id": null,
"discovered_at": "2024-03-20T12:30:00Z",
"completed_at": "2024-03-20T12:30:00Z",
"data": {
"radar_id": "54e32291-25a4-459f-8bd6-4f79489ea931",
"radar_type": "company_social_posts",
"domain": "example.com",
"custom_fields": {
"priority": "high"
}
},
"status": "error",
"code": 400,
"message": "Radar creation failed because the domain does not have required social media accounts.",
"errors": [{ "field": "radar", "reason": "missing_prerequisites" }],
"refund_amount_usd": 0.10,
"timestamp": "2024-03-20T12:30:00Z",
"error_id": "550e8400-e29b-41d4-a716-446655440001"
}
```
### Failure Reasons
Identify the failure type from `code` and `message`. The `errors[].reason` field contains the machine-readable reason key.
| `code` | `errors[].reason` | Description |
| ------ | ----------------------- | ------------------------------------------------------------------------ |
| `400` | `missing_prerequisites` | Required accounts or data sources not found |
| `400` | `setup_failure` | General setup failure during radar initialization |
| `400` | `source_inaccessible` | Target source (e.g. LinkedIn profile, domain) is inaccessible or private |
| `402` | `insufficient_funds` | Radar deactivated β balance too low to cover the charge |
## Best Practices for Webhook Handling
1. **Respond Quickly**: Return a 2xx status code immediately, then process asynchronously
2. **Implement Retry Logic**: Be prepared to handle the same event multiple times
3. **Verify Signatures**: Check webhook signatures to validate authenticity (documentation coming soon)
4. **Track Failure Events**: Monitor `radar_failure` to handle billing/setup problems quickly
5. **Handle Null Fields**: Some fields like `record_id` may be null for certain event types
## Testing Your Webhook
### Quick test
Trigger a sample payload to your endpoint:
```bash theme={null}
curl -X POST https://api.tamradar.com/v1/webhooks/test \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"webhook_url": "https://your-domain.com/webhook",
"radar_type": "company_job_openings" // any supported radar_type
// "update_type": "radar_failure" // optional, defaults to radar_finding
}'
```
The test event is fire-and-forget, with no retries or signatures, and uses the same payload format you'll receive in production.
Need help? [Contact our team](mailto:support@tamradar.com)