Skip to content
seals.deals

🦭 seals.deals · developer platform

The seals.deals API

A REST API for your CRM. Read and write contacts, companies, deals, quotes and more over plain HTTPS — scoped to a key you control, in your own workspace.

BASE https://crm.seals.deals/api/v1 AUTH Bearer <api key>

Introduction

Everything you can see in seals.deals, your integrations can reach over HTTP.

The API is organised around the objects you already work with — contacts, companies, deals, products, activities and quotes. It speaks JSON, uses standard HTTP verbs, and returns predictable status codes. Every request runs inside the workspace that issued the key and is bounded by that key’s permissions, so a key can never see or touch more than its creator can.

  • Base URL — all endpoints live under https://crm.seals.deals/api/v1.
  • JSON everywhere — send Content-Type: application/json; every response is JSON.
  • Envelope — responses are wrapped as { "object": "…", "data": … }.
  • Identifiers are UUIDs; timestamps are ISO 8601 (UTC); money uses ISO 4217 currency codes.

Quickstart

From zero to your first record in three steps.

Create an API key

In seals.deals go to Settings → API keys, create a key, and pick its scopes (read, write, delete). Copy the key — it’s shown once.

Send it as a Bearer token

Add Authorization: Bearer YOUR_API_KEY to every request.

Make a call

List your contacts to confirm it works, then start creating and updating records.

curl https://crm.seals.deals/api/v1/contacts?limit=3 \
  -H "Authorization: Bearer YOUR_API_KEY"

Authentication

One bearer token per integration, scoped and revocable.

Every request must carry an API key as a bearer token:

Authorization: Bearer YOUR_API_KEY

Keys are created in Settings → API keys and hashed at rest — we never store the raw key, so copy it when it’s shown. Requests without a valid, unrevoked key get 401.

Scopes

A key carries one or more scopes. A request that needs a scope the key lacks returns 403.

read

List and retrieve records. Required for every GET.

write

Create and update records — POST and PATCH.

delete

Permanently delete records via DELETE. Grant deliberately.

Reads and writes are additionally bounded by the permissions of the person who created the key. If they can only see records they own, the key sees only those too — the API never widens access.

Requests & responses

Predictable shapes in, predictable shapes out.

Send JSON bodies with Content-Type: application/json. Successful responses are wrapped in an envelope naming the object and carrying the payload in data — an array for lists, a single object for one record.

{
  "object": "contacts",
  "data": [
    {
      "id": "b1e2c3d4-5f6a-4b8c-9d0e-1f2a3b4c5d6e",
      "first_name": "Ada",
      "last_name": "Lovelace",
      "email": "ada@analytical.co",
      "lifecycle_stage": "lead",
      "created_at": "2026-07-09T08:15:00Z"
    }
  ]
}

Pagination & search

List endpoints take four query parameters.

ParameterTypeDescription
limitintRows to return. Default 50, max 100.
offsetintRows to skip, for paging. Default 0.
qstringCase-insensitive search over the object’s key text fields (name, email, number…).
iduuidReturn a single record by id (same as the Retrieve endpoint).
# Page 2 of companies matching "acme", 25 per page
curl "https://crm.seals.deals/api/v1/companies?q=acme&limit=25&offset=25" \
  -H "Authorization: Bearer YOUR_API_KEY"

Errors

Standard HTTP status codes; a JSON body with an error message.

StatusMeaningWhen
200OKRead or update succeeded.
201CreatedA record was created.
400Bad requestInvalid JSON, a bad UUID, or a field that failed validation.
401UnauthorizedMissing, malformed, invalid, or revoked API key.
403ForbiddenThe key is missing the scope this call needs.
404Not foundUnknown object, or the object doesn’t support this verb.
413Payload too largeRequest body exceeds the size limit.
429Too many requestsRate limit exceeded — back off and retry.
500Server errorSomething went wrong on our side.
{ "error": "API key is missing the delete scope" }

Rate limits

Keep it reasonable; handle 429 gracefully.

Requests are rate-limited per key. If you exceed the limit you’ll get a 429 — wait a moment and retry with exponential backoff. Batch your work with limit/offset paging rather than firing many small calls in parallel.

Objects

What you can read, create, update, and delete.

ObjectReadCreateUpdateDeleteNotes
contactsGETPOSTPATCHDELPeople.
companiesGETPOSTPATCHDELOrganisations.
dealsGETPOSTPATCHDELOpportunities in a pipeline.
productsGETPOSTPATCHDELCatalogue line items.
activitiesGETPOSTPATCHDELTasks, notes, calls, meetings, emails.
quotesGETPOSTPATCHDELNumber auto-assigned on create.
quote_itemsGETPOSTPATCHDELLine items; scoped to their parent quote.
pipelinesGETPOSTPATCHDELDeleting one with deals returns 409.
stagesGETPOSTPATCHDELDeleting one with deals returns 409.
formsGETPOSTPATCHDELToken auto-assigned; delete cascades fields + submissions.
form_submissionsGETPOSTPATCHDELInbound form submissions.
email_templatesGETPOSTPATCHDELReusable email templates.
quote_templatesGETPOSTPATCHDELQuote layouts & defaults.
sequencesGETPOSTPATCHDELDelete cascades steps + enrolments.
contractsGETPOSTPATCHDELDocuments.
currenciesGETPOSTPATCHDELOrg currencies & FX rates.
custom_propertiesGETPOSTPATCHDELDelete cascades stored values.

