{
  "openapi": "3.1.0",
  "info": {
    "title": "Dealmoor API",
    "version": "2.3.0",
    "summary": "Companies, contacts, deals, activities and email drafts for one Dealmoor CRM account.",
    "description": "The Dealmoor API gives programmatic access to the sales records of one CRM account: the customer and prospect\ncompanies, their contacts, the deals in the pipeline with their owner, stage and amount, the calls, meetings and\nnotes logged on deals, and email drafts to contacts. All requests and responses are JSON. Dates are ISO 8601\ncalendar dates (`YYYY-MM-DD`); timestamps are UTC. Amounts are net, in EUR. This account (Alder & Finch) holds\ninvented sample data for the Vortner demo.\n\n**Reads and writes.** Every `GET` only reads and changes nothing. Four calls write: `POST /v1/email-drafts` creates\na draft, `DELETE /v1/email-drafts/{id}` deletes one, `POST /v1/email-drafts/{id}/send` sends one as an email to the\ncontact, and `POST /v1/reset` (sandbox key only) clears what the sandbox key has written. Companies, contacts, deals\nand activities are read-only through the API.\n\n**Authentication.** Every `/v1` request carries an API key as `Authorization: Bearer <key>`. Each account has\ntwo keys: a *production* key, which works on the live records, and a *sandbox* key, which reads the same records but\nkeeps everything it writes apart from production. Every response says which one served it in the\n`X-Dealmoor-Environment` header (`production` or `sandbox`). API keys see the whole account; restrict who may use a\nkey in your own integration.\n\n**Owners.** Every company, contact, deal and activity has an owner, and every draft an author: a Dealmoor user,\nnamed by `owner_email` (or `author_email`) and `owner_name`. `GET /v1/users` lists the users.\n\n**Rate limits.** 120 requests per minute per API key, counted in fixed one-minute windows. Every response carries\n`X-RateLimit-Limit`, `X-RateLimit-Remaining` and `X-RateLimit-Reset` (Unix time in seconds when the window resets).\nOver the limit the API answers `429` with a `Retry-After` header in seconds.\n\n**Pagination.** List endpoints take `page` (from 1) and `per_page` (1 to 100, default 25) and return `meta` with\n`page`, `per_page`, `total` and `total_pages`, plus `links.next` and `links.prev` (relative URLs, or null).\n\n**Errors.** Errors are `{\"error\": {\"code\": \"...\", \"message\": \"...\"}}` with a stable machine-readable `code` and a\nhuman-readable `message`.",
    "contact": {
      "name": "Dealmoor Developer Support",
      "url": "https://crm-demo.vortner.dev/docs"
    }
  },
  "servers": [
    {
      "url": "https://crm-demo.vortner.dev",
      "description": "Dealmoor API"
    }
  ],
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "tags": [
    {
      "name": "Users",
      "description": "The people with a seat in this Dealmoor account; they own records."
    },
    {
      "name": "Companies",
      "description": "Customer and prospect companies."
    },
    {
      "name": "Contacts",
      "description": "People at those companies."
    },
    {
      "name": "Deals",
      "description": "Sales opportunities with an owner, a stage, an amount and an expected close date."
    },
    {
      "name": "Activities",
      "description": "Calls, meetings and notes logged on deals."
    },
    {
      "name": "Email drafts",
      "description": "Emails to contacts, written as drafts and sent on request."
    },
    {
      "name": "Sandbox",
      "description": "Tools for the sandbox key."
    },
    {
      "name": "Meta",
      "description": "This API description."
    }
  ],
  "paths": {
    "/openapi.json": {
      "get": {
        "operationId": "getOpenApi",
        "tags": [
          "Meta"
        ],
        "summary": "Get this API description",
        "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.",
        "security": [],
        "responses": {
          "200": {
            "description": "The OpenAPI document.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      }
    },
    "/v1/users": {
      "get": {
        "operationId": "listUsers",
        "tags": [
          "Users"
        ],
        "summary": "List users",
        "description": "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.",
        "responses": {
          "200": {
            "description": "The users.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              },
              "X-Dealmoor-Environment": {
                "$ref": "#/components/headers/X-Dealmoor-Environment"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/User"
                      }
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "usr_8001",
                      "name": "Helena Marsh",
                      "email": "helena.marsh@alderfinch.example",
                      "title": "Chief Executive Officer",
                      "role": "member"
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/companies": {
      "get": {
        "operationId": "listCompanies",
        "tags": [
          "Companies"
        ],
        "summary": "List companies",
        "description": "Returns the companies of the account, ordered by id. Filters combine with AND. Only reads; changes nothing.",
        "parameters": [
          {
            "$ref": "#/components/parameters/page"
          },
          {
            "$ref": "#/components/parameters/per_page"
          },
          {
            "name": "owner_email",
            "in": "query",
            "required": false,
            "description": "Only companies owned by the user with this email address (exact match, case-insensitive).",
            "schema": {
              "type": "string",
              "format": "email"
            },
            "example": "sofia.petrova@alderfinch.example"
          },
          {
            "name": "lifecycle",
            "in": "query",
            "required": false,
            "description": "Only companies at this point in the relationship.",
            "schema": {
              "$ref": "#/components/schemas/CompanyLifecycle"
            }
          },
          {
            "name": "q",
            "in": "query",
            "required": false,
            "description": "Only companies whose name or domain contains this text (case-insensitive).",
            "schema": {
              "type": "string"
            },
            "example": "foods"
          }
        ],
        "responses": {
          "200": {
            "description": "A page of companies.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              },
              "X-Dealmoor-Environment": {
                "$ref": "#/components/headers/X-Dealmoor-Environment"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta",
                    "links"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Company"
                      }
                    },
                    "meta": {
                      "$ref": "#/components/schemas/PageMeta"
                    },
                    "links": {
                      "$ref": "#/components/schemas/PageLinks"
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "cmp_3001",
                      "name": "Merrow Valley Foods",
                      "domain": "merrowvalley.example",
                      "industry": "Food production",
                      "city": "Hamburg",
                      "country": "DE",
                      "employee_count": 640,
                      "lifecycle": "customer",
                      "owner_email": "sofia.petrova@alderfinch.example",
                      "owner_name": "Sofia Petrova",
                      "created_at": "2026-04-22T09:10:00Z"
                    }
                  ],
                  "meta": {
                    "page": 1,
                    "per_page": 25,
                    "total": 1,
                    "total_pages": 1
                  },
                  "links": {
                    "next": null,
                    "prev": null
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/companies/{id}": {
      "get": {
        "operationId": "getCompany",
        "tags": [
          "Companies"
        ],
        "summary": "Get a company",
        "description": "Returns one company by id. Only reads; changes nothing.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Company id.",
            "schema": {
              "type": "string"
            },
            "example": "cmp_3001"
          }
        ],
        "responses": {
          "200": {
            "description": "The company.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              },
              "X-Dealmoor-Environment": {
                "$ref": "#/components/headers/X-Dealmoor-Environment"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Company"
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "cmp_3001",
                    "name": "Merrow Valley Foods",
                    "domain": "merrowvalley.example",
                    "industry": "Food production",
                    "city": "Hamburg",
                    "country": "DE",
                    "employee_count": 640,
                    "lifecycle": "customer",
                    "owner_email": "sofia.petrova@alderfinch.example",
                    "owner_name": "Sofia Petrova",
                    "created_at": "2026-04-22T09:10:00Z"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/contacts": {
      "get": {
        "operationId": "listContacts",
        "tags": [
          "Contacts"
        ],
        "summary": "List contacts",
        "description": "Returns the contacts of the account, ordered by id. Filters combine with AND. Only reads; changes nothing.",
        "parameters": [
          {
            "$ref": "#/components/parameters/page"
          },
          {
            "$ref": "#/components/parameters/per_page"
          },
          {
            "name": "company_id",
            "in": "query",
            "required": false,
            "description": "Only contacts at this company.",
            "schema": {
              "type": "string"
            },
            "example": "cmp_3001"
          },
          {
            "name": "owner_email",
            "in": "query",
            "required": false,
            "description": "Only contacts owned by the user with this email address (exact match, case-insensitive).",
            "schema": {
              "type": "string",
              "format": "email"
            },
            "example": "sofia.petrova@alderfinch.example"
          },
          {
            "name": "email",
            "in": "query",
            "required": false,
            "description": "Only the contact with this email address (exact match, case-insensitive).",
            "schema": {
              "type": "string",
              "format": "email"
            },
            "example": "greta.hollis@merrowvalley.example"
          }
        ],
        "responses": {
          "200": {
            "description": "A page of contacts.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              },
              "X-Dealmoor-Environment": {
                "$ref": "#/components/headers/X-Dealmoor-Environment"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta",
                    "links"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Contact"
                      }
                    },
                    "meta": {
                      "$ref": "#/components/schemas/PageMeta"
                    },
                    "links": {
                      "$ref": "#/components/schemas/PageLinks"
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "con_4001",
                      "company_id": "cmp_3001",
                      "first_name": "Greta",
                      "last_name": "Hollis",
                      "email": "greta.hollis@merrowvalley.example",
                      "job_title": "Head of Operations",
                      "owner_email": "sofia.petrova@alderfinch.example",
                      "owner_name": "Sofia Petrova",
                      "created_at": "2026-04-22T09:12:00Z"
                    }
                  ],
                  "meta": {
                    "page": 1,
                    "per_page": 25,
                    "total": 1,
                    "total_pages": 1
                  },
                  "links": {
                    "next": null,
                    "prev": null
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/contacts/{id}": {
      "get": {
        "operationId": "getContact",
        "tags": [
          "Contacts"
        ],
        "summary": "Get a contact",
        "description": "Returns one contact by id. Only reads; changes nothing.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Contact id.",
            "schema": {
              "type": "string"
            },
            "example": "con_4001"
          }
        ],
        "responses": {
          "200": {
            "description": "The contact.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              },
              "X-Dealmoor-Environment": {
                "$ref": "#/components/headers/X-Dealmoor-Environment"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Contact"
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "con_4001",
                    "company_id": "cmp_3001",
                    "first_name": "Greta",
                    "last_name": "Hollis",
                    "email": "greta.hollis@merrowvalley.example",
                    "job_title": "Head of Operations",
                    "owner_email": "sofia.petrova@alderfinch.example",
                    "owner_name": "Sofia Petrova",
                    "created_at": "2026-04-22T09:12:00Z"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/deals": {
      "get": {
        "operationId": "listDeals",
        "tags": [
          "Deals"
        ],
        "summary": "List deals",
        "description": "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.",
        "parameters": [
          {
            "$ref": "#/components/parameters/page"
          },
          {
            "$ref": "#/components/parameters/per_page"
          },
          {
            "name": "owner_email",
            "in": "query",
            "required": false,
            "description": "Only deals owned by the user with this email address (exact match, case-insensitive).",
            "schema": {
              "type": "string",
              "format": "email"
            },
            "example": "sofia.petrova@alderfinch.example"
          },
          {
            "name": "stage",
            "in": "query",
            "required": false,
            "description": "Only deals at this pipeline stage.",
            "schema": {
              "$ref": "#/components/schemas/DealStage"
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "description": "Only open, won or lost deals.",
            "schema": {
              "$ref": "#/components/schemas/DealStatus"
            }
          },
          {
            "name": "type",
            "in": "query",
            "required": false,
            "description": "Only deals of this type.",
            "schema": {
              "$ref": "#/components/schemas/DealType"
            }
          },
          {
            "name": "company_id",
            "in": "query",
            "required": false,
            "description": "Only deals with this company.",
            "schema": {
              "type": "string"
            },
            "example": "cmp_3001"
          }
        ],
        "responses": {
          "200": {
            "description": "A page of deals.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              },
              "X-Dealmoor-Environment": {
                "$ref": "#/components/headers/X-Dealmoor-Environment"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta",
                    "links"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Deal"
                      }
                    },
                    "meta": {
                      "$ref": "#/components/schemas/PageMeta"
                    },
                    "links": {
                      "$ref": "#/components/schemas/PageLinks"
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "deal_5002",
                      "name": "Analytics add-on",
                      "company_id": "cmp_3001",
                      "company_name": "Merrow Valley Foods",
                      "contact_id": "con_4002",
                      "owner_email": "sofia.petrova@alderfinch.example",
                      "owner_name": "Sofia Petrova",
                      "type": "expansion",
                      "stage": "proposal",
                      "status": "open",
                      "probability": 50,
                      "amount": 9600,
                      "currency": "EUR",
                      "expected_close_date": "2026-12-11",
                      "closed_on": null,
                      "lost_reason": null,
                      "created_at": "2026-10-19T09:00:00Z"
                    }
                  ],
                  "meta": {
                    "page": 1,
                    "per_page": 25,
                    "total": 1,
                    "total_pages": 1
                  },
                  "links": {
                    "next": null,
                    "prev": null
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/deals/{id}": {
      "get": {
        "operationId": "getDeal",
        "tags": [
          "Deals"
        ],
        "summary": "Get a deal",
        "description": "Returns one deal by id. Only reads; changes nothing.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Deal id.",
            "schema": {
              "type": "string"
            },
            "example": "deal_5002"
          }
        ],
        "responses": {
          "200": {
            "description": "The deal.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              },
              "X-Dealmoor-Environment": {
                "$ref": "#/components/headers/X-Dealmoor-Environment"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Deal"
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "deal_5002",
                    "name": "Analytics add-on",
                    "company_id": "cmp_3001",
                    "company_name": "Merrow Valley Foods",
                    "contact_id": "con_4002",
                    "owner_email": "sofia.petrova@alderfinch.example",
                    "owner_name": "Sofia Petrova",
                    "type": "expansion",
                    "stage": "proposal",
                    "status": "open",
                    "probability": 50,
                    "amount": 9600,
                    "currency": "EUR",
                    "expected_close_date": "2026-12-11",
                    "closed_on": null,
                    "lost_reason": null,
                    "created_at": "2026-10-19T09:00:00Z"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/activities": {
      "get": {
        "operationId": "listActivities",
        "tags": [
          "Activities"
        ],
        "summary": "List activities",
        "description": "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.",
        "parameters": [
          {
            "$ref": "#/components/parameters/page"
          },
          {
            "$ref": "#/components/parameters/per_page"
          },
          {
            "name": "deal_id",
            "in": "query",
            "required": false,
            "description": "Only activities on this deal.",
            "schema": {
              "type": "string"
            },
            "example": "deal_5001"
          },
          {
            "name": "owner_email",
            "in": "query",
            "required": false,
            "description": "Only activities owned by the user with this email address (exact match, case-insensitive).",
            "schema": {
              "type": "string",
              "format": "email"
            },
            "example": "sofia.petrova@alderfinch.example"
          },
          {
            "name": "type",
            "in": "query",
            "required": false,
            "description": "Only activities of this type.",
            "schema": {
              "$ref": "#/components/schemas/ActivityType"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of activities.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              },
              "X-Dealmoor-Environment": {
                "$ref": "#/components/headers/X-Dealmoor-Environment"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta",
                    "links"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Activity"
                      }
                    },
                    "meta": {
                      "$ref": "#/components/schemas/PageMeta"
                    },
                    "links": {
                      "$ref": "#/components/schemas/PageLinks"
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "act_6001",
                      "deal_id": "deal_5001",
                      "contact_id": "con_4001",
                      "type": "meeting",
                      "subject": "Demo for the operations team",
                      "notes": "Showed scheduling and reporting to Greta Hollis and four shift leads. Main question: importing the existing rota data.",
                      "owner_email": "sofia.petrova@alderfinch.example",
                      "owner_name": "Sofia Petrova",
                      "occurred_at": "2026-06-18T09:00:00Z",
                      "duration_minutes": 60
                    }
                  ],
                  "meta": {
                    "page": 1,
                    "per_page": 25,
                    "total": 1,
                    "total_pages": 1
                  },
                  "links": {
                    "next": null,
                    "prev": null
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/email-drafts": {
      "get": {
        "operationId": "listEmailDrafts",
        "tags": [
          "Email drafts"
        ],
        "summary": "List email drafts",
        "description": "Returns the email drafts of the account, ordered by id, including drafts already sent. Filters combine with AND. Only reads; changes nothing.",
        "parameters": [
          {
            "$ref": "#/components/parameters/page"
          },
          {
            "$ref": "#/components/parameters/per_page"
          },
          {
            "name": "contact_id",
            "in": "query",
            "required": false,
            "description": "Only drafts to this contact.",
            "schema": {
              "type": "string"
            },
            "example": "con_4008"
          },
          {
            "name": "author_email",
            "in": "query",
            "required": false,
            "description": "Only drafts written by the user with this email address (exact match, case-insensitive).",
            "schema": {
              "type": "string",
              "format": "email"
            },
            "example": "sofia.petrova@alderfinch.example"
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "description": "Only drafts with this status.",
            "schema": {
              "$ref": "#/components/schemas/DraftStatus"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of email drafts.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              },
              "X-Dealmoor-Environment": {
                "$ref": "#/components/headers/X-Dealmoor-Environment"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "meta",
                    "links"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/EmailDraft"
                      }
                    },
                    "meta": {
                      "$ref": "#/components/schemas/PageMeta"
                    },
                    "links": {
                      "$ref": "#/components/schemas/PageLinks"
                    }
                  }
                },
                "example": {
                  "data": [
                    {
                      "id": "drf_7001",
                      "contact_id": "con_4008",
                      "deal_id": "deal_5003",
                      "author_email": "sofia.petrova@alderfinch.example",
                      "author_name": "Sofia Petrova",
                      "to": "martin.kreuzer@pellingham.example",
                      "subject": "Your offer for 150 seats over three years",
                      "body": "Dear Mr Kreuzer,\n\nthank you for the open discussion on Thursday. As agreed, here is the revised offer for 150 seats over three years with a 10 percent discount. The hosting clause now names our data centre in Frankfurt.\n\nCould we settle the last points in a short call next week?\n\nBest regards,\nSofia Petrova\nAlder & Finch",
                      "status": "draft",
                      "created_at": "2026-11-09T08:30:00Z",
                      "sent_at": null
                    }
                  ],
                  "meta": {
                    "page": 1,
                    "per_page": 25,
                    "total": 1,
                    "total_pages": 1
                  },
                  "links": {
                    "next": null,
                    "prev": null
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "post": {
        "operationId": "createEmailDraft",
        "tags": [
          "Email drafts"
        ],
        "summary": "Create an email draft",
        "description": "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.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/EmailDraftCreate"
              },
              "example": {
                "contact_id": "con_4008",
                "deal_id": "deal_5003",
                "author_email": "sofia.petrova@alderfinch.example",
                "subject": "Follow-up on our call",
                "body": "Dear Mr Kreuzer,\n\nthank you for your time today."
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The draft was created with status `draft`.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              },
              "X-Dealmoor-Environment": {
                "$ref": "#/components/headers/X-Dealmoor-Environment"
              },
              "Location": {
                "description": "Path of the new draft.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/EmailDraft"
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "drf_3f9c2a71d0be",
                    "contact_id": "con_4008",
                    "deal_id": "deal_5003",
                    "author_email": "sofia.petrova@alderfinch.example",
                    "author_name": "Sofia Petrova",
                    "to": "martin.kreuzer@pellingham.example",
                    "subject": "Your offer for 150 seats over three years",
                    "body": "Dear Mr Kreuzer,\n\nthank you for the open discussion on Thursday. As agreed, here is the revised offer for 150 seats over three years with a 10 percent discount. The hosting clause now names our data centre in Frankfurt.\n\nCould we settle the last points in a short call next week?\n\nBest regards,\nSofia Petrova\nAlder & Finch",
                    "status": "draft",
                    "created_at": "2026-11-09T08:30:00Z",
                    "sent_at": null
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "description": "The draft is well-formed but cannot be created: `unknown_author` (the author is not a user of this account) or `deal_mismatch` (the deal is with another company).",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              },
              "X-Dealmoor-Environment": {
                "$ref": "#/components/headers/X-Dealmoor-Environment"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "unknown_author",
                    "message": "someone@elsewhere.example is not a Dealmoor user of this account."
                  }
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/email-drafts/{id}": {
      "get": {
        "operationId": "getEmailDraft",
        "tags": [
          "Email drafts"
        ],
        "summary": "Get an email draft",
        "description": "Returns one email draft by id, with its subject, body, recipient and status. Only reads; changes nothing.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Email draft id.",
            "schema": {
              "type": "string"
            },
            "example": "drf_7001"
          }
        ],
        "responses": {
          "200": {
            "description": "The email draft.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              },
              "X-Dealmoor-Environment": {
                "$ref": "#/components/headers/X-Dealmoor-Environment"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/EmailDraft"
                    }
                  }
                },
                "example": {
                  "data": {
                    "id": "drf_7001",
                    "contact_id": "con_4008",
                    "deal_id": "deal_5003",
                    "author_email": "sofia.petrova@alderfinch.example",
                    "author_name": "Sofia Petrova",
                    "to": "martin.kreuzer@pellingham.example",
                    "subject": "Your offer for 150 seats over three years",
                    "body": "Dear Mr Kreuzer,\n\nthank you for the open discussion on Thursday. As agreed, here is the revised offer for 150 seats over three years with a 10 percent discount. The hosting clause now names our data centre in Frankfurt.\n\nCould we settle the last points in a short call next week?\n\nBest regards,\nSofia Petrova\nAlder & Finch",
                    "status": "draft",
                    "created_at": "2026-11-09T08:30:00Z",
                    "sent_at": null
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "delete": {
        "operationId": "deleteEmailDraft",
        "tags": [
          "Email drafts"
        ],
        "summary": "Delete an email draft",
        "description": "Writes: deletes an email draft that has not been sent. A sent email stays on record and cannot be deleted (`409 already_sent`).",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Email draft id.",
            "schema": {
              "type": "string"
            },
            "example": "drf_7001"
          }
        ],
        "responses": {
          "204": {
            "description": "The draft was deleted. No body.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              },
              "X-Dealmoor-Environment": {
                "$ref": "#/components/headers/X-Dealmoor-Environment"
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "The draft was already sent.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              },
              "X-Dealmoor-Environment": {
                "$ref": "#/components/headers/X-Dealmoor-Environment"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "already_sent",
                    "message": "Email draft drf_7001 was sent on 2026-11-11T09:30:00Z; a sent email stays on record."
                  }
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/email-drafts/{id}/send": {
      "post": {
        "operationId": "sendEmailDraft",
        "tags": [
          "Email drafts"
        ],
        "summary": "Send an email draft",
        "description": "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.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Email draft id.",
            "schema": {
              "type": "string"
            },
            "example": "drf_7001"
          }
        ],
        "responses": {
          "200": {
            "description": "The draft as sent, and what happened to the email.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              },
              "X-Dealmoor-Environment": {
                "$ref": "#/components/headers/X-Dealmoor-Environment"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SendResult"
                },
                "example": {
                  "data": {
                    "id": "drf_7001",
                    "contact_id": "con_4008",
                    "deal_id": "deal_5003",
                    "author_email": "sofia.petrova@alderfinch.example",
                    "author_name": "Sofia Petrova",
                    "to": "martin.kreuzer@pellingham.example",
                    "subject": "Your offer for 150 seats over three years",
                    "body": "Dear Mr Kreuzer,\n\nthank you for the open discussion on Thursday. As agreed, here is the revised offer for 150 seats over three years with a 10 percent discount. The hosting clause now names our data centre in Frankfurt.\n\nCould we settle the last points in a short call next week?\n\nBest regards,\nSofia Petrova\nAlder & Finch",
                    "status": "sent",
                    "created_at": "2026-11-09T08:30:00Z",
                    "sent_at": "2026-11-11T09:30:00Z"
                  },
                  "delivery": {
                    "delivered": false,
                    "note": "Sandbox key: the send is recorded on the draft and no email leaves Dealmoor."
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Sending is not enabled for this account (`sending_disabled`, production key).",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              },
              "X-Dealmoor-Environment": {
                "$ref": "#/components/headers/X-Dealmoor-Environment"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "sending_disabled",
                    "message": "Sending is not enabled for this account: no sending domain is verified. With the sandbox key a send is recorded and nothing is delivered."
                  }
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "The draft was already sent.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              },
              "X-Dealmoor-Environment": {
                "$ref": "#/components/headers/X-Dealmoor-Environment"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "already_sent",
                    "message": "Email draft drf_7001 was already sent on 2026-11-11T09:30:00Z."
                  }
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/v1/reset": {
      "post": {
        "operationId": "resetSandbox",
        "tags": [
          "Sandbox"
        ],
        "summary": "Reset the sandbox",
        "description": "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.",
        "responses": {
          "200": {
            "description": "The sandbox was reset.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              },
              "X-Dealmoor-Environment": {
                "$ref": "#/components/headers/X-Dealmoor-Environment"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResetResult"
                },
                "example": {
                  "data": {
                    "environment": "sandbox",
                    "reset": true
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "Called with the production key (`sandbox_only`).",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              },
              "X-Dealmoor-Environment": {
                "$ref": "#/components/headers/X-Dealmoor-Environment"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "sandbox_only",
                    "message": "Reset works with the sandbox key only; production records are never reset through the API."
                  }
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "An API key sent as `Authorization: Bearer <key>`. Each account has a production key (live records) and a sandbox key (same records; writes kept apart from production, and sends recorded without delivery). The `X-Dealmoor-Environment` response header says which one served the request."
      }
    },
    "parameters": {
      "page": {
        "name": "page",
        "in": "query",
        "required": false,
        "description": "Page number, starting at 1.",
        "schema": {
          "type": "integer",
          "minimum": 1,
          "default": 1
        }
      },
      "per_page": {
        "name": "per_page",
        "in": "query",
        "required": false,
        "description": "Items per page, at most 100.",
        "schema": {
          "type": "integer",
          "minimum": 1,
          "maximum": 100,
          "default": 25
        }
      }
    },
    "headers": {
      "X-RateLimit-Limit": {
        "description": "Requests allowed per one-minute window for this API key (120).",
        "schema": {
          "type": "integer"
        }
      },
      "X-RateLimit-Remaining": {
        "description": "Requests left in the current window.",
        "schema": {
          "type": "integer"
        }
      },
      "X-RateLimit-Reset": {
        "description": "Unix time in seconds when the current window ends.",
        "schema": {
          "type": "integer"
        }
      },
      "X-Dealmoor-Environment": {
        "description": "Which key served the request: `production` or `sandbox`.",
        "schema": {
          "type": "string",
          "enum": [
            "production",
            "sandbox"
          ]
        }
      }
    },
    "responses": {
      "BadRequest": {
        "description": "A parameter or the request body is missing or malformed (`invalid_parameter`, `invalid_body`).",
        "headers": {
          "X-RateLimit-Limit": {
            "$ref": "#/components/headers/X-RateLimit-Limit"
          },
          "X-RateLimit-Remaining": {
            "$ref": "#/components/headers/X-RateLimit-Remaining"
          },
          "X-RateLimit-Reset": {
            "$ref": "#/components/headers/X-RateLimit-Reset"
          },
          "X-Dealmoor-Environment": {
            "$ref": "#/components/headers/X-Dealmoor-Environment"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": {
                "code": "invalid_parameter",
                "message": "stage must be one of: lead, qualified, proposal, negotiation, closed_won, closed_lost."
              }
            }
          }
        }
      },
      "Unauthorized": {
        "description": "The API key is missing or not valid (`unauthorized`).",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": {
                "code": "unauthorized",
                "message": "Missing or invalid API key. Send Authorization: Bearer <key>."
              }
            }
          }
        }
      },
      "NotFound": {
        "description": "The company, contact, deal or email draft does not exist (`not_found`).",
        "headers": {
          "X-RateLimit-Limit": {
            "$ref": "#/components/headers/X-RateLimit-Limit"
          },
          "X-RateLimit-Remaining": {
            "$ref": "#/components/headers/X-RateLimit-Remaining"
          },
          "X-RateLimit-Reset": {
            "$ref": "#/components/headers/X-RateLimit-Reset"
          },
          "X-Dealmoor-Environment": {
            "$ref": "#/components/headers/X-Dealmoor-Environment"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": {
                "code": "not_found",
                "message": "No deal with id deal_9999."
              }
            }
          }
        }
      },
      "RateLimited": {
        "description": "More than 120 requests in the current minute for this API key (`rate_limited`).",
        "headers": {
          "X-RateLimit-Limit": {
            "$ref": "#/components/headers/X-RateLimit-Limit"
          },
          "X-RateLimit-Remaining": {
            "$ref": "#/components/headers/X-RateLimit-Remaining"
          },
          "X-RateLimit-Reset": {
            "$ref": "#/components/headers/X-RateLimit-Reset"
          },
          "X-Dealmoor-Environment": {
            "$ref": "#/components/headers/X-Dealmoor-Environment"
          },
          "Retry-After": {
            "description": "Seconds until the window resets.",
            "schema": {
              "type": "integer"
            }
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": {
                "code": "rate_limited",
                "message": "Rate limit of 120 requests per minute exceeded. Retry after 17 seconds."
              }
            }
          }
        }
      },
      "InternalError": {
        "description": "An unexpected error on our side (`internal_error`). Retry later.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": {
                "code": "internal_error",
                "message": "An unexpected error occurred."
              }
            }
          }
        }
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "object",
            "required": [
              "code",
              "message"
            ],
            "properties": {
              "code": {
                "type": "string",
                "description": "Stable machine-readable code.",
                "examples": [
                  "not_found"
                ]
              },
              "message": {
                "type": "string",
                "description": "Human-readable explanation."
              }
            }
          }
        }
      },
      "PageMeta": {
        "type": "object",
        "required": [
          "page",
          "per_page",
          "total",
          "total_pages"
        ],
        "properties": {
          "page": {
            "type": "integer",
            "description": "The page returned."
          },
          "per_page": {
            "type": "integer",
            "description": "Items per page."
          },
          "total": {
            "type": "integer",
            "description": "Items across all pages."
          },
          "total_pages": {
            "type": "integer",
            "description": "Number of pages (at least 1)."
          }
        }
      },
      "PageLinks": {
        "type": "object",
        "required": [
          "next",
          "prev"
        ],
        "properties": {
          "next": {
            "type": [
              "string",
              "null"
            ],
            "description": "Relative URL of the next page, or null on the last page."
          },
          "prev": {
            "type": [
              "string",
              "null"
            ],
            "description": "Relative URL of the previous page, or null on the first page."
          }
        }
      },
      "User": {
        "type": "object",
        "required": [
          "id",
          "name",
          "email",
          "title",
          "role"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "User id.",
            "examples": [
              "usr_8004"
            ]
          },
          "name": {
            "type": "string",
            "description": "Full name."
          },
          "email": {
            "type": "string",
            "format": "email",
            "description": "Work email address; records name their owner by it."
          },
          "title": {
            "type": "string",
            "description": "Job title."
          },
          "role": {
            "type": "string",
            "enum": [
              "admin",
              "manager",
              "member"
            ],
            "description": "`admin` manages the account; `manager` leads a sales team; `member` works their own records."
          }
        }
      },
      "CompanyLifecycle": {
        "type": "string",
        "enum": [
          "prospect",
          "customer",
          "former_customer"
        ],
        "description": "`prospect`: not yet a customer; `customer`: buys today; `former_customer`: bought before and stopped."
      },
      "Company": {
        "type": "object",
        "required": [
          "id",
          "name",
          "domain",
          "industry",
          "city",
          "country",
          "employee_count",
          "lifecycle",
          "owner_email",
          "owner_name",
          "created_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "Company id.",
            "examples": [
              "cmp_3001"
            ]
          },
          "name": {
            "type": "string"
          },
          "domain": {
            "type": "string",
            "description": "Web domain; contact addresses use it."
          },
          "industry": {
            "type": "string"
          },
          "city": {
            "type": "string"
          },
          "country": {
            "type": "string",
            "description": "ISO 3166-1 alpha-2 country code.",
            "examples": [
              "DE"
            ]
          },
          "employee_count": {
            "type": "integer",
            "description": "Approximate number of employees."
          },
          "lifecycle": {
            "$ref": "#/components/schemas/CompanyLifecycle"
          },
          "owner_email": {
            "type": "string",
            "format": "email",
            "description": "Email address of the account owner, a Dealmoor user."
          },
          "owner_name": {
            "type": "string",
            "description": "Name of the account owner."
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "description": "When the company was added (UTC)."
          }
        }
      },
      "Contact": {
        "type": "object",
        "required": [
          "id",
          "company_id",
          "first_name",
          "last_name",
          "email",
          "job_title",
          "owner_email",
          "owner_name",
          "created_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "Contact id.",
            "examples": [
              "con_4001"
            ]
          },
          "company_id": {
            "type": "string",
            "description": "The company the contact works at."
          },
          "first_name": {
            "type": "string"
          },
          "last_name": {
            "type": "string"
          },
          "email": {
            "type": "string",
            "format": "email",
            "description": "Work email address; email drafts go here."
          },
          "job_title": {
            "type": "string"
          },
          "owner_email": {
            "type": "string",
            "format": "email",
            "description": "Email address of the user who owns the contact."
          },
          "owner_name": {
            "type": "string",
            "description": "Name of the user who owns the contact."
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "description": "When the contact was added (UTC)."
          }
        }
      },
      "DealStage": {
        "type": "string",
        "enum": [
          "lead",
          "qualified",
          "proposal",
          "negotiation",
          "closed_won",
          "closed_lost"
        ],
        "description": "Pipeline stage, in order. `closed_won` and `closed_lost` end the deal."
      },
      "DealStatus": {
        "type": "string",
        "enum": [
          "open",
          "won",
          "lost"
        ],
        "description": "`open` until the deal reaches `closed_won` (`won`) or `closed_lost` (`lost`)."
      },
      "DealType": {
        "type": "string",
        "enum": [
          "new_business",
          "expansion",
          "renewal"
        ],
        "description": "`new_business`: a first sale; `expansion`: more for an existing customer; `renewal`: the next term of a subscription."
      },
      "Deal": {
        "type": "object",
        "required": [
          "id",
          "name",
          "company_id",
          "company_name",
          "contact_id",
          "owner_email",
          "owner_name",
          "type",
          "stage",
          "status",
          "probability",
          "amount",
          "currency",
          "expected_close_date",
          "closed_on",
          "lost_reason",
          "created_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "Deal id.",
            "examples": [
              "deal_5001"
            ]
          },
          "name": {
            "type": "string",
            "description": "What is being sold."
          },
          "company_id": {
            "type": "string",
            "description": "The customer or prospect company."
          },
          "company_name": {
            "type": "string",
            "description": "Name of that company."
          },
          "contact_id": {
            "type": "string",
            "description": "The main contact at the company."
          },
          "owner_email": {
            "type": "string",
            "format": "email",
            "description": "Email address of the deal owner, the user responsible for the deal."
          },
          "owner_name": {
            "type": "string",
            "description": "Name of the deal owner."
          },
          "type": {
            "$ref": "#/components/schemas/DealType"
          },
          "stage": {
            "$ref": "#/components/schemas/DealStage"
          },
          "status": {
            "$ref": "#/components/schemas/DealStatus"
          },
          "probability": {
            "type": "integer",
            "description": "Chance of winning in percent, set by the stage (lead 10, qualified 25, proposal 50, negotiation 75, won 100, lost 0)."
          },
          "amount": {
            "type": "number",
            "description": "Deal value, net, in `currency`."
          },
          "currency": {
            "type": "string",
            "enum": [
              "EUR"
            ],
            "description": "ISO 4217 currency code."
          },
          "expected_close_date": {
            "type": "string",
            "format": "date",
            "description": "When the deal is expected to close; for a closed deal, the day it closed."
          },
          "closed_on": {
            "type": [
              "string",
              "null"
            ],
            "format": "date",
            "description": "The day the deal was won or lost; null while open."
          },
          "lost_reason": {
            "type": [
              "string",
              "null"
            ],
            "description": "Why a lost deal was lost; null otherwise."
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "description": "When the deal was created (UTC)."
          }
        }
      },
      "ActivityType": {
        "type": "string",
        "enum": [
          "call",
          "meeting",
          "note"
        ],
        "description": "Kind of activity."
      },
      "Activity": {
        "type": "object",
        "required": [
          "id",
          "deal_id",
          "contact_id",
          "type",
          "subject",
          "notes",
          "owner_email",
          "owner_name",
          "occurred_at",
          "duration_minutes"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "Activity id.",
            "examples": [
              "act_6001"
            ]
          },
          "deal_id": {
            "type": "string",
            "description": "The deal the activity was logged on."
          },
          "contact_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "The contact involved, if any."
          },
          "type": {
            "$ref": "#/components/schemas/ActivityType"
          },
          "subject": {
            "type": "string",
            "description": "One-line summary."
          },
          "notes": {
            "type": "string",
            "description": "What happened, in the words of the user who logged it."
          },
          "owner_email": {
            "type": "string",
            "format": "email",
            "description": "Email address of the user who logged the activity."
          },
          "owner_name": {
            "type": "string",
            "description": "Name of the user who logged the activity."
          },
          "occurred_at": {
            "type": "string",
            "format": "date-time",
            "description": "When the call or meeting took place, or the note was written (UTC)."
          },
          "duration_minutes": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Length of a call or meeting in minutes; null for a note."
          }
        }
      },
      "DraftStatus": {
        "type": "string",
        "enum": [
          "draft",
          "sent"
        ],
        "description": "`draft` until sent; a `sent` email stays on record and cannot be deleted."
      },
      "EmailDraft": {
        "type": "object",
        "required": [
          "id",
          "contact_id",
          "deal_id",
          "author_email",
          "author_name",
          "to",
          "subject",
          "body",
          "status",
          "created_at",
          "sent_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "Email draft id.",
            "examples": [
              "drf_7001"
            ]
          },
          "contact_id": {
            "type": "string",
            "description": "The contact the email is to."
          },
          "deal_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "The deal the email is about, if any."
          },
          "author_email": {
            "type": "string",
            "format": "email",
            "description": "Email address of the user who wrote it; the email is sent from this address."
          },
          "author_name": {
            "type": "string",
            "description": "Name of the user who wrote it."
          },
          "to": {
            "type": "string",
            "format": "email",
            "description": "Recipient: the contact's email address."
          },
          "subject": {
            "type": "string",
            "description": "Subject line."
          },
          "body": {
            "type": "string",
            "description": "Plain-text body."
          },
          "status": {
            "$ref": "#/components/schemas/DraftStatus"
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "description": "When the draft was created (UTC)."
          },
          "sent_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When it was sent (UTC); null while a draft."
          }
        }
      },
      "EmailDraftCreate": {
        "type": "object",
        "required": [
          "contact_id",
          "author_email",
          "subject",
          "body"
        ],
        "additionalProperties": false,
        "properties": {
          "contact_id": {
            "type": "string",
            "description": "The contact to write to."
          },
          "deal_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "Optional: a deal with the contact's company that the email is about."
          },
          "author_email": {
            "type": "string",
            "format": "email",
            "description": "Email address of the Dealmoor user writing the draft."
          },
          "subject": {
            "type": "string",
            "maxLength": 200,
            "description": "Subject line."
          },
          "body": {
            "type": "string",
            "maxLength": 10000,
            "description": "Plain-text body."
          }
        }
      },
      "SendResult": {
        "type": "object",
        "required": [
          "data",
          "delivery"
        ],
        "properties": {
          "data": {
            "$ref": "#/components/schemas/EmailDraft"
          },
          "delivery": {
            "type": "object",
            "required": [
              "delivered",
              "note"
            ],
            "properties": {
              "delivered": {
                "type": "boolean",
                "description": "Whether an email was handed to a mail server. Always false with the sandbox key."
              },
              "note": {
                "type": "string",
                "description": "What happened to the email."
              }
            }
          }
        }
      },
      "ResetResult": {
        "type": "object",
        "required": [
          "data"
        ],
        "properties": {
          "data": {
            "type": "object",
            "required": [
              "environment",
              "reset"
            ],
            "properties": {
              "environment": {
                "type": "string",
                "enum": [
                  "sandbox"
                ],
                "description": "The environment that was reset."
              },
              "reset": {
                "type": "boolean",
                "description": "True when the sandbox writes were cleared."
              }
            }
          }
        }
      }
    }
  }
}