An API key lets your own server call the Heullo API for your organization. Use one for server-to-server integrations, such as a CRM, a booking system or an order pipeline. People still sign in to the web application with their own login. A key acts for the organization, not for a person. Messages it sends, contacts it creates and credits it uses belong to the organization. A key keeps working when the member who created it leaves the organization, so revoke keys yourself when they are no longer needed.

Who can manage keys

Owners and Admins manage keys in Settings → API keys. Other roles do not see that tab. See Your account for the roles. A key cannot create, list or revoke keys. Managing keys always needs a signed-in Owner or Admin.

Create a key

1

Open Settings → API keys

Start a new key from the API keys tab.
2

Name it after the integration

Up to 60 characters, for example CRM sync. A clear name tells you which integration stops working when you revoke the key.
3

Choose the scopes

Tick only what the integration needs. See Scopes below.
4

Choose an expiry

30, 90 or 365 days, or never. Prefer an expiry: a key that expires limits the damage if it leaks unnoticed. An expired key is refused from the moment it expires.
5

Copy the key and store it

The key is shown once, when you create it. Copy it into your secret manager or your platform’s secret store straight away. Heullo stores only a hashed form of the key, so it cannot show it to you again. If you lose a key, create a new one and revoke the old one.
An organization can have at most 10 active keys. Revoked and expired keys do not count toward the limit. Revoke a key you no longer use before creating another. The API keys tab lists every key with its name, a hint such as hk_live_AbCd…wxyz, its scopes, who created it, when it was last used, when it expires and its status: active, expired or revoked. Revoked and expired keys stay in the list as a record.

IP allowlists

When IP allowlists are available for your organization, the create form shows an extra field for them. Enter one public IPv4 or IPv6 address or CIDR range per line, up to 20 entries, for example 203.0.113.7 or 198.51.100.0/24. A key with an allowlist answers 403 with API_KEY_IP_NOT_ALLOWED to requests from any other address. A key without an allowlist accepts requests from any address.

Key format

Production keys start with hk_live_, followed by 40 letters and digits:
Keys created in the staging environment start with hk_test_. A key works only in the environment that created it, and is refused with 401 in the other. See Base URLs. The fixed prefix makes a key easy to match with a secret-scanning rule in your own repositories and logs.

Call the API with a key

Send the key as a bearer token in the Authorization header of every request:
No X-Organization-Id header is needed, because the key identifies its organization. If you send that header anyway, it must name the key’s organization, or the request is refused with 403 and ORGANIZATION_MISMATCH. The key is accepted only in the Authorization header, never in a query string or form field. The examples below call production, https://api.heullo.com, and read the key from an environment variable on your server, so it never appears in your source code. Responses use the standard response envelope. Response examples are abridged; each operation’s page lists every field.

Send a single message

Needs the sms:send scope. Operation reference.
status is SENT when the mobile network accepted the message, FAILED when it was refused (the credits are returned), and SCHEDULED when you set a future scheduledFor. Keep id to read the delivery status later. A balance below the cost of the message answers 402; see Errors.

Send a bulk message

Needs the sms:send scope. Operation reference. One bulk request sends the same message to many recipients. Prefer it to a loop of single sends: it is one request against your rate limit, and it creates one campaign in SMS Management.
The credits for the whole campaign are checked when you send, and a balance that does not cover every recipient answers 402. The messages are then sent in the background. recipientCount and uniqueRecipientsCount are the number of recipients after duplicates were removed, originalRecipientsCount is the number you sent, and duplicatesRemoved the difference. Keep managementId to follow the campaign.

Read a message’s delivery status

Needs the sms:read scope. Operation reference.
The response has the same shape as the single send response. Read status; see Statuses for what each value means. An identifier that does not belong to your organization answers 404. For the recipients of a bulk campaign, list the campaign’s messages with the managementId from the bulk response. Paging starts at 1, and size is at most 500:
See List the messages in a campaign for the filters.

List inbound messages

Needs the inbound:read scope. Operation reference.
Paging starts at 1, and at most 100 messages are returned per page. A key with inbound:read can read messages but cannot mark them as read.

Create a contact

