Five requests: check a key, add a contact, record what they did, and send them an email.
The API is at https://api.kirora.io.
1 Create a key
In the dashboard, open Developer → API keys and create a key with these scopes:
contacts:write, events:write and transactional:send. Copy it now: it is shown once. Then set it in your shell:
export KIRORA_API_KEY=kirora_...
export KIRORA_API_URL=https://api.kirora.io
2 Check it
With cURL:
curl "$KIRORA_API_URL/v1/me" -H "Authorization: Bearer $KIRORA_API_KEY"
The answer names your workspace and the key’s scopes. A 401 means the key is wrong or
revoked; see Authentication.
3 Add a contact
With cURL, identify creates the contact, or updates the one with that externalId or
email:
curl "$KIRORA_API_URL/v1/contacts/identify" \
-H "Authorization: Bearer $KIRORA_API_KEY" \
-H "Content-Type: application/json" \
-d '{"externalId":"user_123","email":"ada@example.com","firstName":"Ada","emailConsent":true}'
example.com is a test address: sends to it go through
every check and are never delivered. Use a real address when you want to see the email
arrive.
4 Track an event
Events drive journeys, segments and conversion goals. Name them for what the person did. With cURL:
curl "$KIRORA_API_URL/v1/events/track" \
-H "Authorization: Bearer $KIRORA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: order-1001-completed" \
-d '{"externalId":"user_123","eventName":"Order Completed","properties":{"total":42.5,"currency":"USD"}}'
occurredAt is optional and defaults to now. Send it to record when the event really
happened: an ISO 8601 timestamp with Z or an offset (2026-10-01T09:30:00Z,
2026-10-01T10:30:00+01:00), at most 24 hours ahead.
The Idempotency-Key makes a retry safe: the same key never records the event twice. See
Idempotency and the standard names in E-commerce events.
Naming events
- One fixed name per action, in Title Case, object then past-tense verb:
Order Completed,Plan Upgraded. Anything that varies, such as an id, a price or a plan, goes inproperties, never in the name. - Names are case-sensitive.
order completedwill not start a journey waiting forOrder Completed. - Up to 128 characters, no control characters (newlines, tabs). Names beginning with
contact.are reserved for the events Kirora records itself. - At most 1,000 distinct names per workspace. A new name past that is refused with
EVENT_NAME_LIMIT_REACHED;GET /v1/events/namesreports the count inmeta.nameUsage.DELETE /v1/events/names/{name}removes a name sent by mistake, with all its events, unless a journey, segment, campaign or goal still reads it.
5 Send an email
Email goes out from a domain you have verified: add one under Settings → Email → Domains and publish the records it shows. Then send with cURL:
curl "$KIRORA_API_URL/v1/transactional/messages" \
-H "Authorization: Bearer $KIRORA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: order-1001-receipt" \
-d '{
"to": { "externalId": "user_123" },
"email": {
"subject": "Thanks for your order, {{ contact.firstName | default: \"there\" }}",
"from": "Shop <orders@mail.yourdomain.com>",
"html": "<p>Your order is confirmed. We will tell you when it ships.</p>"
}
}'
The answer lists the message it queued; its outcome arrives on
webhooks or from GET /v1/transactional/messages/{id}. From an address on a domain that is not verified,
the request fails with 403 SENDING_DOMAIN_UNVERIFIED. For stored
templates, push, and sending to many at once, read
Transactional messages.
You should see the email in your inbox.
Next steps
Marketing email (campaigns and journeys) needs the contact’s consent and your postal
address; set both up in the dashboard, then build in it or over /v1.