---
title: "Create Deal"
method: POST
path: "/deals"
tags: ["Deals"]
---

# Create Deal

`POST /deals`

## Request

- Base URL: `https://be.graph8.com/api/v1`
- URL: `https://be.graph8.com/api/v1/deals`
- Auth: HTTP bearer

Create a new deal. Defaults to the org's default pipeline and first stage if not specified.

``background_tasks`` carries the ``deal.created`` event only (#16242). Like
the PATCH endpoint's stage fan-out it is injected, not a body field, so it
does not appear in the OpenAPI schema and the request contract is unchanged.
Queuing it there rather than publishing inline keeps the Redis round trip
off the caller's latency budget and makes "strictly after the commit"
structural: the task is queued only once the session context has exited,
and FastAPI attaches background tasks to the SUCCESS response, so a raise
anywhere above publishes nothing.

## Headers

- `Authorization` string, nullable
- `X-Target-Org-Id` string, nullable

## Request body

- DealCreateRequest
  - `name` string, required: Deal name
  - `company_id` integer, nullable: Deprecated: the company is derived from the contacts. If sent, it must match; otherwise omit it.
  - `description` string, nullable: Deal description
  - `amount` number, nullable: Deal monetary value
  - `currency` string, nullable: Currency code (default: USD)
  - `stage_id` string, nullable: Stage ID (from /deals/pipelines)
  - `pipeline_id` string, nullable: Pipeline ID (defaults to org default)
  - `close_date` string, nullable: Expected close date (ISO 8601)
  - `owner_id` string, required: Deal owner (user id or email). Required.
  - `contact_ids` integer[], required: mashup_contact_ids to associate with the deal (>=1 required; all must share one company)
  - `allow_duplicate` boolean: Set true to create a deal even if one already exists for the company

## Response `201`

Successful Response

- ApiResponseDealResponse
  - `data` DealResponse, required
    - `revision` integer, nullable: CRM revision after a conditional update
    - `id` string, nullable
    - `name` string, nullable
    - `description` string, nullable
    - `amount` number, nullable
    - `currency` string, nullable
    - `stage_id` string, nullable
    - `stage_name` string, nullable
    - `pipeline_id` string, nullable
    - `pipeline_name` string, nullable
    - `company_id` unknown
    - `owner_id` string, nullable
    - `owner_name` string, nullable
    - `owner_email` string, nullable
    - `last_activity_at` string, nullable
    - `close_date` string, nullable
    - `created_at` string, nullable
    - `updated_at` string, nullable
    - `closed_lost_reason` string, nullable
    - `contact_count` integer
    - `primary_contact` ContactBrief: Lightweight contact summary embedded in deal responses. `id` is the canonical `mashup_contact_id` (BigInt), matching how contacts are addressed everywhere else in the public API (g8_search_contacts, g8_lookup_person, etc.). `role` is the contact's role within THIS deal (champion / decision_maker / influencer / blocker / coach / end_user) and is null when no role was set.
      - `id` integer, required
      - `name` string, nullable
      - `email` string, nullable
      - `title` string, nullable
      - `role` string, nullable
    - `contacts` ContactBrief[], nullable
      - `id` integer, required
      - `name` string, nullable
      - `email` string, nullable
      - `title` string, nullable
      - `role` string, nullable
  - `pagination` PaginationMeta
    - `page` integer, required: Current page number (1-indexed)
    - `limit` integer, required: Items per page
    - `total` integer, required: Total number of items
    - `has_next` boolean, required: Whether there are more pages
    - `next_cursor` string, nullable: Opaque cursor for the next page; null when there are no more items. Pass it back as the `cursor` query param to fetch the following page without computing page numbers.
    - `total_available` integer, nullable: Search endpoints only: how many records really match, when known. Can be larger than `total`, which is capped at the 10,000 results that can be paged. For company search this is a distinct-company estimate. Null when unknown; absent outside search.
    - `total_is_lower_bound` boolean, nullable: Search endpoints only: true when more records match than `total` shows (the result set is larger than the 10,000 results that can be paged). Absent outside search.
    - `notes` string[], nullable: Search endpoints only: plain-language notes about the result, e.g. that more records match than can be paged, or that a reversed `between` range was put in order.

## Other responses

- `422`: Validation Error

---

[API](https://skmtc.dev/graph8/apis/graph8-developer-api.md) · [All operations](https://skmtc.dev/graph8/apis/graph8-developer-api/llms.txt) · [OpenAPI document](https://skmtc.dev/graph8/apis/graph8-developer-api/revisions/2e7cfa0ca921?raw)