Needs the contacts:manage scope. Operation reference.
phoneNumber is required. name and email are stored as the contact’s name and email. Any other field, such as company above, is stored as a custom field and returned at the top level of the contact.
A phone number that already belongs to a contact in your organization answers 409.

Read the balance

Needs the balance:read scope. Operation reference.
See Credits for how the balance is charged.

Scopes

A key can call only the operations of the scopes it was given. The operations each scope allows: These operations show the API key scheme and the scope they need on their page in the API reference. Every other operation refuses an API key with 403 and API_KEY_FORBIDDEN, whatever scopes the key has. That includes members and invitations, billing other than reading the balance, settings, sender IDs, keywords, Email-to-SMS, reports, exports, CSV imports, campaign controls such as pause and cancel, and API key management itself. Use the web application, signed in as a member with the right role, for those.

Idempotency

Network failures leave a client unsure whether a send went through. To retry a send safely, give it an Idempotency-Key header. It is accepted on POST /api/v1/sms/single and POST /api/v1/sms/bulk with an API key.
The value is 1 to 255 printable ASCII characters without spaces. Use one value per intended send, for example your own order or job identifier, or a UUID you store with the job before the first attempt. Heullo keeps each idempotency key for 24 hours from the first request, per API key: “The same request body” means the same bytes. Store the body you sent and retry with it, rather than serialising the request again. Retry advice:
  • Retry timeouts, connection errors, 5xx responses and 409 IDEMPOTENCY_IN_PROGRESS with the same Idempotency-Key and body. Wait for Retry-After where it is given, then back off exponentially.
  • Do not retry 400, 402, 403 or 422 unchanged. Correct the request first. A corrected request is a new send and needs a new Idempotency-Key.
  • If retries keep answering 409 IDEMPOTENCY_IN_PROGRESS for several minutes, check SMS Management for the message before you send it under a new Idempotency-Key.
  • A request with an Idempotency-Key can be at most 1 MiB; a larger one answers 413.

Rate limits

Each key has its own limit, by default 120 requests per minute. The allowance refills evenly through the minute, so a key can make a burst of up to 120 requests and then about two per second. Over the limit, the API answers 429 with RATE_LIMITED and a Retry-After header giving the seconds to wait. Wait at least that long, then retry with exponential backoff and some random jitter, so several workers do not retry at the same moment. Retries and idempotent replays count toward the limit like any other request. To stay under the limit, send to many recipients with one bulk request rather than many single sends, and give each integration its own key. Before running sustained high-volume traffic, contact your Primetel account manager.

Errors

Errors caused by the key itself are answered before the request reaches the operation, with this body:
Match on status and code. The message text can change. Errors from the operation itself, such as a missing field (400), too few credits (402), a record that is not yours (404) or a duplicate contact (409), use the standard envelope with success: false and no code. See Errors.

Keep keys secure

  • One key per integration. You can then revoke one integration’s access without touching the others, and the last-used time tells you which integration is active.
  • Give each key the fewest scopes it needs. A key that only reads the balance cannot send messages if it leaks.
  • Rotate keys. Create the new key, deploy it to the integration, confirm the integration works with it, then revoke the old key. Both keys work during the change, so nothing breaks.
  • Revoke a leaked key at once. Revoking takes effect on the next request. Then create a replacement, and check SMS Management and the credit balance for use you do not recognise.
  • Keep keys on your servers. Never put a key in browser JavaScript, a mobile app or any other code that runs on your customers’ devices, where anyone can extract it. Call Heullo from your own backend instead.
  • Keep keys out of source code and logs. Load the key from a secret store or an environment variable, and do not log the Authorization header.
Owners and Admins of the organization are emailed when a key is created or revoked, and when a member who created keys leaves or is removed from the organization. The email names the key and never contains the key itself. If you receive one you did not expect, review Settings → API keys.

Messages sent with a key

Messages sent with an API key appear in SMS Management with the API channel, like any other message from your organization. A single send is listed as “API SMS to” followed by the recipient, and a bulk send under the name you gave it. Filter by the API channel in SMS Management or in the Outbound report to see your integrations’ traffic, and the credits it used, separately from messages your team sent.