Authentication
Create API keys, send them with each request, choose scopes and handle authentication errors.
Every request to the Signal REST API is authenticated with an API key. A key belongs to one workspace: every request made with it reads and writes that workspace's data, and nothing else. A key stands for the workspace, not for a person, so records it creates aren't credited to anyone unless you say who (see Assigned To and owner fields).
Create a key#
- In Signal, open Autopilot in the sidebar.
- Select the Webhooks & API tab, then API Keys.
- Select New Key.
- Enter a Key Name that says where the key is used (for example "Production integration").
- Tick the Scopes the key needs. Give each key only the scopes its integration uses.
- Select Create Key.
- Copy the key from the Save your API key dialog and store it somewhere safe, such as your secrets manager. This is the only time Signal shows the full key. Afterward the list shows only its first characters.
Who can create keys#
- Anyone whose role can view integrations can see the workspace's keys (name, first characters, scopes, status, created and last-used dates).
- Only people whose role can manage integrations can create or revoke keys. Workspace admins can by default.
- A key can do no more than the person who creates it. For the scopes below that map to a role permission, the creator must hold that permission:
| Scope | Creator needs |
|---|---|
accounts:read |
Read Accounts |
accounts:write |
Create, update and delete Accounts |
contacts:read |
Read Contacts |
contacts:write |
Create, update and delete Contacts |
deals:read |
Read Deals |
deals:write |
Create, update and delete Deals |
activities:read |
Read activities |
activities:write |
Create, update and delete activities |
users:read |
Read team members |
marketing:read |
A plan that includes Marketing, Marketing switched on for their role, and read access to Marketing |
marketing:write |
A plan that includes Marketing, Marketing switched on for their role, and update access to Marketing |
The Marketing scopes are offered only when your plan includes Marketing.
Key format#
Keys look like sk_live_ followed by 64 hexadecimal characters:
sk_live_3b1f0c9e7a2d4e6f8a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f
Signal stores only a hash of the key, so a lost key can't be recovered. Create a new one instead.
Keys created in Signal don't expire. They work until you revoke them.
Send the key#
Send the key on every request in either of these headers. Both work the same way.
Authorization header (recommended):
curl https://crm.empiraa.app/api/v1/me \
-H "Authorization: Bearer YOUR_API_KEY"
X-API-Key header:
curl https://crm.empiraa.app/api/v1/me \
-H "X-API-Key: YOUR_API_KEY"
// Either form works with fetch
await fetch("https://crm.empiraa.app/api/v1/me", {
headers: { Authorization: "Bearer YOUR_API_KEY" },
});
await fetch("https://crm.empiraa.app/api/v1/me", {
headers: { "X-API-Key": "YOUR_API_KEY" },
});
If you send both, the Authorization: Bearer value is used.
Keep keys on your server. Don't put them in browser code, mobile apps or public repositories, where anyone can read them.
Scopes#
Each key carries a list of scopes. An endpoint checks for its scope on every request and answers 403 if the key doesn't have it. GET /me needs no scope.
| Scope | Allows | Endpoints |
|---|---|---|
accounts:read |
Read Accounts and their spend records | GET /accounts, GET /accounts/{id}, GET /accounts/{id}/spends, GET /accounts/{id}/spends/{spendId} |
accounts:write |
Create, update and deactivate Accounts; add, change and delete spend records | POST /accounts, PATCH /accounts/{id}, DELETE /accounts/{id}, POST /accounts/{id}/spends, PATCH /accounts/{id}/spends/{spendId}, DELETE /accounts/{id}/spends/{spendId} |
contacts:read |
Read Contacts | GET /contacts, GET /contacts/{id} |
contacts:write |
Create, update and delete Contacts | POST /contacts, PATCH /contacts/{id}, DELETE /contacts/{id} |
deals:read |
Read Deals | GET /deals, GET /deals/{id} |
deals:write |
Create, update and delete Deals | POST /deals, PATCH /deals/{id}, DELETE /deals/{id} |
pipeline:read |
Read pipelines and their stages | GET /pipelines, GET /pipelines/{id} |
pipeline:write |
Create, rename and delete pipelines | POST /pipelines, PATCH /pipelines/{id}, DELETE /pipelines/{id} |
prospects:read |
Read prospect contacts, Prospect Companies and prospect sources | GET /prospects, GET /prospects/{id}, GET /prospect-companies, GET /prospect-companies/{id}, GET /prospect-sources, GET /prospect-sources/{id} |
prospects:write |
Create, update and delete prospect contacts and Prospect Companies | POST /prospects, PATCH /prospects/{id}, DELETE /prospects/{id}, POST /prospect-companies, PATCH /prospect-companies/{id}, DELETE /prospect-companies/{id} |
activities:read |
Read activities and calls | GET /activities, GET /activities/{id}, GET /calls, GET /calls/{id} |
activities:write |
Create, update and delete activities; log calls | POST /activities, PATCH /activities/{id}, DELETE /activities/{id}, POST /calls, POST /call-logs |
tasks:read |
Read the /tasks list |
GET /tasks, GET /tasks/{id} |
tasks:write |
Create, update and delete items on the /tasks list |
POST /tasks, PATCH /tasks/{id}, DELETE /tasks/{id} |
notes:read |
Read notes on prospect contacts | GET /notes, GET /notes/{id} |
notes:write |
Create, edit and delete notes | POST /notes, PATCH /notes/{id}, DELETE /notes/{id} |
bookings:read |
Read Scheduler bookings | GET /bookings, GET /bookings/{id} |
bookings:write |
Create, update and cancel bookings | POST /bookings, PATCH /bookings/{id}, DELETE /bookings/{id} |
decks:read |
Read Signal Decks | GET /decks, GET /decks/{id} |
decks:write |
Create, update and delete Decks | POST /decks, PATCH /decks/{id}, DELETE /decks/{id} |
users:read |
Read the workspace's users | GET /users, GET /users/{id} |
portfolio:read |
Read portfolio summary figures | GET /portfolio |
marketing:read |
Read Campaigns (needs a plan that includes Marketing) | GET /marketing/campaigns |
marketing:write |
Reserved. No endpoint uses it yet. | None |
property:read |
Read developments, plots, reservations and call-offs | GET /property/developments, GET /property/developments/{id}, GET /property/plots, GET /property/plots/{id}, GET /property/reservations, GET /property/reservations/{id}, GET /property/call-offs, GET /property/call-offs/{id} |
property:write |
Create, update and delete developments, plots and reservations; book call-offs and record their invoices and payments | POST, PATCH and DELETE on /property/developments, /property/plots and /property/reservations; POST /property/call-offs, PATCH /property/call-offs/{id} |
A key can't be given new scopes after it's created. To change what a key can do, create a new key with the scopes you want and revoke the old one.
Rotate a key#
Keys don't expire, so rotate them on your own schedule and whenever someone who had access leaves:
- Create a new key with the same scopes.
- Deploy the new key to your integration.
- Check the old key's last used time in the API Keys list stops changing.
- Revoke the old key.
Revoke a key#
In Autopilot → Webhooks & API → API Keys, select the revoke button on the key's row and confirm with Revoke. Revoking takes effect on the next request: the key is rejected with 401 INVALID_API_KEY. A revoked key stays in the list, marked Revoked, and can't be turned back on.
Authentication errors#
Errors use the problem details format.
401 Unauthorized#
No key was sent:
{
"type": "https://signal.empiraa.com/errors/missing-api-key",
"title": "Missing API key",
"status": 401,
"code": "MISSING_API_KEY",
"detail": "Provide a key via Authorization: Bearer or X-API-Key"
}
The key doesn't exist, was revoked or has expired:
{
"type": "https://signal.empiraa.com/errors/invalid-api-key",
"title": "Invalid or revoked API key",
"status": 401,
"code": "INVALID_API_KEY"
}
403 Forbidden#
The key is valid but lacks the endpoint's scope. detail names the scope it needs:
{
"type": "https://signal.empiraa.com/errors/insufficient-scope",
"title": "Insufficient scope",
"status": 403,
"code": "INSUFFICIENT_SCOPE",
"detail": "This key requires the 'deals:write' scope"
}
GET /marketing/campaigns also answers 403 with code PLAN_FEATURE_REQUIRED when the workspace's plan no longer includes Marketing. A key keeps working as a key after a downgrade, but the Marketing endpoint stops answering. See Marketing Campaigns.
402 Payment Required#
Write requests (POST, PATCH, DELETE) answer 402 WORKSPACE_PAYMENT_REQUIRED when the workspace's subscription doesn't currently allow changes, for example after a failed payment. Reads keep working. Update the billing details in Signal to restore writes.
{
"type": "https://signal.empiraa.com/errors/workspace-payment-required",
"title": "Workspace payment required. Please update your billing details.",
"status": 402,
"code": "WORKSPACE_PAYMENT_REQUIRED"
}

