Recipes
Tag and qualify companies
Tagging a company is one PATCH call. What matters is not how — it's what to tag with, and
what each tag earns you downstream. Qualification (label) is the same call with
three pre-baked values reserved for is this in target at all — from your side, a limited
tag. The simple cases are trivial. The interesting questions show up once you treat tags as a
system: anti-filters, lifecycle markers, sync state, owner routing. Here are the patterns we use.
tags or label — usually both. From the record's
side it is just metadata; from your queries it is the levers that suppress noise and route work.
Mental model
Two fields, one record, one PATCH. tags are free strings you invent. label is a tag with three legal values, reserved for in-target / not-in-target / new. They sit on
the same record and travel together.
| Field | Shape | Set via | Filter via |
|---|---|---|---|
tags | free strings, multi-value (CSV) | PATCH /companies | tags=, NOT_tags= |
label | enum: -1, 0, 1 | PATCH /companies (same call) | label= |
Basics
One endpoint, one record per call. Send what you want the record to look like — both fields are optional, but at least one is needed for the call to do anything.
/v1.3/companies | Parameter | Value | Why |
|---|---|---|
id | company id | Which company you are updating. Required. |
tags | CSV of tags | Replaces the current tag set. Case-sensitive, URL-encoded. |
label | 1, 0 or -1 | 1 = in target audience · 0 = not in target audience · -1 = new / not qualified. |
Response echoes the updated record. Treat it as your new source of truth — there is no second GET needed.
Common patterns
The combinations we reach for most often. Each pattern is one tag — the work is downstream, in queries that read the tag back.
| Pattern | Tag / label | Why |
|---|---|---|
| Customer suppression | tags=Customer | Mark paying customers, then NOT_tags=Customer in every prospect query. |
| Competitor / churn exclusion | tags=Competitor,Churned | Permanent disqualification — a one-time tag that earns lifetime exclusion. |
| Sync state | tags=Synced | Tag after pushing to CRM, then NOT_tags=Synced to find what is left. |
| Lifecycle stage | tags=Prospect | Demo | Closed-Won | Track sales-pipeline state on the record itself. Pick one of a fixed set per company. |
| Engagement score | tags=Hot | Warm | Cold | Lightweight scoring without a separate field. Re-tag when the signal changes. |
| Owner / territory routing | tags=Anna,EMEA | Route records by rep or region. Filter on a tag to see your slice. |
| ICP qualification | label=1 | Mark a fit (or non-fit). FirmTrace uses the signal to bias matching toward your kind of company. |
Anti-filter pattern
Most useful tags double as filters in two directions. Marking a record as Customer
lets you find customers (tags=Customer); it also lets you keep them out of
every other query (NOT_tags=Customer). The second use — the anti-filter — does
most of the real work in production pipelines.
The shape is always the same: tag what you want to keep out, then negate it on read.
| What you want | Filter |
|---|---|
| Records you have not pushed to your CRM yet | NOT_tags=Synced |
| Prospects only — no existing customers | NOT_tags=Customer |
| Permanent exclusion list | NOT_tags=Competitor,Churned,Blacklist |
| In-target, not-yet-customer, not-yet-synced | label=1 & NOT_tags=Customer,Synced |
Every tag you introduce becomes a future filter and anti-filter. A tag you set today is a query you do not have to write tomorrow. Cheap to add, expensive to retrofit — pick names you are willing to live with.
Considerations
| Point | Detail |
|---|---|
| PATCH replaces, not adds | Sending tags=Customer,VIP to a company already tagged Customer,Synced loses Synced. To add: read current tags, append, write back in one call. |
| Tags are case-sensitive | Customer ≠ customer. Pick a convention (we lean PascalCase) and document it somewhere central. |
| URL-encode tag values | Spaces, commas inside a single tag, and any special characters need encoding before they hit the URL. |
| label is exclusive | A company has exactly one label at a time, picked from {-1, 0, 1}. There is no multi-value form. |
| label is a signal back to us | Tags are just yours. label=1/0 nudges FirmTrace matching toward (or away from) records that look like your in-target ones. |
| Invalid id → 403, not 404 | A non-existent or wrong-account company id returns 403 with no records updated. Validate ids upstream. |
Errors & retry
Sort responses into the usual three buckets.
| Bucket | Examples | What to do |
|---|---|---|
| Transient | network, 5xx,
429 |
Retry with exponential backoff (1s → 2s → 4s, capped). On
429, respect Retry-After if present.
|
| Permanent | 403 (invalid id), other 4xx | Log and stop for that record. Retrying an invalid id will not start working — fix upstream. |
| Success | 200 | Response echoes the updated record. Persist it locally — see callout below. |
PATCH is naturally idempotent on the same payload — two identical writes produce the same
end state. The risk is the read-modify-write race: two processes that both compute a new
tag set from the same starting point will overwrite each other. Serialize writes per company.id, or accept last-writer-wins.
See also Filter companies Poll for new and updated companies