PulseWise API Documentation
Welcome to the PulseWise API documentation. This RESTful API provides access to healthcare data and analytics.
Base URL
https://pulsewise.com
Version
v1
Response Format
JSON
Getting Started
Quick Start Guide
- Register for an API account and obtain your API key
- Include your API key in the Authorization header of all requests
- Make your first API call to test connectivity
- Explore the available endpoints below
Test Your Connection
curl -H "Authorization: Bearer YOUR_API_KEY" \
https://pulsewise.com/api/v1/status
Authentication
PulseWise API uses Bearer token authentication. Include your API key in the Authorization header:
Authorization: Bearer YOUR_API_KEY
API Endpoints
entitlement it returns
403 FEATURE_RESTRICTED.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
Code Examples
Try It Live
Response:
Error:
Sample Response
Log Monitor ingest
Your application posts an hourly summary of what happened; PulseWise tells you when those numbers stop looking like your application. Everything below is what you need to write a client — see Log Monitor to create a source and get its key.
Send every hour, including empty ones
Post each hour even when every count is zero. A zero is a measurement; a missing hour is the absence of one. If quiet hours are skipped, "nobody used the site" becomes indistinguishable from "the site stopped reporting" — and a source that stops reporting is treated as down. Skipping empty hours will get you paged for an outage you do not have.
https://pulsewise.com/api/v1/logs/ingest
X-Ingest-Key: ls_…. This is the source's own key, not a
pk_ API key — it can only write that one source's counts, and revoking it
does not affect your API access.
Request
{
"hours": [
{
"hour": "2026-09-08T14:00:00Z", // start of the hour, ISO 8601, UTC
"events": { // omit a type, or send 0 — same thing
"login_success": 42,
"login_failure": 3,
"password_reset": 1
},
"distinct_users": 31, // optional
"distinct_ips": 28, // optional
"new_ips": 4, // optional
"changes": [ // optional — see Notable changes
{"event": "plugin_installed", "subject": "wordfence", "count": 1}
]
}
]
}
Batching
Up to 48 hours per request. Send one hour at a time normally; send several to catch up after downtime.
Safe to retry
Re-posting an hour replaces it rather than adding to it, so a timeout you are unsure about can simply be retried. A duplicate would otherwise look exactly like a spike.
Accepted window
Backfill up to 7 days. Hours more than 90 minutes ahead of our clock are refused — check the clock on the sending server.
Event types
Send whichever apply. Unknown names are ignored rather than rejected, so a client reporting something newer than this server does not lose the hours that came with it.
| Key | Meaning | Assessed |
|---|---|---|
| login_success | Successful logins | In the hour's total traffic |
| login_failure | Failed logins | Per hour, against that hour of the week |
| page_view | Page views | In the hour's total traffic |
| password_reset | Password resets | Over a rolling window |
| user_created | Users created | Over a rolling window |
| user_deleted | Users deleted | Over a rolling window |
| role_changed | Roles changed | Over a rolling window |
| plugin_activated | Plugins activated | Every occurrence (Notable changes) |
| plugin_deactivated | Plugins deactivated | Every occurrence (Notable changes) |
| theme_changed | Themes changed | Every occurrence (Notable changes) |
| core_updated | Core updated | Every occurrence (Notable changes) |
| post_published | Posts published | In the hour's total traffic |
| file_changed | Files changed | Over a rolling window |
| admin_login | Administrator logins | Over a rolling window |
| plugin_installed | Plugins installed | Every occurrence (Notable changes) |
| plugin_updated | Plugins updated | Every occurrence (Notable changes) |
| plugin_deleted | Plugins deleted | Every occurrence (Notable changes) |
| theme_installed | Themes installed | Every occurrence (Notable changes) |
| theme_updated | Themes updated | Every occurrence (Notable changes) |
| theme_deleted | Themes deleted | Every occurrence (Notable changes) |
| admin_granted | Administrator rights granted | Every occurrence (Notable changes) |
| option_changed | Critical settings changed | Every occurrence (Notable changes) |
| file_edited | Files edited from the dashboard | Every occurrence (Notable changes) |
| app_password_created | Application passwords created | Every occurrence (Notable changes) |
| page_published | Pages published | Over a rolling window |
| post_updated | Published posts edited | Over a rolling window |
| page_updated | Published pages edited | Over a rolling window |
| post_trashed | Posts trashed | Over a rolling window |
| page_trashed | Pages trashed | Over a rolling window |
| post_deleted | Posts deleted | Over a rolling window |
| page_deleted | Pages deleted | Over a rolling window |
| user_updated | User profiles updated | Over a rolling window |
| password_changed | Passwords changed | Over a rolling window |
| not_found | Page-not-found (404) responses | Per hour, against that hour of the week |
| comment_posted | Comments posted | Over a rolling window |
| comment_spam | Comments marked as spam | Per hour, against that hour of the week |
Types added after the first release (404s, comments, content edits and the change events)
count as measured only when you send them — send 0 for a quiet hour rather than
leaving the key out, or PulseWise cannot tell "none happened" from "not counted".
Notable changes
Some things are worth hearing about every time, however normal their rate: a plugin installed at
3am, a new administrator. List them in the hour's changes array and each is reported
once as its own finding. subject names what changed — a plugin or theme
slug, a setting's name, a file path, a version — and must never name who: no
usernames, emails or post titles. Also count each one in events under the same
name. Up to 200 entries per hour; unknown events are ignored.
| Event | Reported as | Severity |
|---|---|---|
| admin_granted | A user was given administrator rights | ALERT |
| option_changed | Critical site setting changed | ALERT |
| file_edited | A file was saved from the dashboard editor | ALERT |
| app_password_created | An application password was created | ALERT |
| plugin_installed | Plugin installed | REVIEW |
| plugin_updated | Plugin updated | REVIEW |
| plugin_deleted | Plugin deleted | REVIEW |
| plugin_activated | Plugin activated | REVIEW |
| plugin_deactivated | Plugin deactivated | REVIEW |
| theme_installed | Theme installed | REVIEW |
| theme_updated | Theme updated | REVIEW |
| theme_deleted | Theme deleted | REVIEW |
| theme_changed | Active theme switched | REVIEW |
| core_updated | WordPress updated | REVIEW |
Response
HTTP 202
{
"success": true,
"accepted": 1,
"rejected": [], // anything refused is named, never silently dropped
"next_expected_hour": "2026-09-08T15:00:00+00:00"
}
202 with a non-empty rejected array means some hours were not
stored and why. Log it — a client that ignores it will keep resending the same rejected hours
forever while its operator believes the site is monitored.
401 is an unknown or rotated key, 403 a paused source,
422 a malformed body.
A worked example
curl -X POST https://pulsewise.com/api/v1/logs/ingest -H "X-Ingest-Key: ls_your_key_here" -H "Content-Type: application/json" -d '{"hours":[{"hour":"2026-09-08T14:00:00Z","events":{"login_success":42,"login_failure":3},"distinct_users":31}]}'
In a Laravel application, an hourly scheduled command is usually the whole client:
// Runs hourly. Reports the hour that has just closed — including
// the hours when nothing happened, which is the point.
$hour = now()->utc()->subHour()->startOfHour();
Http::withHeaders(['X-Ingest-Key' => config('services.pulsewise.ingest_key')])
->post('https://pulsewise.com/api/v1/logs/ingest', [
'hours' => [[
'hour' => $hour->toIso8601String(),
'events' => [
'login_success' => Activity::type('login')->inHour($hour)->count(),
'login_failure' => Activity::type('login_failed')->inHour($hour)->count(),
'password_reset' => Activity::type('password_reset')->inHour($hour)->count(),
],
'distinct_users' => Activity::inHour($hour)->distinct('user_id')->count(),
]],
])->throw();
Send counts, not records
The endpoint accepts numbers and nothing else. No usernames, email addresses, IP addresses or log lines are wanted or stored — which keeps your users' data on your server, keeps this well clear of GDPR obligations, and means a breach here would reveal how busy you were and nothing more.
Site Monitor — scheduled malware scanning
The /api/malware/{target} endpoint above is a one-shot
check: it fetches a page and tells you whether known malware is being served right now. With
no history to compare against, it cannot tell you that something changed.
Site Monitor is the stateful half. Register a website and PulseWise fetches its home page — and any same-domain sub-pages you name — on a cadence you choose, then reports what it finds.
ALERT
Known malware is being served. A payload signature matched, packed JavaScript was found inlined in the page, or a script appeared from a host that is on a threat feed or that the site has never used before.
REVIEW
Something changed since the baseline you accepted. On a live site this is usually a plugin or theme update. Kept separate from alerts on purpose — blurring the two is what makes a scanner unreadable.
The baseline only moves when you move it
Changes are reported every scan until you accept the current state of the site. Nothing is adopted automatically, because a baseline that updates itself would absorb a slow injection one scan at a time.
Email only when there is something new
A report is sent only when a scan finds something the previous scan did not, so a change you have seen and chosen to leave unaccepted does not mail you every hour. A scan that finds nothing is silent.
Error Codes
| Code | Status | Description |
|---|---|---|
| 200 | OK | Request successful |
| 400 | Bad Request | Invalid request parameters |
| 401 | Unauthorized | Invalid or missing API key |
| 403 | Forbidden | Access denied |
| 404 | Not Found | Resource not found |
| 429 | Too Many Requests | Rate limit exceeded |
| 500 | Internal Server Error | Server error |
Rate Limits
The PulseWise API implements rate limiting to ensure fair usage and maintain service quality.
Rate Limit
1000 requests per hour
Rate Limit Headers
X-RateLimit-Limit
X-RateLimit-Remaining
X-RateLimit-Reset
Support
Need help with the PulseWise API? Here are some resources: