API
API reference
7 endpoints, all GET, all read-only, all scoped to the key’s operator. Rendered from /api/v1/openapi.json, which is generated from the schemas that validate each request. Authentication and limits are on the API overview.
GET /api/v1/events
Search your events. The entry point for almost every workflow. Filter by part of the name, a date range or a status, and get back one row per event with its counts and gallery link. Take the id from here into the per-event tools.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
| query | query | string, up to 80 characters | No | Part of the event name |
| from | query | date (YYYY-MM-DD) | No | Earliest event date, inclusive |
| to | query | date (YYYY-MM-DD) | No | Latest event date, inclusive |
| status | query | one of "draft", "active", "archived" | No | Only events in this status. |
| limit | query | integer 1 to 100default 25 | No | Maximum number of rows to return. |
Example request
curl "https://www.happyhearaudio.com/api/v1/events?query=Rivera&from=2026-11-01&to=2026-11-30&limit=10" \
-H "Authorization: Bearer $HHA_API_KEY"Example response
{
"events": [
{
"id": "7c1f3a2e-9b4d-4e61-8f0a-2d5c6b7e8a91",
"event_name": "Rivera 50th Anniversary",
"event_date": "2026-11-14",
"status": "active",
"unlocked": true,
"payment_type": "per_event",
"created_at": "2026-10-02T18:21:07.412Z",
"updated_at": "2026-10-29T03:14:55.009Z",
"gallery_slug": "rivera-50th-anniversary",
"gallery_url": "https://www.happyhearaudio.com/g/rivera-50th-anniversary",
"has_share_code": false,
"gallery_sent": true,
"gallery_first_opened_at": "2026-11-16T15:02:41.000Z",
"download_enabled": true,
"callin_enabled": true,
"callin_start_date": "2026-11-07",
"callin_end_date": "2026-11-21",
"clean_level": "standard",
"date_changes_remaining": 2,
"messages_total": 42,
"messages_ready": 39,
"messages_cleaning": 2,
"messages_waiting": 0,
"messages_needs_attention": 1,
"messages_hidden_by_operator": 1,
"unique_visitors": 63,
"total_plays": 217,
"total_downloads": 11
}
],
"count": 1,
"truncated": false
}Errors
400A parameter is missing or malformed; `issues` names the field.401No key, an unknown key, or a revoked key.403The account is not on the Monthly plan.429More than 60 requests in a minute for this key.503A read on our side failed. Retry after a short wait.
Notes
- Rows come newest event date first; events without a date sort last.
- The name match is a case-insensitive substring. Wildcard characters in query are matched literally.
- truncated is true when exactly limit rows came back, so there may be more: narrow the filter or raise limit (100 at most).
- Only the signed-in operator's events are visible. Row-level security enforces this below the server, not in this code.
GET /api/v1/events/needing-attention
List events that need a look. An account-wide "is anything stuck?" check: events with a message in needs_attention, or a message still cleaning more than 30 minutes after it arrived. An empty list means nothing needs the operator.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
| limit | query | integer 1 to 100default 25 | No | Maximum number of rows to return. |
Example request
curl "https://www.happyhearaudio.com/api/v1/events/needing-attention?limit=25" \
-H "Authorization: Bearer $HHA_API_KEY"Example response
{
"events": [
{
"id": "7c1f3a2e-9b4d-4e61-8f0a-2d5c6b7e8a91",
"event_name": "Rivera 50th Anniversary",
"event_date": "2026-11-14",
"messages_needing_attention": 1,
"messages_stuck_cleaning": 0,
"dashboard_url": "https://www.happyhearaudio.com/dashboard/events/7c1f3a2e-9b4d-4e61-8f0a-2d5c6b7e8a91"
}
],
"count": 1,
"truncated": false
}Errors
400A parameter is missing or malformed; `issues` names the field.401No key, an unknown key, or a revoked key.403The account is not on the Monthly plan.429More than 60 requests in a minute for this key.503A read on our side failed. Retry after a short wait.
Notes
- Stuck is measured from the message's arrival time, so a re-clean started on an older message reads as stuck for its first 30 minutes.
- limit bounds the candidate events checked, not the rows returned; truncated is true when the candidate list filled it.
- When nothing is found the result carries a note field saying so, so an assistant has something to say.
GET /api/v1/events/{id}
Get one event. One event in full: the same row search_events returns, plus one entry per message with its number, source, state, length and whether the operator hid it. Use it when counts are not enough.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
| id | path | string (uuid) | Yes | The event id, as returned by search_events or create_event. |
Example request
curl "https://www.happyhearaudio.com/api/v1/events/7c1f3a2e-9b4d-4e61-8f0a-2d5c6b7e8a91" \
-H "Authorization: Bearer $HHA_API_KEY"Example response
{
"event": {
"id": "7c1f3a2e-9b4d-4e61-8f0a-2d5c6b7e8a91",
"event_name": "Rivera 50th Anniversary",
"event_date": "2026-11-14",
"status": "active",
"unlocked": true,
"payment_type": "per_event",
"created_at": "2026-10-02T18:21:07.412Z",
"updated_at": "2026-10-29T03:14:55.009Z",
"gallery_slug": "rivera-50th-anniversary",
"gallery_url": "https://www.happyhearaudio.com/g/rivera-50th-anniversary",
"has_share_code": false,
"gallery_sent": true,
"gallery_first_opened_at": "2026-11-16T15:02:41.000Z",
"download_enabled": true,
"callin_enabled": true,
"callin_start_date": "2026-11-07",
"callin_end_date": "2026-11-21",
"clean_level": "standard",
"date_changes_remaining": 2,
"messages_total": 42,
"messages_ready": 39,
"messages_cleaning": 2,
"messages_waiting": 0,
"messages_needs_attention": 1,
"messages_hidden_by_operator": 1,
"unique_visitors": 63,
"total_plays": 217,
"total_downloads": 11
},
"messages": [
{
"message_number": 1,
"source": "callin",
"state": "ready",
"duration_seconds": 24,
"hidden_from_gallery": false
},
{
"message_number": 2,
"source": "upload",
"state": "ready",
"duration_seconds": 41,
"hidden_from_gallery": false
},
{
"message_number": 3,
"source": "web",
"state": "cleaning",
"duration_seconds": 18,
"hidden_from_gallery": false
},
{
"message_number": 4,
"source": "callin",
"state": "needs_attention",
"duration_seconds": 0,
"hidden_from_gallery": true
}
],
"dashboard_url": "https://www.happyhearaudio.com/dashboard/events/7c1f3a2e-9b4d-4e61-8f0a-2d5c6b7e8a91"
}Errors
400A parameter is missing or malformed; `issues` names the field.401No key, an unknown key, or a revoked key.403The account is not on the Monthly plan.404No event with that id in your account.429More than 60 requests in a minute for this key.503A read on our side failed. Retry after a short wait.
Notes
- Message states: ready, cleaning, waiting, needs_attention, video.
- Sources are how the message arrived: callin, sms, web or upload.
- At most 500 messages are listed per event.
- No audio, no transcript text, no caller details. The view the tool reads has no such columns.
GET /api/v1/events/{id}/audio-status
Check audio status on an event. The cheapest way to answer "is the audio ready?" for one event: counts by state, a yes/no all_audio_ready flag and one short line per message. Smaller than get_event when you do not need durations or sources.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
| id | path | string (uuid) | Yes | The event id, as returned by search_events or create_event. |
Example request
curl "https://www.happyhearaudio.com/api/v1/events/7c1f3a2e-9b4d-4e61-8f0a-2d5c6b7e8a91/audio-status" \
-H "Authorization: Bearer $HHA_API_KEY"Example response
{
"event_id": "7c1f3a2e-9b4d-4e61-8f0a-2d5c6b7e8a91",
"event_name": "Rivera 50th Anniversary",
"total": 4,
"counts": {
"ready": 2,
"cleaning": 1,
"waiting": 0,
"needs_attention": 1,
"video": 0
},
"all_audio_ready": false,
"messages": [
"Message 1: ready",
"Message 2: ready",
"Message 3: cleaning",
"Message 4: needs attention"
],
"dashboard_url": "https://www.happyhearaudio.com/dashboard/events/7c1f3a2e-9b4d-4e61-8f0a-2d5c6b7e8a91"
}Errors
400A parameter is missing or malformed; `issues` names the field.401No key, an unknown key, or a revoked key.403The account is not on the Monthly plan.404No event with that id in your account.429More than 60 requests in a minute for this key.503A read on our side failed. Retry after a short wait.
Notes
- all_audio_ready is true only when there is at least one message and none is cleaning or waiting. A needs_attention message does not block it.
- Video messages never enter the audio pipeline, so they count separately.
GET /api/v1/events/{id}/gallery-link
Get the gallery link. The share link for one gallery, on the operator's own domain when it is verified, plus whether guests need a share code to open it. The code itself is never returned.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
| id | path | string (uuid) | Yes | The event id, as returned by search_events or create_event. |
Example request
curl "https://www.happyhearaudio.com/api/v1/events/7c1f3a2e-9b4d-4e61-8f0a-2d5c6b7e8a91/gallery-link" \
-H "Authorization: Bearer $HHA_API_KEY"Example response
{
"event_id": "b84d2e07-5c3a-4f19-a6e2-91f0c3d7b2e4",
"event_name": "Nguyen Wedding",
"gallery_url": "https://audio.northlightbooths.com/g/nguyen-wedding",
"share_code_required": true,
"note": "Guests need the share code to open it. The code is shown only in your dashboard, never here.",
"status": "active",
"unlocked": true,
"gallery_sent": false,
"downloads_enabled": true,
"dashboard_url": "https://www.happyhearaudio.com/dashboard/events/b84d2e07-5c3a-4f19-a6e2-91f0c3d7b2e4"
}Errors
400A parameter is missing or malformed; `issues` names the field.401No key, an unknown key, or a revoked key.403The account is not on the Monthly plan.404No event with that id in your account.429More than 60 requests in a minute for this key.503A read on our side failed. Retry after a short wait.
Notes
- This reads a link; it does not send the gallery. Delivery to the client still happens from the event page.
- The note field is present only when a share code is set.
GET /api/v1/events/{id}/gallery-stats
Get gallery stats. Visitors, plays and downloads for one gallery, whether it was sent, and the day it was first opened. Counts and dates only, from the same numbers the event page shows.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
| id | path | string (uuid) | Yes | The event id, as returned by search_events or create_event. |
Example request
curl "https://www.happyhearaudio.com/api/v1/events/7c1f3a2e-9b4d-4e61-8f0a-2d5c6b7e8a91/gallery-stats" \
-H "Authorization: Bearer $HHA_API_KEY"Example response
{
"event_id": "7c1f3a2e-9b4d-4e61-8f0a-2d5c6b7e8a91",
"event_name": "Rivera 50th Anniversary",
"visits": 63,
"plays": 217,
"downloads": 11,
"gallery_sent": true,
"first_opened_on": "2026-11-16",
"dashboard_url": "https://www.happyhearaudio.com/dashboard/events/7c1f3a2e-9b4d-4e61-8f0a-2d5c6b7e8a91"
}Errors
400A parameter is missing or malformed; `issues` names the field.401No key, an unknown key, or a revoked key.403The account is not on the Monthly plan.404No event with that id in your account.429More than 60 requests in a minute for this key.503A read on our side failed. Retry after a short wait.
Notes
- first_opened_on is null until a guest opens the gallery.
- Nothing per visitor is kept or returned.
GET /api/v1/account/plan
Get your plan and usage. Which plan the account is on, its subscription status and paid-through date, whether the free first event is used, and how many events exist. Call it before create_event when the assistant needs to say whether the next event will need payment.
Parameters
No parameters.
Example request
curl "https://www.happyhearaudio.com/api/v1/account/plan" \
-H "Authorization: Bearer $HHA_API_KEY"Example response
{
"plan": "monthly",
"unlimited_events": true,
"subscription_status": "active",
"paid_through": "2026-11-23",
"pause_ends_on": null,
"free_event_used": true,
"first_event_created_on": "2026-06-18",
"events_created": 27,
"note": "Unlimited events are included while the subscription is paid.",
"billing_url": "https://www.happyhearaudio.com/dashboard/settings"
}Errors
401No key, an unknown key, or a revoked key.403The account is not on the Monthly plan.429More than 60 requests in a minute for this key.503A read on our side failed. Retry after a short wait.
Notes
- plan is per_event, monthly or annual. unlimited_events is the entitlement that matters for create_event.
- A paused subscription is paid only through paid_through; pause_ends_on says when billing resumes.
- No card, invoice or payment detail is ever included.