SealDeal API
A REST API over your contacts, companies, lists, deals, sequences, drafts, action items, suppression list, and platform events, plus analytics and webhooks for event driven integrations. All 58 operations are documented below. Machine readable OpenAPI 3.1 schema: /api-docs/openapi.json
Base URL: https://sealdeal.ai/api/v1
Authentication
Every request is authenticated with an API key belonging to exactly one organization. Mint keys at /admin/integrations/api-keys, choosing the scopes the key should carry. The secret is shown once at creation and stored only as a hash, so it cannot be recovered later.
curl https://sealdeal.ai/api/v1/me \
-H "Authorization: Bearer obk_live_<your-secret>"A missing header returns 401, an unknown or expired key returns 401, and a valid key that lacks the scope a route requires returns 403 naming the missing scope.
Scopes
| Scope | Grants |
|---|---|
| contacts.read | Read contacts and their research dossiers. |
| contacts.write | Create, update, and soft delete contacts, convert inbound leads, act on action items. |
| contacts.erase | Permanently and irreversibly erase a contact (GDPR Article 17). Also requires the acting key to be attributed to an org admin role (manager, admin, or owner), the scope alone is not sufficient. |
| lists.read | Read contact lists. |
| lists.write | Create, generate drafts for, and soft delete lists. |
| deals.read | Read deals and companies. |
| deals.write | Create and update deals, and move them between stages. |
| sequences.read | Read contact sequences. |
| sequences.write | Enqueue and pause sequences. |
| drafts.read | Read drafts and messages. |
| drafts.write | Approve and reject drafts. |
| drafts.send | Enqueue an approved draft for real sending. |
| suppression.write | Add, look up, and remove suppression entries. |
| actions.read | Read action items (the NBA work queue). |
| events.read | Read the platform event log and usage counters. |
| analytics.read | Read aggregate funnel and lift analytics. |
| webhooks.manage | Register, inspect, and delete webhook endpoints. |
Rate limits
- 60 requests per minute per API key (default).
- 120 requests per 30 seconds (burst window) for short bursts.
- Response headers:
X-RateLimit-Limit,X-RateLimit-Reset. - On exhaustion: 429 Too Many Requests with a
Retry-Afterheader.
Pagination
List endpoints return cursor paginated results:
{
"data": [ ... ],
"nextCursor": "eyJpZCI6Ii4uLiIsInQiOiIyMDI2LTA1LTIzVDEwOjAwOjAwLjAwMFoifQ"
}Pass ?cursor=<value> on the next call to fetch the following page. A null nextCursor means the last page. Default page size is 50 and the maximum is 200, set with ?limit=.
Errors
Error responses use RFC 7807 application/problem+json:
{
"type": "https://sealdeal.ai/errors/insufficient-scope",
"title": "Insufficient scope",
"status": 403,
"detail": "The API key is missing required scopes: deals.write."
}Endpoints
Identity
| Method | Path | Scope | Description |
|---|---|---|---|
| GET | /api/v1/me | any valid key | The calling key’s organization (id and name) plus the scopes it was granted. Useful as a connection test. |
Contacts
| Method | Path | Scope | Description |
|---|---|---|---|
| GET | /api/v1/contacts | contacts.read | List contacts. Cursor paginated. Filters: listId, qualificationStatus, q. Each item includes deletedAt and erasedAt (both null for a live contact). Pass ?includeDeleted=true to also see soft deleted contacts, the only way to see one over this API. |
| POST | /api/v1/contacts | contacts.write | Create a contact. |
| GET | /api/v1/contacts/:id | contacts.read | Fetch a single contact. Pass ?include=research to embed the cached research dossier under a research key (null when none has been computed yet). |
| PATCH | /api/v1/contacts/:id | contacts.write | Update a contact. |
| GET | /api/v1/contacts/:id/research | contacts.read | The cached research dossier for a contact (sources, recent news, pain points). |
| POST | /api/v1/contacts/:id/research | contacts.write | Refresh the research dossier for a contact. |
| POST | /api/v1/contacts/:id/pause-sequence | sequences.write | Pause the contact’s active outreach sequence. |
| DELETE | /api/v1/contacts/:id | contacts.write | Soft delete a contact. Reversible, and the operation most callers want. The contact and its history are kept, it disappears from default reads and the send queue, and an admin can restore it later (undoAvailableUntil is advisory only, not a hard deadline). Idempotent. This is not the GDPR verb, see the erase action below for that. |
| POST | /api/v1/contacts/:id/erase | contacts.erase | Permanently and irreversibly erase a contact (GDPR Article 17). Requires the contacts.erase scope, and the acting API key must additionally be attributed to an org admin role (manager, admin, or owner). Holding the scope alone is not enough, a rep owned key with the scope still gets a 403. Body requires requesterRef and reason. Returns 503 if this deployment has no erasure hash secret configured. |
Companies
| Method | Path | Scope | Description |
|---|---|---|---|
| GET | /api/v1/companies | deals.read | List companies. Cursor paginated. Optional ?search= filters on name or domain. |
| GET | /api/v1/companies/:id | deals.read | Fetch a single company, including industry, size, headquarters, tier, tags, and a research source count. |
| GET | /api/v1/companies/:id/research | deals.read | The cached research dossier for a company (sources, recent news, pain points). |
| POST | /api/v1/companies/:id/research | deals.write | Trigger a fresh, company seeded research run for a company (bypasses cache). |
Contact lists
| Method | Path | Scope | Description |
|---|---|---|---|
| GET | /api/v1/lists | lists.read | List contact lists. Cursor paginated. Pass ?includeArchived=1 to include archived lists. |
| POST | /api/v1/lists | lists.write | Create a contact list. |
| GET | /api/v1/lists/:id | lists.read | Fetch a single contact list. |
| POST | /api/v1/lists/:id/generate | lists.write | Generate outreach drafts for the contacts on a list. |
| DELETE | /api/v1/lists/:id | lists.write | Soft delete a list and its memberships. Contacts always survive, a contact whose only membership was this list becomes unlisted, never deleted. Returns 409 if the list has in flight sequences unless you pass ?force=true, which pauses them first instead of stranding them mid cadence. Idempotent. |
Deals
Every deal (list and detail) carries forecastCategory, readinessScore, and lossReason, plus probability (always 0 to 100, never a 0 to 1 fraction). Pass ?include=meddpic on either GET to also get the qualification framework blob, since it can be large and most callers do not need it.
| Method | Path | Scope | Description |
|---|---|---|---|
| GET | /api/v1/deals | deals.read | List deals. Cursor paginated. Filter: stageSlug. Optional ?include=meddpic. |
| POST | /api/v1/deals | deals.write | Create a deal. |
| GET | /api/v1/deals/:id | deals.read | Fetch a single deal. Same shape as a list item. Optional ?include=meddpic. |
| PATCH | /api/v1/deals/:id | deals.write | Update a deal. |
| POST | /api/v1/deals/:id/stage | deals.write | Move a deal to a new pipeline stage. Records the transition in stage history. |
| GET | /api/v1/deals/:id/history | deals.read | The deal’s pipeline stage transition log, newest first. Cursor paginated. |
Sequences
| Method | Path | Scope | Description |
|---|---|---|---|
| GET | /api/v1/sequences | sequences.read | List contact sequences. Filters: contactId, status. |
| POST | /api/v1/sequences | sequences.write | Enqueue a sequence for a contact against a sequence template. |
Drafts and messages
| Method | Path | Scope | Description |
|---|---|---|---|
| GET | /api/v1/drafts | drafts.read | List drafts. Cursor paginated. Filters: status, contactId. |
| GET | /api/v1/drafts/:id | drafts.read | Fetch a single draft, including the full body and its engagement timeline. Pass ?include=evidence to add resolved citations, an unresolved citation count, the guardrail report, and the effective model, so a reviewer can see why the AI wrote what it wrote, not just what it wrote. |
| POST | /api/v1/drafts/:id/approve | drafts.write | Approve a draft. |
| POST | /api/v1/drafts/:id/reject | drafts.write | Reject a draft. |
| POST | /api/v1/drafts/:id/send | drafts.send | Enqueue an approved draft’s sequence for real sending. Gated by the platform send kill switch and by per-organization transport verification. |
| GET | /api/v1/messages | drafts.read | List inbound and outbound messages. Cursor paginated. |
Inbound leads
| Method | Path | Scope | Description |
|---|---|---|---|
| POST | /api/v1/leads/:id/convert | contacts.write | Convert a captured inbound lead into a contact (and, where policy allows, a deal). |
| POST | /api/v1/leads/:id/spam | contacts.write | Mark a captured inbound lead as spam. |
Actions
Each action declares its own required scope; the key must carry that scope in addition to being valid.
| Method | Path | Scope | Description |
|---|---|---|---|
| POST | /api/v1/actions | per action | Invoke a named capability by key. Returns 403 when the key lacks that capability’s scope. |
| GET | /api/v1/action-items | actions.read | List action items (the NBA work queue). Cursor paginated. Filters: status, priority, entityType, entityId, salesPersonId. |
| GET | /api/v1/action-items/:id | actions.read | Fetch a single action item. |
| POST | /api/v1/action-items/:id/:verb | contacts.write | Apply a verb (for example complete or dismiss) to an action item. |
Suppression
There is no dedicated suppression.read scope. Reading (GET) reuses suppression.write, since every caller that can suppress an address is already trusted with the lesser act of looking one up.
| Method | Path | Scope | Description |
|---|---|---|---|
| GET | /api/v1/suppression | suppression.write | Pass ?email= to look up a single address, or omit it to list the org’s suppression entries. Cursor paginated in list mode. |
| POST | /api/v1/suppression | suppression.write | Suppress an email address or domain so no future outreach can be sent to it. Idempotent. |
| DELETE | /api/v1/suppression | suppression.write | Lift a suppression with ?email=. Idempotent, always 200, even when no entry existed. |
Events and usage
| Method | Path | Scope | Description |
|---|---|---|---|
| GET | /api/v1/events | events.read | The platform event log, cursor paginated. Filter with eventType (a single type or a comma separated list). |
| GET | /api/v1/usage | events.read | Usage counters for the organization. |
Analytics
Aggregate only, GET only. No per contact or per person row is ever returned. Every numeric field names its unit (_count, _cents, _pct, _pp), and a rate is null, never 0, when its denominator is 0.
| Method | Path | Scope | Description |
|---|---|---|---|
| GET | /api/v1/analytics/funnel | analytics.read | The outbound to pipeline funnel over a required, bounded window (?from=&to=, YYYY-MM-DD, both inclusive, maximum 366 days). Attribution is sequence grain, single attribution, so dealsAttributed can be less than dealsCreatedAllSources by design, and truncation.dealScanTruncated marks every number as a floor rather than a total when the scan limit is hit. |
| GET | /api/v1/analytics/lift | analytics.read | The two holdout experiments (email lift and AI actions lift). Takes no date range, since a holdout experiment is measured from assignment to a pre registered horizon, not an arbitrary window. lift_pp and its confidence interval are real percentage points, converted from the underlying fraction. |
Webhooks
Webhook management is how event driven integrations subscribe. See the Webhooks section below for the delivery contract.
| Method | Path | Scope | Description |
|---|---|---|---|
| GET | /api/v1/webhooks | webhooks.manage | List registered webhook endpoints. |
| POST | /api/v1/webhooks | webhooks.manage | Register an endpoint. Body: url plus eventTypes (an array of event types, or ["*"] for all). Returns the signing secret once. |
| GET | /api/v1/webhooks/events | webhooks.manage | The catalogue of event types available to subscribe to. |
| GET | /api/v1/webhooks/:id | webhooks.manage | Fetch a single webhook endpoint. |
| PATCH | /api/v1/webhooks/:id | webhooks.manage | Update an endpoint’s url, subscribed event types, or status. |
| DELETE | /api/v1/webhooks/:id | webhooks.manage | Delete an endpoint. Used by consumers to unsubscribe. |
| GET | /api/v1/webhooks/:id/deliveries | webhooks.manage | List delivery attempts for an endpoint, with status and response code. |
| GET | /api/v1/webhooks/:id/deliveries/:deliveryId | webhooks.manage | Fetch a single delivery attempt, including the request body that was sent. |
| POST | /api/v1/webhooks/:id/deliveries/:deliveryId/replay | webhooks.manage | Replay a previous delivery attempt. |
| POST | /api/v1/webhooks/:id/rotate-secret | webhooks.manage | Rotate the signing secret. The new secret is returned once. |
| POST | /api/v1/webhooks/:id/test-deliver | webhooks.manage | Send a synthetic test payload to the endpoint and return the HTTP status and latency. |
Public content
This endpoint requires no authentication and allows any origin.
| Method | Path | Scope | Description |
|---|---|---|---|
| GET | /api/v1/public-templates | none | Active public sequence templates. Filters: useCase, industry, sequenceStep. |
Webhooks
Register an endpoint with POST /api/v1/webhooks (or from /admin/integrations/webhooks), naming the event types to subscribe to. Retrieve the catalogue of available types from GET /api/v1/webhooks/events.
Deliveries arrive as a stable JSON envelope:
{
"apiVersion": "2026-07-21",
"id": "evt_2a7f9c00example",
"type": "deal.won",
"createdAt": "2026-07-21T10:14:00.000Z",
"organizationId": "org_example",
"data": { "dealId": "d_1", "dealName": "Acme, Platform" }
}Each delivery carries an X-Outbound-Signature header in the form t=<unix-seconds>,v1=<hex>. Verify it by computing HMAC-SHA256("<t>." + rawBody, signingSecret) and comparing in constant time. The signing secret is returned once when the endpoint is created and can be rotated with POST /api/v1/webhooks/:id/rotate-secret.
Failed deliveries are retried with backoff. Inspect attempts with GET /api/v1/webhooks/:id/deliveries and replay an individual attempt if needed.
Multi-tenancy
Every API key belongs to exactly one organization, and every endpoint scopes its queries by that key's organization. There is no cross-tenant access path: a key minted by one organization cannot read another's data even if it knows the other's resource ids.