Docs
Workspace API reference
Read stored workspace intelligence from your own tools and agents. Subscribe to signed change notifications without generating content or bypassing human review.
Prerequisites
- An EarShots workspace with stored data.
- Workspace owner or admin access to create keys.
- An HTTP client that follows same-origin redirects and preserves its Authorization header.
Read your workspace
- 1
Create a key
Open Settings, API access. Choose a name, permission preset and expiry of 1 to 365 days. Override individual permissions when needed. Save the secret before closing, because it appears once. - 2
Set the key locally
Set EARSHOTS_API_KEY in your local environment or secret manager. Do not put it in URLs, source control, browser code or logs. - 3
Send the request
curl --location --fail-with-body \ --header "Authorization: Bearer $EARSHOTS_API_KEY" \ https://earshots.dev/api/v1/workspace
A successful read returns a data object containing id, name, product_name and plan. The key selects its workspace. You cannot supply another workspace identifier.
The canonical entry is /api/v1. It returns a same-origin 307 redirect to /api/public/v1, preserving the method and credentials. If your client cannot follow redirects, use the direct endpoint on earshots.dev.
Read operations
Every operation below is GET-only. Data is stored, not generated on demand. Read scopes grant access to their matching surface only.
| Path | Required scope and result |
|---|---|
| /workspace | workspace:read. Workspace metadata, no filters. |
| /contacts | contacts:readSaved professional contacts with exact profile URLs and separately verified work email when available. Dates filter created_at. No enrichment or credit spending. |
| /mentions | mentions:readNonarchived, deduplicated source posts. Body excerpts are capped at 12,000 characters. |
| /sentiment | sentiment:readDaily listening totals from stored rollups. sentiment_sum / scored gives the average when scored is greater than zero. |
| /ground-truth | ground-truth:readStored findings with verdict, severity and claim references. Historical review findings may appear. Findings are not automatic fact verification. |
| /ground-truth-claims | ground-truth:readObserved claims with literal evidence spans and source URLs. Observation does not establish accuracy. |
| /brain | brain:readCurrent verified Knowledge Hub nodes only. Unconfirmed candidates are excluded. |
| /aeo | aeo:readDaily stored AI Search answer, capture, mention and citation counts. No search or generation is triggered. |
| /competitors | competitors:readTracked competitor names, products and stored summaries. No provider diagnostics. |
| /competitor-signals | competitors:readStored competitor source evidence and sentiment, linked by competitor_id. |
| /context-buzz | context-buzz:readNonarchived GitHub evidence with usage, blocker and agent-authorship categories. |
| /replies | replies:readSaved reply draft text and review state. Reading or saving content does not publish it. |
| /campaigns | campaigns:readStored campaign activation and progress associated with individual prompt themes. |
| /campaign-stages | campaigns:readStored campaign assets, citations and approval timestamps. No generation or approval operation. |
Listen Enrich contacts use contacts:read. Old keys do not gain this permission automatically. Reads never start paid lookups or send messages. Contact dates filter created_at.
Join related records with mention_id, competitor_id, campaign_id, theme_id and claim_ids. Source URLs and literal spans are evidence, not proof that a claim is true. Brain reads exclude unconfirmed candidates. Campaigns remain independent of reply drafting.
OpenAPI 3.1 reference lists response fields, authentication and supported parameters for machine clients.
Filters and pagination
- limit: 1 to 100 records. Default 25.
- since and until: inclusive UTC dates, YYYY-MM-DD, at most 180 days. Defaults are 30 days before today through today.
- cursor: copy meta.next_cursor unchanged into your next request. Keep the date filters fixed. Stop when it is null.
- UUID feeds order by id descending, not publication time. Sentiment and AEO use descending day cursors.
- Dates filter posted_at for Mentions and Context Buzz, day for rollups, and created_at for other collections. For older Brain or competitor records, request their creation window.
- Unknown and duplicate parameters are rejected. Query-string credentials are never accepted.
curl --location --fail-with-body \
--header "Authorization: Bearer $EARSHOTS_API_KEY" \
'https://earshots.dev/api/v1/mentions?limit=25&since=2026-10-01&until=2026-10-22'List responses contain data and meta (limit, next_cursor, since, until, ordering). An empty data array means no matching stored records. It is not a failed request. Rollups can lag recent ingestion. Missing scored evidence is not a neutral or positive sentiment result.
Signed webhooks
POST /webhooks requires webhooks:write. GET /webhooks and GET /webhooks/deliveries require webhooks:read. DELETE /webhooks?id=WEBHOOK_ID requires webhooks:write. Get WEBHOOK_ID from the create or list response.
Supported receivers are exact HTTPS hosts hooks.zapier.com, hook.us1.make.com, hook.us2.make.com and hook.eu1.make.com, on port 443. Custom hosts, IP addresses, credentials, fragments and redirects are not supported. Each workspace can have 10 active subscriptions. JSON requests are limited to 4 KB.
curl --location --fail-with-body \
--header "Authorization: Bearer $EARSHOTS_API_KEY" \
--header 'Content-Type: application/json' \
--data '{"url":"https://hooks.zapier.com/hooks/catch/RECEIVER_ID/RECEIVER_TOKEN/","events":["mention.updated"]}' \
https://earshots.dev/api/v1/webhooksReplace RECEIVER_ID and RECEIVER_TOKEN with the values from your Zapier Catch Hook URL. For Make, use the URL from your custom webhook module. Save signing_secret from the create response. It is not returned by later reads. Revoke and recreate the subscription if it is lost.
- mention.updated: a nonarchived, nonduplicate mention completes triage or changes track/status after triage.
- campaign.stage.updated: a campaign stage is created or changes status.
- ground-truth.finding.created: a stored Ground Truth finding is created.
Notification JSON contains id, type, workspace_id, resource_id and created_at. Fetch content through the matching read operation. Historical changes are not replayed on registration.
Verify EarShots-Signature (v1= followed by hexadecimal HMAC-SHA256) against EarShots-Timestamp + "." + the exact raw request body, using signing_secret. Compare in constant time. Reject timestamps more than five minutes old. Deduplicate by EarShots-Event-Id before processing.
// JavaScript receiver verification, before parsing JSON
import { createHmac, timingSafeEqual } from 'node:crypto';
const expected = createHmac('sha256', signingSecret)
.update(timestamp + '.' + rawBody).digest();
const received = Buffer.from(signature.replace(/^v1=/, ''), 'hex');
const fresh = /^\d+$/.test(timestamp) &&
Math.abs(Date.now() / 1000 - Number(timestamp)) <= 300;
const valid = fresh && received.length === expected.length &&
timingSafeEqual(received, expected);
if (!valid) throw new Error('Invalid webhook signature');Queued delivery uses five attempts, a five-second receiver timeout, and retry delays of 5, 10, 20 and 40 minutes. Actual delivery also depends on the scheduled queue. Return 2xx to acknowledge. Delivery is at-least-once, not exactly-once. No receiver body is stored. A revoked/expired key or removed manager cancels its subscriptions' pending deliveries. GET /webhooks/deliveries returns the latest 100 safe status records, optionally filtered by subscription id.
Errors and recovery
Errors contain error.code and error.message. Responses are private and no-store.
- 400 invalid_parameters: check bounds, dates, cursor, destination and JSON. Do not retry unchanged.
- 401 unauthorized: key missing, invalid, expired, revoked, or its creator is no longer a manager. Create or rotate a key.
- 403 forbidden_scope: grant the required permission with a replacement key.
- 404 not_found: operation or webhook does not exist in the key's workspace.
- 409 webhook_limit: revoke an active subscription before creating another.
- 429 too_many_requests: rate limit exceeded (100 requests per minute sliding window). Inspect Retry-After header.
- 503 temporarily_unavailable: retry with exponential backoff and jitter. Preserve your cursor.
Public API endpoints enforce a 100 requests per minute sliding window rate limit per workspace key (or caller IP address for unauthenticated requests). All API responses include standard rate limit headers (X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset). Use bounded pagination and avoid tight polling loops. Write scopes reserved in key presets do not enable product writes. No REST operation generates content, approves a draft, or posts externally.
Verify access
Read /workspace, then a scoped collection. Confirm the workspace identity and pagination metadata. Revoke a temporary key in Settings and confirm its next read returns 401. Check webhook delivery status separately from subscription creation. Production aliases and receiver delivery must be verified after publication before relying on them.