Field reference

Writable fields for the create/update objects. Related ids (company_id, owner_id…) must belong to your workspace.

contacts

FieldTypeNotes
first_namestringAt least one of first_name / last_name / email is required.
last_namestring
emailstring
phonestring
job_titlestring
company_iduuidAssociates the contact with a company.
lifecycle_stagestringe.g. lead, customer. Defaults to lead.
lead_statusstringDefaults to new.
owner_iduuidDefaults to the key’s creator.
city, countrystring

deals

FieldTypeNotes
namestringrequired
amountnumber
currencystringISO 4217, e.g. USD.
pipeline_iduuidWhich pipeline the deal lives in.
stage_iduuidCurrent stage. Accepting a linked quote auto-advances this to Closed Won.
company_iduuid
close_datedateYYYY-MM-DD.
prioritystringlow · medium · high.
forecast_categorystring
sourcestring
owner_iduuid

products

FieldTypeNotes
namestringrequired
skustring
unit_pricenumber
currencystringISO 4217.
tax_ratenumberPercent, e.g. 20.
categorystring
activeboolean
image_urlstring

activities

FieldTypeNotes
typestringtask · note · call · meeting · email.
titlestring
bodystring
contact_id / company_id / deal_iduuidWhat the activity is about.
due_datetimestampFor tasks.
statusstringe.g. open, completed.
end_at, location, meeting_urlFor meetings.

quotes & quote_items

FieldTypeNotes
quotes.titlestringNumber is auto-assigned if omitted.
quotes.deal_id / company_id / contact_iduuidAssociations.
quotes.currencystringISO 4217.
quotes.statusstringdraft · sent · accepted · rejected.
quote_items.quote_iduuidrequired parent quote (in your workspace).
quote_items.namestring
quote_items.quantitynumber
quote_items.unit_pricenumber
quote_items.discountnumberPercent.

List records

GET/v1/{object}

Returns an array of records the key is allowed to see, newest first. Combine with limit, offset and q.

curl "https://crm.seals.deals/api/v1/deals?limit=20" \
  -H "Authorization: Bearer YOUR_API_KEY"

Retrieve one record

GET/v1/{object}?id={uuid}

Fetch a single record by id. Returns data as an array with zero or one element.

curl "https://crm.seals.deals/api/v1/companies?id=3f2a…" \
  -H "Authorization: Bearer YOUR_API_KEY"

Create a record

POST/v1/{object}

Send a JSON object of writable fields. Returns 201 with the created record. Requires the write scope.

curl -X POST https://crm.seals.deals/api/v1/deals \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "VEEWER Premium — Acme",
    "amount": 12000,
    "currency": "USD",
    "company_id": "3f2a…",
    "pipeline_id": "c74d…",
    "stage_id": "248c…"
  }'

Update a record

PATCH/v1/{object}?id={uuid}

Partial update — send only the fields you want to change; everything else is left untouched. Requires the write scope.

curl -X PATCH "https://crm.seals.deals/api/v1/deals?id=f0b8…" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "amount": 15000, "stage_id": "9a1d…" }'

Delete a record

DELETE/v1/{object}?id={uuid}

Permanently removes the record. Requires the delete scope — keys without it get 403.

!
Deletes are permanent. There’s no undo via the API, and some deletes cascade to children — deleting a form removes its submissions, a sequence its enrolments, a custom property its stored values. A pipeline or stage that still has deals returns 409 (reassign them first). Give the delete scope only to keys that truly need it.
curl -X DELETE "https://crm.seals.deals/api/v1/contacts?id=b1e2…" \
  -H "Authorization: Bearer YOUR_API_KEY"

# → 200 { "object": "contacts", "data": { "deleted": true, "id": "b1e2…" } }

Webhooks

Get notified when records change, instead of polling.

Register an endpoint under Settings → API & webhooks and subscribe to events. When a record is created, updated, or deleted, seals.deals sends a signed POST to your URL — so your systems stay in sync in real time without polling.

Events are <object>.created, <object>.updated, and <object>.deleted (e.g. deal.updated, contact.created).

{
  "id": "d3f1…",                 // unique delivery id
  "event": "deal.updated",
  "data": { /* the full record */ },
  "sent_at": "2026-07-10T09:00:00Z"
}
Verify every delivery. Compute HMAC-SHA256(rawBody, yourSecret) and compare it, hex-encoded, against the X-Seals-Signature header (after the sha256= prefix) before trusting a payload.