API Reference
Complete reference for the Pulse public API. Manage changelogs, roadmaps, feedback, notifications, and announcements for your projects.
Overview
The Pulse API is organized around REST. All endpoints accept and return JSON. The public API is available at the base URL below. Every request is scoped to a specific project via the :slugpath parameter, which is your project’s unique URL slug.
Base URL
For example, if your project slug is my-app, all requests will be prefixed with https://pulse.g8n.ai/api/v1/projects/my-app.
Authentication
The public API endpoints documented on this page resolve projects by their URL slug and do not require API key authentication. They are designed to be called from client-side code, such as the embeddable widget.
Admin API (Bearer Token)
The Admin API at /api/v1/admin/* requires an API key. Generate keys from your project settings in the Pulse dashboard. Keys are prefixed with plsk_ and authenticated via SHA-256 hash comparison using timing-safe equality.
Include your API key in the Authorization header:
curl -H "Authorization: Bearer plsk_your_api_key_here" \
https://pulse.g8n.ai/api/v1/admin/changelogVisitor Identification
Several endpoints require an x-pulse-user-id header to identify the visitor. This is an anonymous UUID generated client-side (similar to an analytics anonymous ID). It is not an authenticated user credential. It must be a valid UUID v4.
x-pulse-user-id: 550e8400-e29b-41d4-a716-446655440000API Key Scopes
Admin API keys include scopes that determine what operations are permitted. The scopes are stored alongside the key and returned during validation. Common scopes include read, write, and module-specific scopes. Expired keys are automatically rejected.
Rate Limiting
All API requests are rate-limited using an in-memory sliding window. Limits are applied per endpoint group and per identifier (project slug or user ID).
| Category | Max Requests | Window | Applies To |
|---|---|---|---|
| General API | 100 | 60 seconds | Changelog, Roadmap, Notifications, Announcements, Votes, Views |
| Feedback | 10 | 60 seconds | Feedback submission, Feedback messages |
| Auth | 5 | 60 seconds | Authentication attempts (Admin API) |
When the rate limit is exceeded, the API returns a 429 Too Many Requests response:
{
"data": null,
"error": "Rate limit exceeded"
}Response Format
All responses follow a consistent JSON envelope with two fields: data and error. On success, error is null. On failure, data is null.
Success Response
{
"data": [ ... ],
"error": null
}Error Response
{
"data": null,
"error": "Human-readable error message"
}CORS
All responses include permissive CORS headers to allow requests from any origin. The allowed headers are: Content-Type, Authorization, x-api-key, and x-pulse-user-id.
Error Handling
The API uses standard HTTP status codes to indicate success or failure.
| Status Code | Meaning |
|---|---|
| 200 | Success |
| 201 | Created (resource successfully created) |
| 400 | Bad Request (invalid or missing parameters) |
| 401 | Unauthorized (missing or invalid x-pulse-user-id / API key) |
| 403 | Forbidden (insufficient permissions) |
| 404 | Not Found (project, entry, or resource does not exist) |
| 429 | Too Many Requests (rate limit exceeded) |
| 500 | Internal Server Error |
Changelog
Retrieve published changelog entries, check for unseen entries, and mark entries as viewed. Entries are returned in reverse chronological order by publication date.
List changelog entries
Returns all published changelog entries for a project, ordered by published_at descending.
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| slug | string | Required | Project URL slug |
Response (array of entries)
{
"data": [
{
"id": "uuid",
"version": "1.2.0",
"title": "New Dashboard Layout",
"summary": "Redesigned the main dashboard...",
"content": "<p>Full HTML content...</p>",
"type": "feature",
"category": "ui",
"is_highlight": true,
"ai_summary": "AI-generated summary text",
"published_at": "2025-03-15T10:00:00Z",
"created_at": "2025-03-14T08:30:00Z"
}
],
"error": null
}Error Responses
Get unseen changelog entries
Returns the count and IDs of published changelog entries that the given visitor has not yet viewed. Used by the widget to show a badge count.
Headers
| Parameter | Type | Required | Description |
|---|---|---|---|
| x-pulse-user-id | string (UUID) | Required | Anonymous visitor identifier |
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| slug | string | Required | Project URL slug |
Response
{
"data": {
"unseen_count": 3,
"unseen_ids": [
"uuid-1",
"uuid-2",
"uuid-3"
]
},
"error": null
}Error Responses
Mark a changelog entry as viewed
Records that a visitor has viewed a specific changelog entry. Uses an upsert, so calling this multiple times for the same visitor and entry is safe (idempotent).
Headers
| Parameter | Type | Required | Description |
|---|---|---|---|
| x-pulse-user-id | string (UUID) | Required | Anonymous visitor identifier |
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| slug | string | Required | Project URL slug |
| id | string (UUID) | Required | Changelog entry ID |
Response
{
"data": {
"viewed": true
},
"error": null
}Error Responses
This endpoint is idempotent. Duplicate calls will not create duplicate view records.
Roadmap
Retrieve published roadmap entries and submit votes. Entries are ordered by milestone number (ascending) then sort order (ascending).
List roadmap entries
Returns all published roadmap entries for a project, ordered by milestone number and sort order.
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| slug | string | Required | Project URL slug |
Response (array of entries)
{
"data": [
{
"id": "uuid",
"milestone_number": 1,
"milestone_name": "v2.0 Launch",
"feature_name": "Dark Mode Support",
"feature_status": "in_progress",
"user_friendly_description": "Full dark mode theme...",
"target_date": "2025-06-01",
"category": "ui",
"tags": ["theme", "accessibility"],
"sort_order": 1,
"is_highlight": false,
"vote_count": 42,
"published_at": "2025-03-01T00:00:00Z"
}
],
"error": null
}Error Responses
Vote on a roadmap entry
Submit an upvote or downvote on a roadmap entry. Voting is toggle-based: sending the same vote type again removes the vote. Sending a different vote type switches it. The vote_count is maintained by a database trigger.
Headers
| Parameter | Type | Required | Description |
|---|---|---|---|
| x-pulse-user-id | string (UUID) | Required | Anonymous visitor identifier (must be valid UUID v4) |
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| slug | string | Required | Project URL slug |
| id | string (UUID) | Required | Roadmap entry ID |
Request Body
| Parameter | Type | Required | Description |
|---|---|---|---|
| vote_type | "up" | "down" | Required | The type of vote to cast |
Response
{
"data": {
"vote_count": 43,
"user_vote": "up"
},
"error": null
}Error Responses
Voting the same type again removes the vote (toggle). user_vote will be null when the vote is removed.
vote_count is updated atomically by a database trigger (update_roadmap_vote_count).
Feedback
Submit user feedback, retrieve feedback history for a user, and add messages to existing feedback threads. Feedback submission uses the stricter feedback rate limit (10 requests per minute).
Submit feedback
Create a new feedback entry for a project. Automatically assigns priority based on category (bugs get medium priority, others get low). The status is set to 'new'.
Headers
| Parameter | Type | Required | Description |
|---|---|---|---|
| x-pulse-user-id | string (UUID) | Optional | Anonymous visitor identifier. If provided, the feedback is associated with this user. |
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| slug | string | Required | Project URL slug |
Request Body
| Parameter | Type | Required | Description |
|---|---|---|---|
| title | string | Required | Brief summary of the feedback |
| description | string | Optional | Detailed description |
| category | "bug" | "feature" | "general" | Optional | Category of feedback. Defaults to "general" |
| string | Optional | Contact email for follow-up | |
| page_url | string | Optional | URL of the page where feedback was submitted |
| browser_info | object | Optional | Browser metadata (e.g. userAgent, language). Stored as JSON. |
Response (201 Created)
{
"data": {
"id": "uuid"
},
"error": null
}Error Responses
Invalid category values are silently coerced to "general".
Bug reports are automatically assigned "medium" priority; all others get "low".
List user feedback
Returns all feedback entries submitted by the specified visitor for a project, ordered by created_at descending.
Headers
| Parameter | Type | Required | Description |
|---|---|---|---|
| x-pulse-user-id | string (UUID) | Required | Anonymous visitor identifier |
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| slug | string | Required | Project URL slug |
Response (array of feedback entries)
{
"data": [
{
"id": "uuid",
"title": "Button not working on mobile",
"category": "bug",
"status": "new",
"priority": "medium",
"created_at": "2025-03-15T10:00:00Z"
}
],
"error": null
}Error Responses
Add a message to a feedback thread
Post a follow-up message on an existing feedback entry. Only the original feedback author (identified by x-pulse-user-id) can add messages to their thread.
Headers
| Parameter | Type | Required | Description |
|---|---|---|---|
| x-pulse-user-id | string (UUID) | Required | Must match the user_id of the original feedback entry |
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| slug | string | Required | Project URL slug |
| id | string (UUID) | Required | Feedback entry ID |
Request Body
| Parameter | Type | Required | Description |
|---|---|---|---|
| message | string | Required | The message text |
Response (201 Created)
{
"data": {
"id": "uuid",
"feedback_id": "uuid",
"message": "Here is additional context...",
"sender_role": "user",
"sender_id": "visitor-uuid",
"is_public": true,
"created_at": "2025-03-15T11:00:00Z"
},
"error": null
}Error Responses
Messages are created with sender_role "user" and is_public true.
Only the original author (matched by x-pulse-user-id) can post messages.
Notifications
Retrieve notifications for a visitor and mark them as read. Returns up to 50 notifications per request, ordered by creation date descending.
List notifications
Returns up to 50 notifications for the identified visitor, along with an unread count. Ordered by created_at descending.
Headers
| Parameter | Type | Required | Description |
|---|---|---|---|
| x-pulse-user-id | string (UUID) | Required | Anonymous visitor identifier (must be valid UUID v4) |
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| slug | string | Required | Project URL slug |
Response
{
"data": {
"notifications": [
{
"id": "uuid",
"type": "changelog",
"title": "New version released",
"message": "Version 2.1 is now available",
"link": "/changelog/v2.1",
"metadata": {},
"read_at": null,
"created_at": "2025-03-15T10:00:00Z"
}
],
"unreadCount": 1
},
"error": null
}Error Responses
Mark notifications as read
Mark a single notification or all unread notifications as read. Send notification_id in the body to mark a specific one, or send an empty body to mark all as read.
Headers
| Parameter | Type | Required | Description |
|---|---|---|---|
| x-pulse-user-id | string (UUID) | Required | Anonymous visitor identifier (must be valid UUID v4) |
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| slug | string | Required | Project URL slug |
Request Body
| Parameter | Type | Required | Description |
|---|---|---|---|
| notification_id | string (UUID) | Optional | ID of a specific notification to mark as read. Omit to mark all as read. |
Response
{
"data": {
"read": true
},
"error": null
}Error Responses
An empty body (or invalid JSON) marks all unread notifications as read.
Already-read notifications are not affected.
Announcements
Retrieve active announcements and dismiss them. Announcements support time-based scheduling (starts_at / ends_at) and are automatically filtered to show only currently active entries, ordered by priority descending.
List active announcements
Returns all currently active announcements for a project. Automatically filters by time window (starts_at/ends_at) and active status. If x-pulse-user-id is provided, previously dismissed announcements are excluded.
Headers
| Parameter | Type | Required | Description |
|---|---|---|---|
| x-pulse-user-id | string (UUID) | Optional | If provided, dismissed announcements are filtered out |
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| slug | string | Required | Project URL slug |
Response (array of active announcements)
{
"data": [
{
"id": "uuid",
"title": "Scheduled Maintenance",
"message": "We will be performing maintenance on March 20th.",
"type": "warning",
"banner_color": "#d97706",
"is_dismissible": true,
"priority": 10,
"starts_at": "2025-03-18T00:00:00Z",
"ends_at": "2025-03-21T00:00:00Z"
}
],
"error": null
}Error Responses
Announcements with type values: "info", "warning", "success", "error".
Only announcements where is_active is true and the current time is within the starts_at/ends_at window (if set) are returned.
Dismiss an announcement
Records that a visitor has dismissed a specific announcement. The announcement will no longer appear for this visitor in subsequent GET requests. Only announcements with is_dismissible set to true can be dismissed.
Headers
| Parameter | Type | Required | Description |
|---|---|---|---|
| x-pulse-user-id | string (UUID) | Required | Anonymous visitor identifier |
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| slug | string | Required | Project URL slug |
| id | string (UUID) | Required | Announcement ID |
Response
{
"data": {
"dismissed": true
},
"error": null
}Error Responses
This endpoint is idempotent. Duplicate dismiss calls are safely handled via upsert.
Embeddable Widget
Pulse provides a drop-in JavaScript widget that adds a feedback button, changelog panel, and announcement banners to any website. The widget renders inside a Shadow DOM to avoid CSS conflicts with your site.
Installation
Add the following script tag to your HTML. Replace your-project-slugwith your project’s slug.
<script
src="https://pulse.g8n.ai/api/widget/widget.js"
data-pulse-project="your-project-slug"
data-pulse-user="optional-uuid-for-visitor"
data-pulse-theme="light"
data-pulse-position="bottom-right"
data-pulse-features="feedback,changelog,announcements"
defer
></script>Configuration Attributes
| Attribute | Required | Default | Description |
|---|---|---|---|
| data-pulse-project | Yes | -- | Your project's URL slug |
| data-pulse-user | No | null | UUID to identify the visitor (enables unseen counts, voting, dismissals) |
| data-pulse-api | No | Auto-detected | Override the API base URL |
| data-pulse-theme | No | "light" | "light" or "dark" |
| data-pulse-position | No | "bottom-right" | "bottom-right", "bottom-left", "top-right", "top-left" |
| data-pulse-features | No | "feedback,changelog,announcements" | Comma-separated list of enabled features |
How the Widget Uses the API
On initialization, the widget makes the following API calls based on enabled features:
Visitor ID Pattern
The data-pulse-user attribute (sent as the x-pulse-user-id header) serves as an anonymous visitor identifier. It is analogous to an analytics anonymous ID and is not an authenticated user credential.
If you have authenticated users, you can pass a stable identifier (or a UUID derived from their user ID) to enable per-user features like unseen changelog counts, vote tracking, and announcement dismissals. If you do not provide a visitor ID, the widget will still work for features that do not require it (viewing changelogs, viewing announcements, submitting feedback).
// Generate and persist a visitor ID
function getVisitorId() {
let id = localStorage.getItem('pulse-visitor-id');
if (!id) {
id = crypto.randomUUID();
localStorage.setItem('pulse-visitor-id', id);
}
return id;
}Need Help?
If you run into issues or have questions about the API, reach out to our support team at support@g8n.ai.