Dealmoor API
REST API for the Dealmoor CRM: companies, contacts, deals, activities and email drafts. Version 2.3.0. This account holds invented sample data for the Vortner demo.
Reads and writes
Every GET call only reads and changes nothing. Four calls write: creating an email draft, deleting one, sending one, and resetting the sandbox. Sending is the only call that would email someone outside the company; this account has no verified sending domain, so no email is ever delivered. Companies, contacts, deals and activities are read-only through the API.
| Call | Effect |
|---|---|
GET /v1/users | Only reads |
GET /v1/companies | Only reads |
GET /v1/companies/{id} | Only reads |
GET /v1/contacts | Only reads |
GET /v1/contacts/{id} | Only reads |
GET /v1/deals | Only reads |
GET /v1/deals/{id} | Only reads |
GET /v1/activities | Only reads |
GET /v1/email-drafts | Only reads |
POST /v1/email-drafts | Writes |
GET /v1/email-drafts/{id} | Only reads |
DELETE /v1/email-drafts/{id} | Writes |
POST /v1/email-drafts/{id}/send | Writes and sends an email |
POST /v1/reset | Writes |
Authentication
Base URL https://crm-demo.vortner.dev. Every /v1 request carries an API key:
curl https://crm-demo.vortner.dev/v1/deals \
-H "Authorization: Bearer $DEALMOOR_API_KEY"
Each account has two keys. The production key works on the live records. The sandbox key reads the same records, but the drafts it creates, deletes or sends are kept apart from production and are not visible to the production key; a sandbox send is recorded and no email is delivered. Every authenticated response carries X-Dealmoor-Environment: production or sandbox. A missing or wrong key gets 401 unauthorized. An API key sees the whole account, so decide in your integration who may use it.
Rate limits
120 requests per minute per API key, counted in fixed one-minute windows. Every /v1 response carries:
X-RateLimit-Limit | 120 |
X-RateLimit-Remaining | Requests left in the current window |
X-RateLimit-Reset | Unix time (seconds) when the window ends |
Over the limit the API answers 429 rate_limited with Retry-After in seconds. /openapi.json and this page are not rate limited.
Pagination
The list calls for companies, contacts, deals, activities and email drafts are paginated with page (from 1) and per_page (1 to 100, default 25; larger values get 400). Responses look like:
{
"data": [ ... ],
"meta": { "page": 1, "per_page": 25, "total": 31, "total_pages": 2 },
"links": { "next": "/v1/deals?page=2&per_page=25", "prev": null }
}
Single resources come as {"data": {...}}. The user list is short and returned complete in data.
Errors
{ "error": { "code": "not_found", "message": "No deal with id deal_9999." } }
| Status | Codes |
|---|---|
| 400 | invalid_parameter, invalid_body |
| 401 | unauthorized |
| 403 | sending_disabled, sandbox_only |
| 404 | not_found |
| 405 | method_not_allowed |
| 409 | already_sent |
| 422 | unknown_author, deal_mismatch |
| 429 | rate_limited |
Data model
A company is a customer or prospect with an account owner. A contact works at one company. A deal is with one company and one main contact, has an owner, and moves through the stages lead, qualified, proposal and negotiation until it is won or lost. An activity (call, meeting or note) is logged on a deal by a user. An email draft is written to a contact by a user, optionally about a deal, and stays a draft until it is sent or deleted. Every owner and author is a user of the account, named by email address.
User
| Field | Type | Description |
|---|---|---|
id | string | User id. |
name | string | Full name. |
email | string (email) | Work email address; records name their owner by it. |
title | string | Job title. |
role | admin | manager | member | admin manages the account; manager leads a sales team; member works their own records. |
Company
| Field | Type | Description |
|---|---|---|
id | string | Company id. |
name | string | |
domain | string | Web domain; contact addresses use it. |
industry | string | |
city | string | |
country | string | ISO 3166-1 alpha-2 country code. |
employee_count | integer | Approximate number of employees. |
lifecycle | prospect | customer | former_customer | |
owner_email | string (email) | Email address of the account owner, a Dealmoor user. |
owner_name | string | Name of the account owner. |
created_at | string (date-time) | When the company was added (UTC). |
Contact
| Field | Type | Description |
|---|---|---|
id | string | Contact id. |
company_id | string | The company the contact works at. |
first_name | string | |
last_name | string | |
email | string (email) | Work email address; email drafts go here. |
job_title | string | |
owner_email | string (email) | Email address of the user who owns the contact. |
owner_name | string | Name of the user who owns the contact. |
created_at | string (date-time) | When the contact was added (UTC). |
Deal
| Field | Type | Description |
|---|---|---|
id | string | Deal id. |
name | string | What is being sold. |
company_id | string | The customer or prospect company. |
company_name | string | Name of that company. |
contact_id | string | The main contact at the company. |
owner_email | string (email) | Email address of the deal owner, the user responsible for the deal. |
owner_name | string | Name of the deal owner. |
type | new_business | expansion | renewal | |
stage | lead | qualified | proposal | negotiation | closed_won | closed_lost | |
status | open | won | lost | |
probability | integer | Chance of winning in percent, set by the stage (lead 10, qualified 25, proposal 50, negotiation 75, won 100, lost 0). |
amount | number | Deal value, net, in currency. |
currency | EUR | ISO 4217 currency code. |
expected_close_date | string (date) | When the deal is expected to close; for a closed deal, the day it closed. |
closed_on | string | null (date) | The day the deal was won or lost; null while open. |
lost_reason | string | null | Why a lost deal was lost; null otherwise. |
created_at | string (date-time) | When the deal was created (UTC). |
Activity
| Field | Type | Description |
|---|---|---|
id | string | Activity id. |
deal_id | string | The deal the activity was logged on. |
contact_id | string | null | The contact involved, if any. |
type | call | meeting | note | |
subject | string | One-line summary. |
notes | string | What happened, in the words of the user who logged it. |
owner_email | string (email) | Email address of the user who logged the activity. |
owner_name | string | Name of the user who logged the activity. |
occurred_at | string (date-time) | When the call or meeting took place, or the note was written (UTC). |
duration_minutes | integer | null | Length of a call or meeting in minutes; null for a note. |
EmailDraft
| Field | Type | Description |
|---|---|---|
id | string | Email draft id. |
contact_id | string | The contact the email is to. |
deal_id | string | null | The deal the email is about, if any. |
author_email | string (email) | Email address of the user who wrote it; the email is sent from this address. |
author_name | string | Name of the user who wrote it. |
to | string (email) | Recipient: the contact's email address. |
subject | string | Subject line. |
body | string | Plain-text body. |
status | draft | sent | |
created_at | string (date-time) | When the draft was created (UTC). |
sent_at | string | null (date-time) | When it was sent (UTC); null while a draft. |
Endpoints
GET /openapi.json Only reads
Get this API description
Returns this OpenAPI 3.1 document. No API key is needed and the request does not count toward the rate limit. Only reads; changes nothing.
200The OpenAPI document.
GET /v1/users Only reads
List users
Returns every user with a seat in the account: name, email address, job title and Dealmoor role. The list is complete and not paginated. Only reads; changes nothing.
200The users.401The API key is missing or not valid (unauthorized).429More than 120 requests in the current minute for this API key (rate_limited).500An unexpected error on our side (internal_error). Retry later.
GET /v1/companies Only reads
List companies
Returns the companies of the account, ordered by id. Filters combine with AND. Only reads; changes nothing.
| Parameter | In | Required | Type | Description |
|---|---|---|---|---|
page | query | no | integer | Page number, starting at 1. |
per_page | query | no | integer | Items per page, at most 100. |
owner_email | query | no | string (email) | Only companies owned by the user with this email address (exact match, case-insensitive). |
lifecycle | query | no | prospect | customer | former_customer | Only companies at this point in the relationship. |
q | query | no | string | Only companies whose name or domain contains this text (case-insensitive). |
200A page of companies.400A parameter or the request body is missing or malformed (invalid_parameter,invalid_body).401The API key is missing or not valid (unauthorized).429More than 120 requests in the current minute for this API key (rate_limited).500An unexpected error on our side (internal_error). Retry later.
GET /v1/companies/{id} Only reads
Get a company
Returns one company by id. Only reads; changes nothing.
| Parameter | In | Required | Type | Description |
|---|---|---|---|---|
id | path | yes | string | Company id. |
200The company.401The API key is missing or not valid (unauthorized).404The company, contact, deal or email draft does not exist (not_found).429More than 120 requests in the current minute for this API key (rate_limited).500An unexpected error on our side (internal_error). Retry later.
GET /v1/contacts Only reads
List contacts
Returns the contacts of the account, ordered by id. Filters combine with AND. Only reads; changes nothing.
| Parameter | In | Required | Type | Description |
|---|---|---|---|---|
page | query | no | integer | Page number, starting at 1. |
per_page | query | no | integer | Items per page, at most 100. |
company_id | query | no | string | Only contacts at this company. |
owner_email | query | no | string (email) | Only contacts owned by the user with this email address (exact match, case-insensitive). |
email | query | no | string (email) | Only the contact with this email address (exact match, case-insensitive). |
200A page of contacts.400A parameter or the request body is missing or malformed (invalid_parameter,invalid_body).401The API key is missing or not valid (unauthorized).404The company, contact, deal or email draft does not exist (not_found).429More than 120 requests in the current minute for this API key (rate_limited).500An unexpected error on our side (internal_error). Retry later.
GET /v1/contacts/{id} Only reads
Get a contact
Returns one contact by id. Only reads; changes nothing.
| Parameter | In | Required | Type | Description |
|---|---|---|---|---|
id | path | yes | string | Contact id. |
200The contact.401The API key is missing or not valid (unauthorized).404The company, contact, deal or email draft does not exist (not_found).429More than 120 requests in the current minute for this API key (rate_limited).500An unexpected error on our side (internal_error). Retry later.
GET /v1/deals Only reads
List deals
Returns the deals of the account, ordered by id: open deals in the pipeline and deals already won or lost. Filters combine with AND; filter by status open for the current pipeline. Only reads; changes nothing.
| Parameter | In | Required | Type | Description |
|---|---|---|---|---|
page | query | no | integer | Page number, starting at 1. |
per_page | query | no | integer | Items per page, at most 100. |
owner_email | query | no | string (email) | Only deals owned by the user with this email address (exact match, case-insensitive). |
stage | query | no | lead | qualified | proposal | negotiation | closed_won | closed_lost | Only deals at this pipeline stage. |
status | query | no | open | won | lost | Only open, won or lost deals. |
type | query | no | new_business | expansion | renewal | Only deals of this type. |
company_id | query | no | string | Only deals with this company. |
200A page of deals.400A parameter or the request body is missing or malformed (invalid_parameter,invalid_body).401The API key is missing or not valid (unauthorized).404The company, contact, deal or email draft does not exist (not_found).429More than 120 requests in the current minute for this API key (rate_limited).500An unexpected error on our side (internal_error). Retry later.
GET /v1/deals/{id} Only reads
Get a deal
Returns one deal by id. Only reads; changes nothing.
| Parameter | In | Required | Type | Description |
|---|---|---|---|---|
id | path | yes | string | Deal id. |
200The deal.401The API key is missing or not valid (unauthorized).404The company, contact, deal or email draft does not exist (not_found).429More than 120 requests in the current minute for this API key (rate_limited).500An unexpected error on our side (internal_error). Retry later.
GET /v1/activities Only reads
List activities
Returns the calls, meetings and notes logged on deals, ordered by id. The owner is the user who logged the activity, who is not always the deal's owner. Filters combine with AND. Only reads; changes nothing.
| Parameter | In | Required | Type | Description |
|---|---|---|---|---|
page | query | no | integer | Page number, starting at 1. |
per_page | query | no | integer | Items per page, at most 100. |
deal_id | query | no | string | Only activities on this deal. |
owner_email | query | no | string (email) | Only activities owned by the user with this email address (exact match, case-insensitive). |
type | query | no | call | meeting | note | Only activities of this type. |
200A page of activities.400A parameter or the request body is missing or malformed (invalid_parameter,invalid_body).401The API key is missing or not valid (unauthorized).404The company, contact, deal or email draft does not exist (not_found).429More than 120 requests in the current minute for this API key (rate_limited).500An unexpected error on our side (internal_error). Retry later.
GET /v1/email-drafts Only reads
List email drafts
Returns the email drafts of the account, ordered by id, including drafts already sent. Filters combine with AND. Only reads; changes nothing.
| Parameter | In | Required | Type | Description |
|---|---|---|---|---|
page | query | no | integer | Page number, starting at 1. |
per_page | query | no | integer | Items per page, at most 100. |
contact_id | query | no | string | Only drafts to this contact. |
author_email | query | no | string (email) | Only drafts written by the user with this email address (exact match, case-insensitive). |
status | query | no | draft | sent | Only drafts with this status. |
200A page of email drafts.400A parameter or the request body is missing or malformed (invalid_parameter,invalid_body).401The API key is missing or not valid (unauthorized).404The company, contact, deal or email draft does not exist (not_found).429More than 120 requests in the current minute for this API key (rate_limited).500An unexpected error on our side (internal_error). Retry later.
POST /v1/email-drafts Writes
Create an email draft
Writes: creates an email draft to a contact, written by a Dealmoor user. Nothing is sent: the draft waits with status draft until someone sends or deletes it. The recipient is the contact's email address. A deal_id, when given, must be a deal with the contact's company.
Body (JSON):
| Field | Required | Type | Description |
|---|---|---|---|
contact_id | yes | string | The contact to write to. |
deal_id | no | string | null | Optional: a deal with the contact's company that the email is about. |
author_email | yes | string (email) | Email address of the Dealmoor user writing the draft. |
subject | yes | string | Subject line. |
body | yes | string | Plain-text body. |
201The draft was created with statusdraft.400A parameter or the request body is missing or malformed (invalid_parameter,invalid_body).401The API key is missing or not valid (unauthorized).404The company, contact, deal or email draft does not exist (not_found).422The draft is well-formed but cannot be created:unknown_author(the author is not a user of this account) ordeal_mismatch(the deal is with another company).429More than 120 requests in the current minute for this API key (rate_limited).500An unexpected error on our side (internal_error). Retry later.
GET /v1/email-drafts/{id} Only reads
Get an email draft
Returns one email draft by id, with its subject, body, recipient and status. Only reads; changes nothing.
| Parameter | In | Required | Type | Description |
|---|---|---|---|---|
id | path | yes | string | Email draft id. |
200The email draft.401The API key is missing or not valid (unauthorized).404The company, contact, deal or email draft does not exist (not_found).429More than 120 requests in the current minute for this API key (rate_limited).500An unexpected error on our side (internal_error). Retry later.
DELETE /v1/email-drafts/{id} Writes
Delete an email draft
Writes: deletes an email draft that has not been sent. A sent email stays on record and cannot be deleted (409 already_sent).
| Parameter | In | Required | Type | Description |
|---|---|---|---|---|
id | path | yes | string | Email draft id. |
204The draft was deleted. No body.401The API key is missing or not valid (unauthorized).404The company, contact, deal or email draft does not exist (not_found).409The draft was already sent.429More than 120 requests in the current minute for this API key (rate_limited).500An unexpected error on our side (internal_error). Retry later.
POST /v1/email-drafts/{id}/send Writes and sends an email
Send an email draft
Writes and sends: sends the draft as an email from its author to the contact, and marks it sent. A sent email cannot be recalled. This account has no verified sending domain, so with the production key the call answers 403 sending_disabled and nothing changes. With the sandbox key the send is recorded (status sent, sent_at set) and no email is delivered. No request body.
| Parameter | In | Required | Type | Description |
|---|---|---|---|---|
id | path | yes | string | Email draft id. |
200The draft as sent, and what happened to the email.401The API key is missing or not valid (unauthorized).403Sending is not enabled for this account (sending_disabled, production key).404The company, contact, deal or email draft does not exist (not_found).409The draft was already sent.429More than 120 requests in the current minute for this API key (rate_limited).500An unexpected error on our side (internal_error). Retry later.
POST /v1/reset Writes
Reset the sandbox
Writes: clears everything the sandbox key has written (drafts created, drafts deleted, sends recorded), so the sandbox shows the account as it was. Sandbox key only; the production key gets 403 sandbox_only. No request body.
200The sandbox was reset.401The API key is missing or not valid (unauthorized).403Called with the production key (sandbox_only).429More than 120 requests in the current minute for this API key (rate_limited).500An unexpected error on our side (internal_error). Retry later.