Company Search
The Company Search endpoint powers the typeahead experience inside our Transact SDK and is the right primitive for building your own employer picker. This guide covers how to call it, how to interpret results that look like duplicates, and how to differentiate companies that support full authentication from those that only offer manual deposit guidance.
For a snapshot of every company available to your account (useful when building a local index), see Company List. For full metadata on a single company, see Company Details. Once you have a company's _id and want to drop the user straight into its login step, see the Deeplink to Login guide.
Making a search request
POST /company/search performs a fuzzy match on company names using the required query property. The most useful optional filters are:
scopes— product suite. Values:user-link,pay-link.product— restrict to companies that support a single product.products— restrict to companies that support all of the listed products.productandproductsare mutually exclusive; sending both is rejected.tags— include only companies with the listed tags (e.g.payroll-provider,gig-economy).excludedTags— exclude companies with the listed tags. Use this for the franchise pattern below. It matches on a company's stored tags, so it is not a reliable way to filtermanual-deposits— see Authentication vs. manual deposit guidance.franchiseParent— return only the franchise children of the given parent company_id. See Franchises.
async function searchForCompany(query) {
const response = await fetch('https://api.atomicfi.com/company/search', {
method: 'POST',
headers: {
'x-api-key': '<YOUR_API_KEY>',
'x-api-secret': '<YOUR_API_SECRET>',
'Content-Type': 'application/json'
},
body: JSON.stringify({
query,
product: 'deposit'
})
});
if (!response.ok) {
throw new Error(`API error: ${response.status} ${response.statusText}`);
}
const { data } = await response.json();
return data;
} Each item in the response data array includes _id, name, status, tags, branding, and a slim connector object with availableProducts and capabilities. Use _id for deeplinking; use tags and connector.availableProducts to branch your UI.
{
"data": [
{
"_id": "5d38f1e8512bbf71fb776015",
"name": "DoorDash",
"status": "operational",
"connector": {
"_id": "5d38f182512bbf0c06776013",
"availableProducts": ["deposit"],
"capabilities": {
"distributionTypes": ["total"]
}
},
"branding": {
"logo": {
"url": "https://cdn-public.atomicfi.com/logos/doordash.png",
"backgroundColor": "#F2F2F2"
},
"color": "#9460FE"
},
"tags": ["gig-economy"]
}
]
}Franchise networks
Searching for a brand like Sonic Drive-In, Taco Bell, or McDonald's will often return several companies with similar names. This is expected: large brands are modeled as a franchise network, where each operator has its own payroll setup and its own company record.
franchise-parent— the brand itself. Treat this as the canonical entry.franchise-child— an individual franchisee. Each child has its own_idand connector, and references the parent viafranchiseParent.
You have two recommended ways to present this in your UI:
Option A — Collapse children in the primary search
Exclude franchise children so the typeahead shows a single result per brand. When the user picks the parent, follow up with a secondary "Which location do you work at?" search to disambiguate.
await fetch('https://api.atomicfi.com/company/search', {
method: 'POST',
headers: { /* ... */ },
body: JSON.stringify({
query: 'sonic',
excludedTags: ['franchise-child']
})
});Option B — Dedicated franchise picker
This is the pattern used inside Transact. When a user selects a parent, route them to a separate screen that lists only the children of that parent. Pass the parent's _id as franchiseParent and the search is scoped to that brand's franchisees server-side — no client-side filtering required. query is still required, so send the brand name (or the user's own search text) alongside it.
// parentId is the _id of the company tagged `franchise-parent`
await fetch('https://api.atomicfi.com/company/search', {
method: 'POST',
headers: { /* ... */ },
body: JSON.stringify({
query: 'sonic',
franchiseParent: parentId
})
});Authentication vs. manual deposit guidance
Some companies in the catalog do not support programmatic authentication. Instead of a login flow, users receive manual instructions to update their direct deposit. These companies carry a manual-deposits tag in the search response.
Because the tag is derived from a company's manual-deposit configuration rather than stored on the company record, excludedTags: ['manual-deposits'] is not a reliable way to filter these out. excludedTags matches stored tags only. Filter on the response instead, or have the account setting turned off.
Branch your UI on the tag
Inspect tags on each result and render a different CTA (for example, "View instructions" instead of "Connect"). This is closest to what Transact does: manual companies stay in the search results, and selecting one routes the user into a manual instructions flow rather than a login.
function ctaFor(company) {
if (company.tags?.includes('manual-deposits')) {
return { label: 'View instructions', kind: 'manual' };
}
return { label: 'Connect', kind: 'auth' };
}If you would rather not surface them at all, drop them client-side after the request:
const results = await searchForCompany('acme');
// `excludedTags` will not do this for you — the tag is derived, not stored.
const connectable = results.filter(
(company) => !company.tags?.includes('manual-deposits')
);Inspect connector capabilities for finer control
Company Search returns a slim connector payload. When you need to know how a company authenticates — headless credentials vs. SSO redirect, which distribution actions are supported — call Company Details and read connector.capabilities:
headlessAuthentication— supports username/password login.ssoAuthentication— requires an SSO redirect.authenticationMethods— e.g.["standard-auth", "true-auth", "true-auth-desktop"].distributionActions/distributionTypes— what the connector can do once authenticated.
Building your own search index
If you want to host your own typeahead index instead of calling Company Search per keystroke, pull the full catalog from Company List and re-sync periodically. The semantics described above still apply on your side:
- The
_idfor a given employer is stable. The same employer can, however, appear as multiple distinct companies because of franchise modeling — do not assume one row per brand. - When you re-sync, treat
tagsas the source of truth for franchise handling, and usestatusto suppressunder-maintenanceordisabledcompanies from your surfaces. - Company List applies the same account-level manual-deposit rule as Company Search, so a locally built index inherits it. Re-sync after any account setting change.
- Use
connector.availableProducts(andcapabilities.supportedPaymentMethodsfor Pay Link) to pre-validate that an employer supports the product your user is trying to use.
Handling status
status can be operational, under-maintenance, or disabled. Company Search never returns disabled companies — they are excluded server-side — so in practice you only need to handle under-maintenance here. disabled can appear on Company List and Company Details.
Users cannot start an authentication flow against an under-maintenance or disabled company, so surface a disabled state or filter these results before presenting them.
Troubleshooting
- Unexpected duplicates — check the
tagsarray. If you seefranchise-child, the duplicates are intentional. ApplyexcludedTags: ['franchise-child']or build a franchise picker. - Search results that can't be authenticated — look for
manual-depositsintagsand render a manual-guidance CTA. Filtering withexcludedTagswill not remove them; see Authentication vs. manual deposit guidance. - Unauthorized — verify your
x-api-keyandx-api-secretheaders.