API reference
Activity data API
Look up the email engagement your own account's Kawaa tracking has recorded, to understand email behavior and deliverability.
Your activity is yours alone. Every lookup on this page answers only from engagement observed by your own account's tracking pixels and tracked links. Another Kawaa customer tracking the same address never sees your data, and you never see theirs: an address only another account has tracked returns unknown for you. This includes include_activity on POST /v1/verify and batch or file verification.
Get email activity
/v1/activity/{email}Retrieve the engagement your account's tracking has recorded for a specific email address.
{
"success": true,
"data": {
"email": "user@example.com",
"email_hash": "b4c9a289323b21a01c3e940f150eb9b8c542587f1abfd8f0e1cc1ffc5e475514",
"activity": {
"level": "active",
"score": 78,
"last_activity_at": "2026-01-28T15:30:00Z",
"engagement": {
"open_count": 45,
"click_count": 12,
"reply_count": 2
},
"deliverability": {
"bounce_count": null,
"complaint_count": null
},
"confidence": 80,
"data_points": 18
},
"credits_used": 0.5,
"credits_remaining": 4999.5
}
}email_hash is the SHA-256 of the address, lower-cased and trimmed, with an internationalized domain in its ASCII xn-- form, written in hex. So user@münchen.de and user@xn--mnchen-3ya.de find the same records, as they do when you verify them. credits_remaining is your credit balance after the lookup. In the rare case the balance cannot be read, the field is left out rather than guessed.
When no activity data exists for an email, activity.level is unknown with zeroed engagement metrics and a message field explaining that no data is available.
deliverability.bounce_count and deliverability.complaint_count are null for every address today. Kawaa does not currently receive a bounce or complaint feed, so null means not measured. Read it as “we have no bounce or complaint data for this address”, never as “this address has never bounced”. Both fields carry a number once the signal is available.
Batch activity lookup
/v1/activity/batchLook up your account's activity data for multiple emails at once (up to 100 per request).
{
"emails": [
"user1@example.com",
"user2@example.com"
]
}{
"success": true,
"data": {
"results": [
{
"email": "user1@example.com",
"email_hash": "b36a83701f1c3191e19722d6f90274bc1b5501fe69ebf33313e440fe4b0fe210",
"activity": {
"level": "active",
"score": 78,
"last_activity_at": "2026-01-28T15:30:00Z",
"engagement": { "open_count": 45, "click_count": 12, "reply_count": 2 },
"deliverability": { "bounce_count": null, "complaint_count": null },
"confidence": 80,
"data_points": 18
}
},
{
"email": "user2@example.com",
"email_hash": "2b3b2b9ce842ab8b6a6c614cb1f9604bb8a0d502d1af49c526b72b10894e95b5",
"activity": {
"level": "unknown",
"score": 0,
"last_activity_at": null,
"engagement": { "open_count": 0, "click_count": 0, "reply_count": 0 },
"deliverability": { "bounce_count": null, "complaint_count": null },
"confidence": 0,
"data_points": 0,
"message": "No activity data available for this email"
}
}
],
"total": 2,
"credits_used": 0.5,
"credits_remaining": 4999.5
}
}Domain activity
/v1/activity/domain/{domain}Get the engagement your account's tracking has recorded across an entire domain. The counters cover only events your own tracking observed; they are not totals across Kawaa customers.
{
"success": true,
"data": {
"domain": "example.com",
"reputation_score": null,
"reputation_grade": null,
"activity": {
"total_events": 2,
"total_opens": 1,
"total_clicks": 1
},
"last_updated_at": "2026-01-13T01:41:35.752Z",
"credits_used": 1,
"credits_remaining": 4999
}
}reputation_score and reputation_grade are null for every domain today: Kawaa does not currently compute a domain reputation score, and reports null rather than a 0 that would read as the worst possible reputation on a 0–100 scale.
Inside activity, a counter is present only when that event type has actually been recorded for the domain. An absent counter means never recorded, not zero. When nothing at all has been recorded, activity is empty, last_updated_at is null, and a message field says so.
Safe retries with Idempotency-Key
Paid activity lookups accept an optional Idempotency-Key header. Use a unique token of 1–255 printable ASCII characters with no spaces or commas for each logical lookup, then keep it unchanged when retrying after a timeout or lost response.
# Requires Python 3. Run once per lookup; on retry, run only curl again.
KEY=$(python3 -c 'import uuid; print(uuid.uuid4())') || exit 1
test -n "$KEY" || exit 1
curl "https://api.kawaa.com/v1/activity/user@example.com" \
-H "X-Api-Key: ev_your_api_key" \
-H "Idempotency-Key: $KEY"GET /v1/activity/{email}, POST /v1/activity/batch, and GET /v1/activity/domain/{domain}. For 24 hours, an exact repeat returns the stored result with credits_used: 0 and idempotent_replay: true. Reusing a key with different input returns 409 IDEMPOTENCY_KEY_CONFLICT before any charge. Safe replay requires an internal receipt containing the submitted input and result. The receipt has a 24-hour expiration and cannot be replayed after that window; storage lifecycle deletion is asynchronous. Kawaa does not expose the receipt as a job or include it in activity analytics.Without a key, the two GET lookups are still charged once per 15 minutes. HTTP clients, proxies and browsers can resend a GET on their own. So when your account repeats GET /v1/activity/{email} or GET /v1/activity/domain/{domain} for the same address (spelled the same way) or the same domain within 15 minutes of the lookup that was charged, you get that lookup's stored result back with credits_used: 0 and idempotent_replay: true. Copies sent at the same time are charged once. The 15 minutes count from the charge, and a repeat does not extend them: the first repeat after that is a new paid lookup with fresh data. Engagement recorded in between shows up then. To get fresh data sooner, send a new Idempotency-Key. Only your own lookups are ever replayed to you.
Without a key, a batch is protected only while it runs. An identical POST /v1/activity/batch (the same addresses in the same order) that Kawaa reads while the first is still being looked up joins it: it gets that batch's result with credits_used: 0 and idempotent_replay: true, and is not charged. What counts is when Kawaa reads the copy, not when you sent it: a copy read after the first batch has finished is a new lookup and is charged again, even one sent while the first was still running, and so is a retry after a lost response. Send an Idempotency-Key to retry a batch safely.
Credits
- 0.5 credits per single email activity lookup (
GET /v1/activity/{email}) - 0.25 credits per email in a batch lookup (
POST /v1/activity/batch, max 100 valid addresses of 254 characters each) - 1 credit per domain activity lookup (
GET /v1/activity/domain/{domain}) - Credits are charged per request, including when no activity data is found. An identical GET lookup repeated within 15 minutes of the charged one is free (see Safe retries)
- If a lookup fails internally after the charge, the credits are refunded automatically. A lookup that stops before it answers (for example, the request times out) is finished free by an identical repeat within 15 minutes of the charge; if nobody repeats it, its credits are refunded automatically after those 15 minutes
Engagement levels
| Level | Description | Criteria |
|---|---|---|
| active | Recently engaged | Engagement within the last 30 days |
| inactive | Fading engagement | No engagement in 30-90 days |
| dormant | Long-term unengaged | No engagement in 90+ days |
| unknown | No data available | Insufficient tracked activity |
Data sources
Activity data is built from engagement observed only by your own account's Kawaa tracking:
- • Tracking pixels from email campaigns
- • Click tracking from email links
Activity is therefore specific to your account: an address your tracking has never seen engage returns an empty record rather than an inferred one, even if another Kawaa customer tracks it, and coverage grows as you send with tracking enabled.
Privacy: Activity is stored per account and keyed by a SHA-256 hash of the address, never the address itself. Kawaa does not pool engagement across customers: what your tracking records is returned only to your account, and is never shown to another customer or shared with third parties.
Where tracking links are served
Tracking links are served from https://t.kawaa.com. Use the tracking_url the API returns for each link: creating, listing and updating links all give it on that host. Pixel URLs stay on https://api.kawaa.com on purpose: many mail services load images through a shared image proxy, and the tracking host limits how many pixel loads one address can make.
Every link works on t.kawaa.com, including links created before the tracking host went live in October 2026. A link URL on api.kawaa.com no longer redirects: it goes to https://kawaa.com and the click is not recorded. To use an older link again, replace api.kawaa.com with t.kawaa.com in its URL.
Tracking link limits
/v1/tracking/links/v1/tracking/links/{id}A tracking link sends whoever opens it on to the URL you chose, so creating links is limited. Changing an existing link's redirect_url to a different URL counts toward the same daily limit as creating a link. Editing a link's name or campaign, or sending its current URL back unchanged, does not count. Links you already have keep working, and nothing is created or changed when a limit refuses a request. A link expires one year after it is created.
| Limit | Applies to | When reached |
|---|---|---|
| 100 links created or pointed at a new URL per UTC day | Every plan | 429 RATE_LIMITED with Retry-After, the seconds until midnight UTC |
| 10 active links held at once | Free | 403 PLAN_LIMIT_EXCEEDED. Delete a link you no longer use, or upgrade |
If a limit cannot be checked, the request is refused with 503 SERVICE_UNAVAILABLE and Retry-After rather than creating or changing the link.