Overview
Alphie identifies companies that visit your website and captures or discovers contacts at those companies. The Public API gives you programmatic, read-only access to that data so you can keep your own systems — most commonly a CRM — continuously up to date, without any manual export or import step.
Two resources are available:
- Companies — organizations Alphie has identified visiting your site, along with firmographic data and a fit score against your ideal customer profile.
- Contacts — people captured by your Alphie chat widget and forms, plus people Alphie has discovered at companies it identified.
The API is designed to be polled on a schedule: do an initial backfill, then periodically ask for what's new or changed since your last successful sync. See Incremental Sync for a recommended approach.
This version of the API is read-only — it does not support creating, updating, or deleting data in Alphie.
Base URL
https://app.meetalphie.com/api/public/v1If your account is served from a dedicated Alphie host, use that host in place of app.meetalphie.com in every example in this document — the path (/api/public/v1/...) stays the same.
Authentication
Requests are authenticated with an API key, issued to you by your Alphie account administrator. API keys look like alph_ followed by 48 hexadecimal characters, for example:
alph_a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6The full key value is shown to your admin exactly once, at creation time. Alphie retains only a one-way hash of it and cannot display it again afterward, so store it securely (e.g. in a secrets manager) as soon as it's created.
Send the key on every request, using either of the following headers:
| Header | Example |
|---|---|
Authorization (primary) | Authorization: Bearer alph_a1b2c3d4e5f6… |
X-API-Key (alternative) | X-API-Key: alph_a1b2c3d4e5f6… |
curl -H "Authorization: Bearer alph_YOUR_KEY" \
"https://app.meetalphie.com/api/public/v1/companies"A few things to keep in mind:
- API keys are read-only in this version and are scoped to your Alphie account — a key only ever returns data belonging to the account it was created under.
- An admin can revoke a key at any time. Requests made with a revoked key receive
401 API key revoked(see Errors). Revocation is immediate and cannot be undone — issue a new key if you need to reconnect.
Rate Limits
The API allows 120 requests per minute. Every response includes headers describing your current standing against that limit:
| Header | Description |
|---|---|
X-RateLimit-Limit | The maximum number of requests allowed in the current window. |
X-RateLimit-Remaining | The number of requests you have left in the current window. |
X-RateLimit-Reset | When the current window resets. |
If you exceed the limit, the API responds with 429 Too Many Requests:
{
"error": "Too many requests",
"retryAfter": 30
}retryAfter is the number of seconds to wait before retrying. Back off for at least that long, and consider spacing out your requests (for example, pausing briefly between pages) rather than retrying immediately in a loop.
Pagination
Both list endpoints (GET /companies and GET /contacts) return results in a common envelope:
{
"data": [ ... ],
"page": 1,
"pageSize": 25,
"total": 214
}| Field | Type | Description |
|---|---|---|
data | array | The results for this page. |
page | number | The page number returned. |
pageSize | number | The number of records requested per page. |
total | number | The total number of records matching the query, across all pages. |
Both endpoints accept the following pagination parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
page | integer | 1 | Page number to retrieve. |
pageSize | integer | 25 | Number of records to return per page. Maximum 100. |
Ordering is stable across pages, so you can safely page from 1 through the last page and get a consistent, non-overlapping view of the result set, even as new data arrives in the background.
Note:pageandpageSizevalues outside their valid range (for example,pageSize=500orpage=0) are rejected with400 Invalid query parameters— they are not silently clamped to the nearest valid value. Validate these on your side before sending requests.
Incremental Sync
Rather than re-fetching every company and contact on every sync, use the lastSeenSince (companies) and createdSince (contacts) parameters to ask Alphie only for what's changed. A typical integration looks like this:
- Initial backfill. Starting at
page=1with a largepageSize(up to100), page through the full result set — incrementpageuntil the number of records returned is less thanpageSize, or you've retrievedtotalrecords. Upsert every record you receive into your CRM, keyed by itsid. - Record a checkpoint. Once the backfill finishes successfully, store the current time as your
lastSyncedAtcheckpoint. - Poll incrementally. On each subsequent run, call the endpoint with
lastSeenSince(companies) orcreatedSince(contacts) set to your stored checkpoint, minus about one minute. That small overlap protects against clock skew and against records that were still being written at the exact instant of your last checkpoint. Page through the results the same way as the backfill. - Upsert by
id. Because the overlap window may return a few records you've already synced, always upsert (insert-or-update) byidrather than inserting blindly. This makes each sync run idempotent and safe to retry or re-run. - Advance the checkpoint. After a poll completes successfully, move your stored
lastSyncedAtforward to the time you started that poll (again, before the one-minute overlap is applied next time). - Repeat on an interval that respects the rate limit — every 5–15 minutes is a reasonable starting point for most integrations.
Run the full backfill first. The one-per-person de-duplication described underGET /contactsis evaluated across the records in the response window. In a full backfill (nocreatedSince) that window is your entire history, so each person appears once. In an incremental poll, a person you already imported as a chat/form contact during the backfill can reappear later as analphiecontact if Alphie discovers them at their company after your checkpoint. Upserting byidkeeps this safe; if you require strictly one record per person, de-duplicate by
GET /companies
Returns companies Alphie has identified visiting your website — via your Alphie widget or forms — ordered by most recent activity first (lastSeenAt descending). Traffic identified only as a bot or generic ISP, with no attributable company, is excluded automatically.
GET /api/public/v1/companiesQuery parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
page | integer | No | 1 | Page number to retrieve. |
pageSize | integer | No | 25 | Records per page. Maximum 100. |
lastSeenSince | string (ISO 8601 datetime, with timezone offset) | No | — | Only return companies whose most recent visit (lastSeenAt) is at or after this instant, e.g. 2026-07-01T00:00:00Z. This is the recommended cursor for incremental syncs. |
Response fields
Each object in data has the following shape:
| Field | Type | Description |
|---|---|---|
id | string | Stable, unique identifier for the company. Use this as your dedupe/upsert key. |
domain | string | The company's website domain. Unique per company. |
name | string | null | Company name. |
website | string | null | Company website URL. |
description | string | null | Short description of the company. |
industry | string | null | Primary industry. |
employeeCount | string | null | Employee count, provided as a range or descriptive text where known (e.g. "51-200"). |
annualRevenue | string | null | Annual revenue, provided as a range or descriptive text where known (e.g. "$10M-$25M"). |
foundedYear | string | null | Year the company was founded. |
linkedinUrl | string | null | URL of the company's LinkedIn page. |
city | string | null | City of the company's primary location. |
country | string | null | Country of the company's primary location. |
icpScore | number | null | Alphie's fit score for this company against your ideal customer profile, from 0–100. Higher means a better fit. |
icpFit | string | null | Human-readable label for icpScore (e.g. "Strong Fit"). |
sessions | number | Number of identified visit sessions recorded from this company. |
firstSeenAt | string | null | ISO 8601 timestamp of the company's first identified visit. |
lastSeenAt | string | null | ISO 8601 timestamp of the company's most recent identified visit. |
Example request
curl -H "Authorization: Bearer alph_YOUR_KEY" \
"https://app.meetalphie.com/api/public/v1/companies?pageSize=50&lastSeenSince=2026-07-01T00:00:00Z"Example response
{
"data": [
{
"id": "8f3a1c2e-9b7d-4f10-a2c4-6e8d0b2f4a61",
"domain": "brightloopsystems.com",
"name": "BrightLoop Systems",
"website": "https://www.brightloopsystems.com",
"description": "Cloud-based inventory management software for mid-market retailers.",
"industry": "Software",
"employeeCount": "51-200",
"annualRevenue": "$10M-$25M",
"foundedYear": "2015",
"linkedinUrl": "https://www.linkedin.com/company/brightloop-systems",
"city": "Austin",
"country": "United States",
"icpScore": 87,
"icpFit": "Strong Fit",
"sessions": 14,
"firstSeenAt": "2026-06-02T14:22:31Z",
"lastSeenAt": "2026-07-21T09:47:03Z"
},
{
"id": "2b6e9d4a-1f5c-4873-b0e2-7c31da95f2c8",
"domain": "nordvale-consulting.eu",
"name": "Nordvale Consulting",
"website": "https://www.nordvale-consulting.eu",
"description": null,
"industry": "Professional Services",
"employeeCount": "11-50",
"annualRevenue": null,
"foundedYear": null,
"linkedinUrl": "https://www.linkedin.com/company/nordvale-consulting",
"city": "Amsterdam",
"country": "Netherlands",
"icpScore": 54,
"icpFit": "Possible Fit",
"sessions": 3,
"firstSeenAt": "2026-07-10T08:03:12Z",
"lastSeenAt": "2026-07-20T16:58:44Z"
}
],
"page": 1,
"pageSize": 50,
"total": 214
}GET /contacts
Returns people captured by your Alphie chat widget and forms, plus contacts Alphie has discovered at companies it has identified, ordered newest first (createdAt descending).
GET /api/public/v1/contactsQuery parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
page | integer | No | 1 | Page number to retrieve. |
pageSize | integer | No | 25 | Records per page. Maximum 100. |
source | string | No | all | Filter by how the contact originated. One of all, chat, form, alphie — see below. |
createdSince | string (ISO 8601 datetime) | No | — | Only return contacts created at or after this instant. Recommended cursor for incremental syncs. |
source values:
| Value | Description |
|---|---|
all (default) | All contacts, regardless of source. |
chat | Captured via your Alphie chat widget. |
form | Submitted through one of your forms. |
alphie | Discovered by Alphie at a company it identified visiting your site (not directly captured via chat or a form). |
Dedupe note: if the same person both submits a chat or form message and is separately discovered by Alphie at their company, they appear only once in this endpoint — as thechatorformrecord — never duplicated as a separatealphierecord.
Response fields
Each object in data has the following shape:
| Field | Type | Description |
|---|---|---|
id | string | Stable, unique identifier for the contact. Use this as your dedupe/upsert key. |
source | "chat" | "form" | "alphie" | How the contact originated. See the source value table above. |
fullName | string | null | Contact's full name. |
firstName | string | null | Contact's first name. Populated for alphie contacts. |
lastName | string | null | Contact's last name. Populated for alphie contacts. |
email | string | null | Contact's email address. |
phone | string | null | Contact's phone number. Only present for chat and form contacts. |
jobTitle | string | null | Contact's job title. Only present for alphie contacts. |
linkedinUrl | string | null | URL of the contact's LinkedIn profile. |
message | string | null | The message the visitor submitted via chat or a form. Only present for chat and form contacts. |
status | string | null | Lead status: one of NEW, CONTACTED, QUALIFIED, CONVERTED, LOST. Only present for chat and form contacts; always null for alphie contacts. |
company | object | null | The contact's associated company, or null if none is known. See below. |
createdAt | string | ISO 8601 timestamp of when the contact was captured or discovered. |
The nested company object, when present, has the following shape:
| Field | Type | Description |
|---|---|---|
id | string | The associated company's id (matches id in GET /companies). |
name | string | null | Company name. |
domain | string | Company domain. |
website | string | null | Company website URL. |
Example request
curl -H "Authorization: Bearer alph_YOUR_KEY" \
"https://app.meetalphie.com/api/public/v1/contacts?source=alphie&pageSize=50"Example response
The example below shows one contact from each source (a request without a source filter, i.e. source=all, would return a mix like this):
{
"data": [
{
"id": "1a4f7c2e-8b3d-4961-9c5e-d40e2a718b36",
"source": "chat",
"fullName": "Maria Chen",
"firstName": null,
"lastName": null,
"email": "maria.chen@brightloopsystems.com",
"phone": "+1-512-555-0148",
"jobTitle": null,
"linkedinUrl": null,
"message": "Hi, we're looking for a solution to sync inventory across 3 warehouses. Can you tell me more about pricing?",
"status": "QUALIFIED",
"company": {
"id": "8f3a1c2e-9b7d-4f10-a2c4-6e8d0b2f4a61",
"name": "BrightLoop Systems",
"domain": "brightloopsystems.com",
"website": "https://www.brightloopsystems.com"
},
"createdAt": "2026-07-21T09:50:12Z"
},
{
"id": "9d2e5b8a-4f1c-4036-8d7b-64c1e0af5923",
"source": "form",
"fullName": "Tobias Reyes",
"firstName": null,
"lastName": null,
"email": "tobias.reyes@nordvale-consulting.eu",
"phone": null,
"jobTitle": null,
"linkedinUrl": null,
"message": "Requesting a demo for our operations team.",
"status": "NEW",
"company": {
"id": "2b6e9d4a-1f5c-4873-b0e2-7c31da95f2c8",
"name": "Nordvale Consulting",
"domain": "nordvale-consulting.eu",
"website": "https://www.nordvale-consulting.eu"
},
"createdAt": "2026-07-20T17:02:09Z"
},
{
"id": "5f8c1a3e-9d7b-4264-a1c9-3be07f61d245",
"source": "alphie",
"fullName": "Priya Natarajan",
"firstName": "Priya",
"lastName": "Natarajan",
"email": "priya.natarajan@brightloopsystems.com",
"phone": null,
"jobTitle": "VP of Operations",
"linkedinUrl": "https://www.linkedin.com/in/priyanatarajan",
"message": null,
"status": null,
"company": {
"id": "8f3a1c2e-9b7d-4f10-a2c4-6e8d0b2f4a61",
"name": "BrightLoop Systems",
"domain": "brightloopsystems.com",
"website": "https://www.brightloopsystems.com"
},
"createdAt": "2026-07-19T11:14:55Z"
}
],
"page": 1,
"pageSize": 25,
"total": 58
}Errors
All error responses are JSON objects with an error field. Some also include additional detail specific to the error.
| Status | Body | Meaning |
|---|---|---|
400 | { "error": "Invalid query parameters", "details": { … } } | One or more query parameters failed validation (e.g. pageSize out of range, a malformed date). details describes the specific problem. |
401 | { "error": "API key required" } | No API key was supplied on the request. |
401 | { "error": "Invalid API key" } | The supplied API key doesn't match any known key. |
401 | { "error": "API key revoked" } | The supplied API key was valid but has since been revoked. |
403 | { "error": "Insufficient scope" } | The API key doesn't have permission to perform this request. |
403 | { "error": "Subscription required" } | The account's current subscription doesn't include API access. |
429 | { "error": "Too many requests", "retryAfter": 30 } | The rate limit was exceeded. retryAfter is the number of seconds to wait before retrying. |
500 | { "error": "Internal server error" } | An unexpected error occurred. Retry with backoff. |
Versioning
The current version of the API is v1, identified by the /v1 segment in the base URL. Within v1, changes are additive only: new optional query parameters and new response fields may be introduced over time, but existing fields will not be removed, renamed, or repurposed. Integrate defensively — parse responses leniently and ignore any fields you don't recognize — so future additions never break your sync.
