Developer API and Webhooks
Connect Sparko to Your Own Systems
Sparko gives your engineering team two building blocks for custom integrations: a read-only public API for pulling people, time-and-leave, and recruiting data on demand, and webhooks that push a signed notification to your endpoint the moment key events happen. Both are managed from Admin Settings and are designed to be safe by default: keys and secrets are shown once, every request is tenant-isolated, and outbound webhooks are cryptographically signed.
Overview
What you can build
- Pull data on a schedule — sync employees into a data warehouse, feed a BI dashboard, or reconcile a downstream system nightly using the public API.
- React to events in real time — kick off a provisioning workflow when someone is hired, notify payroll when a departure is recorded, or update an applicant-tracking mirror when a candidate changes stage.
Two surfaces, two auth models
| Surface | Direction | How it authenticates |
|---|---|---|
| Public API | Your system pulls from Sparko | API key in an X-API-Key header |
| Webhooks | Sparko pushes to your system | HMAC-SHA256 signature you verify with a signing secret |
Availability
The developer API and webhooks are a paid capability. They are available on Growth and above; on Core the endpoints return 403. If you downgrade, existing keys stop authenticating, but you can still sign in to audit and revoke them.
Note: The public API is intentionally read-only. There is no write scope — the API can never create, edit, or delete records in your Sparko tenant.
Part 1 — The Public API
Base URL
https://api.sparko.app/v1
Interactive reference: https://api.sparko.app/v1/docs
Creating an API key
API keys are created and managed under Admin Settings → Integrations → API Keys.
- Go to Admin Settings → Integrations → API Keys
- Click Create API Key
- Give the key a descriptive name (for example, "Data warehouse sync" or "Zapier integration")
- Choose the scopes the key should carry (see below) — pick only what the integration needs
- Click Create key
- Copy the key immediately. It starts with
spk_live_and is shown exactly once. Sparko stores only a one-way hash, so if you lose the key you must create a new one.
Security: Treat the key like a password. Store it in your secrets manager, never in source control or a browser. Only the key's short prefix (for example
spk_live_ab12cd…) is ever shown again, so you can recognize a key in the list without exposing the secret.
Authenticating requests
Send the key in an X-API-Key header:
curl https://api.sparko.app/v1/employees \
-H "X-API-Key: spk_live_your_key_here"
An Authorization: Bearer <key> header works too. Every request is resolved to your company only — the tenant is derived from the key itself, never from anything in the request, so a key can only ever read your own data.
Any authentication problem (missing key, revoked key, expired key) returns a generic 401 Unauthorized with no detail about which key or why. A valid key that lacks the scope an endpoint requires returns 403 Forbidden.
Scopes
A key carries one or more read-only scopes. Each endpoint requires a specific scope, so a key scoped to people.read can list employees but cannot touch recruiting data.
| Scope | Grants read access to |
|---|---|
people.read |
Employees and departments |
timeleave.read |
Leave requests and leave balances |
recruiting.read |
Jobs, applications, and candidate names |
Endpoints
All endpoints are GET only. List endpoints accept skip and limit query parameters for pagination (limit defaults to 50, maximum 200).
| Method | Path | Scope | Returns |
|---|---|---|---|
GET |
/employees |
people.read |
A page of employees |
GET |
/employees/{employee_id} |
people.read |
One employee |
GET |
/leave-requests |
timeleave.read |
A page of leave requests |
GET |
/leave-balances |
timeleave.read |
A page of leave balances |
GET |
/jobs |
recruiting.read |
A page of job postings |
GET |
/jobs/{job_id} |
recruiting.read |
One job posting (with full description) |
GET |
/applications |
recruiting.read |
A page of applications (candidate name + stage) |
What the API returns (and what it does not)
Responses are deliberately trimmed to non-sensitive fields. The API surfaces things like job title, department, employment status, dates, leave balances, and application stage. It never returns deep personal data — no email, phone number, date of birth, national ID, home address, salary, or demographic information. Employee and candidate names are the only decrypted personal fields, and only on the endpoints whose scope you granted.
Example — list employees:
curl "https://api.sparko.app/v1/employees?limit=2" \
-H "X-API-Key: spk_live_your_key_here"
{
"items": [
{
"employee_id": "emp_01H...",
"employee_number": "E-1042",
"first_name": "Emily",
"last_name": "Thomas",
"job_title": "Senior Engineer",
"department": "Engineering",
"employment_status": "active",
"manager_id": "emp_01G...",
"hire_date": "2023-04-01",
"termination_date": null
}
],
"skip": 0,
"limit": 2
}
Rate limits
The public API is limited to 300 requests per minute per API key, counted across every public API endpoint. The budget follows the key, not your network:
- Two integrations that share an office IP or NAT gateway each get their own 300.
- One integration running on ten servers still shares a single 300, because they all present the same key.
- Rejected requests still count, against whatever they present: a request with no key counts against its source address, and a mistyped or revoked key gets a budget of its own. Neither can drain a working key.
The window is a rolling 60 seconds rather than a clock minute: each request counts against the budget until it is 60 seconds old. There is no separate burst allowance.
Over the limit you get 429 Too Many Requests with a Retry-After: 60 header. Space out bulk syncs and use pagination rather than hammering an endpoint.
A second ceiling of 1,500 requests per minute applies per source address to requests that do not carry a working key: unauthenticated calls, and key-guessing floods. Once your key has made one successful call, its own 300 per minute is the only limit that applies to it, even if someone else on your address is being throttled.
Sparko's MCP server has its own separate limit (see MCP server below).
Rotating and revoking keys
- Rotate: Create a new key, update your integration to use it, then revoke the old one. There is no in-place edit — rotation is always "create new, retire old."
- Revoke: Click Revoke next to a key in the list. Revocation is immediate — the next request with that key gets a
401.
Part 2 — Webhooks
Webhooks let Sparko notify your systems the instant something happens, so you do not have to poll the API. When a subscribed event occurs, Sparko sends a signed POST to the URL you configured.
The events you can subscribe to
A subscription listens for one or more of these five events:
| Event | Fires when |
|---|---|
employee.hired |
A new employee is hired |
employee.terminated |
An employee is terminated / offboarded |
leave.approved |
A leave request is approved |
application.stage_changed |
A candidate moves to a new pipeline stage |
offer.accepted |
A candidate accepts an offer |
Creating a subscription
Webhooks are managed under Admin Settings → Integrations → Webhooks.
- Go to Admin Settings → Integrations → Webhooks
- Click Add webhook
- Enter your endpoint URL (must be
https://and publicly reachable) - Optionally add a short description
- Choose which events to receive
- Click Create webhook
- Copy the signing secret immediately. It starts with
whsec_and is shown exactly once. You will use it to verify that incoming webhooks genuinely came from Sparko.
URL safety: Sparko validates your endpoint URL when you save it and re-checks the resolved address again at delivery time. Endpoints that point at private, internal, or loopback addresses are rejected — webhooks can only be delivered to genuinely public destinations.
The webhook payload
Sparko sends a JSON envelope with a stable shape:
{
"id": "del_01H...",
"event": "leave.approved",
"created_at": "2026-07-27T14:03:11+00:00",
"data": {
"request_id": "req_01H...",
"employee_id": "emp_01H..."
}
}
Each delivery also carries these headers:
| Header | Purpose |
|---|---|
X-Sparko-Event |
The event name (for example leave.approved) |
X-Sparko-Delivery |
A unique delivery ID, useful for de-duplication |
X-Sparko-Signature |
The signature you verify (see below) |
Your endpoint should respond with any 2xx status code to acknowledge receipt. Anything else is treated as a failed delivery and retried.
Verifying the signature
Every webhook is signed so you can confirm it came from Sparko and was not tampered with or replayed. The X-Sparko-Signature header looks like this:
X-Sparko-Signature: t=1753624991,v1=4f2c...9ab
tis the Unix timestamp when we signed the request.v1is an HMAC-SHA256 hex digest computed over the string"{t}." + raw_request_body, keyed with your subscription's signing secret.
To verify, recompute the HMAC yourself and compare:
import hashlib
import hmac
import time
def verify(secret: str, header: str, raw_body: bytes, tolerance_seconds: int = 300) -> bool:
parts = dict(p.split("=", 1) for p in header.split(",") if "=" in p)
timestamp, provided = parts.get("t"), parts.get("v1")
if not timestamp or not provided:
return False
# Reject stale deliveries to defend against replays.
if abs(int(time.time()) - int(timestamp)) > tolerance_seconds:
return False
signed = f"{timestamp}.".encode() + raw_body
expected = hmac.new(secret.encode(), signed, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, provided)
Two rules for a correct implementation:
- Sign the raw bytes. Compute the HMAC over the exact request body as received, before any JSON re-serialization. Re-encoding can change bytes and break the signature.
- Enforce the timestamp window. Reject signatures whose
tis more than a few minutes old (Sparko's own tolerance is 5 minutes) so an intercepted request cannot be replayed later.
Use a constant-time comparison (like hmac.compare_digest above) rather than ==.
Testing an endpoint
After you add a webhook, click Test on the subscription. Sparko sends a one-off signed test delivery so you can confirm your endpoint receives the request and verifies the signature correctly — before any real event depends on it.
Delivery, retries, and the dead-letter state
Sparko attempts each delivery immediately when the event fires. If your endpoint does not return a 2xx, the delivery is retried with exponential backoff:
| Attempt | Waits before retrying |
|---|---|
| 2nd | ~1 minute |
| 3rd | ~5 minutes |
| 4th | ~15 minutes |
| 5th | ~1 hour |
| (final) | ~6 hours |
After 5 failed attempts, the delivery is marked dead-letter and no longer retried automatically.
To inspect what happened, click Deliveries on a subscription. Each row shows the event, its status (delivered, failed, or dead_letter), the last HTTP status code your endpoint returned, the attempt count, and when it was sent. You can Redeliver any past delivery to send it again as a fresh attempt — handy after you fix an outage on your side.
Pausing and deleting
- Pause / Resume a subscription to temporarily stop deliveries without losing its configuration or signing secret.
- Delete a subscription to remove it and its delivery history. This cannot be undone.
Who can manage the API and webhooks
Access is controlled by permissions, so you decide which admins can see and change these settings:
| Permission | Allows |
|---|---|
admin.api_keys.view |
See the list of API keys (never the secrets) |
admin.api_keys.manage |
Create and revoke API keys |
admin.webhooks.view |
See webhook subscriptions and their delivery history |
admin.webhooks.manage |
Create, edit, test, redeliver, pause, and delete webhooks |
These are typically held by IT Admin and Super Admin roles. See Roles and Permissions for how to assign them.
Best Practices
✅ Least privilege: Scope each API key to only the data its integration needs
✅ One key per integration: So you can revoke one without breaking the others
✅ Store secrets safely: Keep API keys and signing secrets in a secrets manager, never in code or config committed to source control
✅ Always verify signatures: Reject any webhook whose signature or timestamp does not check out
✅ Make handlers idempotent: Use X-Sparko-Delivery to de-duplicate, since a retried delivery may arrive more than once
✅ Respond fast: Acknowledge with a 2xx quickly and do heavy work asynchronously, so a slow handler is not treated as a failure
✅ Rotate keys periodically: Create a new key, migrate, then revoke the old one
Troubleshooting
Q: My API request returns 401 Unauthorized
- The key is missing, revoked, expired, or mistyped. Confirm you are sending it in the
X-API-Keyheader and that the key is still Active in the list. If in doubt, create a fresh key.
Q: My API request returns 403 Forbidden
- Either your plan does not include API access, or the key is missing the scope the endpoint requires. Check the key's scopes in the list and create a new key with the right scope if needed.
Q: My API request returns 429 Too Many Requests
- That API key has exceeded 300 requests per minute across the public API. Slow down and paginate with
skip/limitinstead of many rapid calls. Splitting the traffic across more servers will not help, because the budget follows the key. If you genuinely need more throughput, run separate integrations on separate keys.
Q: My API request returns 503 Service Unavailable
- Sparko could not reach the service that counts your rate limit, and stops requests rather than letting them through uncounted. It is transient. Retry with backoff.
Q: My webhook signature check keeps failing
- Verify you are computing the HMAC over the raw request body and the string
"{timestamp}." + body, using the exact signing secret shown when you created the subscription. Re-serializing the JSON before hashing is the most common cause.
Q: A webhook shows as dead_letter
- Your endpoint failed all retry attempts. Fix the endpoint, then use Redeliver to resend that delivery.
Q: I lost my API key or signing secret
- Secrets are shown only once and cannot be recovered. Create a new key (or a new webhook subscription) and update your integration.
MCP server (connect your own AI assistant)
Sparko hosts a Model Context Protocol (MCP) endpoint so you can connect an MCP-capable AI client — Claude Desktop, Claude Code, and others — directly to your Sparko workspace.
- MCP endpoint:
https://mcp.sparko.app/mcp - Auth: OAuth 2.1 with PKCE. When you connect, your client opens a browser, you sign in to Sparko and approve the connection on a consent screen. No API key to copy or paste.
- Acts as you. The assistant runs every tool as your own user, limited to exactly the permissions you already have. It can never see or do more than you can in the product.
- Read-only. v1 exposes read-only tools only — the assistant cannot change data, submit requests, or message anyone.
Connecting from Claude Desktop / Claude Code
Add the server URL in your client (for example, claude mcp add --transport http sparko https://mcp.sparko.app/mcp). Your client discovers the sign-in automatically,
you approve access, and the Sparko tools appear. The tools you see are the subset your role
allows — someone with fewer permissions sees fewer tools.
Available tools (read-only)
| Tool | Reads |
|---|---|
| Leave balance | Your own leave balances |
| Team summary | Your team's headcount, OKR progress, and who's out (managers) |
| Employee lookup | The employee directory (no personal contact details) |
| Find mentor | Available mentors |
| Performance summary | Performance summaries for people you manage |
| Hiring plan | Approved hiring-plan positions (Business plan) |
| Backfill info | Backfill candidates for open roles (Business plan) |
Limits and control
- Rate limit: 120 MCP requests per minute per workspace.
- Short-lived access. Tokens expire quickly and refresh automatically; there is no long-lived secret to leak.
- Revoke anytime. Remove a connected app under Settings → Connected apps — this immediately stops it from getting new access.
- Availability: the MCP server is included on Growth plans and above, and follows your workspace's AI setting (if AI is turned off for the workspace, the MCP tools are unavailable).
Need Help?
- Email: [email protected]
- Documentation: https://sparko.app/docs
- Status: https://status.sparko.app
Applies To: Admins on paid Sparko plans Feature Tier: Developer API and webhooks are a paid capability
Still need help? Reach us at [email protected].
← Back to Help Center