---
title: "Put one planned outreach touch (an email, LinkedIn or call step for one contact) on a rep's desk with its why, facts and draft. The rep edits and sends it; nothing sends by itself. owner_id must be the caller's own email unless the key is an org or machine key: a personal key or an OAuth connection is a person and gets 403 owner_not_self for anyone else. Posting the same external_key again updates the open card instead of adding a second one. Needs deals:write and a key allowed to see the contact"
method: POST
path: "/desk/cards"
tags: ["Desk"]
---

# Put one planned outreach touch (an email, LinkedIn or call step for one contact) on a rep's desk with its why, facts and draft. The rep edits and sends it; nothing sends by itself. owner_id must be the caller's own email unless the key is an org or machine key: a personal key or an OAuth connection is a person and gets 403 owner_not_self for anyone else. Posting the same external_key again updates the open card instead of adding a second one. Needs deals:write and a key allowed to see the contact

`POST /desk/cards`

## Request

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

Put one planned touch on a rep's desk, with its why, facts, fields and draft.

201 when the card is made. The same touch posted again (same
`external_key`, else the same `list_id` + `contact_id` + `touch`, else the
same rep, contact, channel and due day) while the card is unsent answers
200 with the same `card_id` and `updated: true`. An `external_key` is
kept apart per `origin.kind`, so two integrations may both use "1"; a
body with no `origin.kind` shares one namespace with every other such
caller in the org, so its key must be unique across the org. Once the rep has sent or
decided it, a re-post makes a new card. A card the desk will not make is a
4xx whose `detail.reason` says why. A personal key may name only its own
holder (403 `owner_not_self`); an org key may name any active rep.
The key must be allowed to see the contact (and the account when one is
named, else the contact's own company), checked the way the contacts and deals routes check a record id
from a body; one it may not see is 403 `contact_not_allowed` /
`account_not_allowed` and no card is made.

## Headers

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

## Request body

- DeskCardCreateRequest
  - `owner_id` string, required: Whose desk: a team member's email, id or user id. With a personal key or an OAuth connection this must be the caller's own email (the key is a person; anyone else is 403 owner_not_self). Only an org or machine key may name another rep.
  - `contact_id` integer, required: The contact the touch is for.
  - `account_id` integer, nullable: The contact's company, when it is not the contact's own.
  - `channel` string, required: email, linkedin or call.
  - `due_at` string, date-time, nullable: When the touch is due. It orders the queue and never hides the card.
  - `priority` string: low, normal, high or urgent.
  - `list_id` integer, nullable
  - `touch` integer, nullable: The touch number in the list's cadence.
  - `external_key` string, nullable: Your own key for this touch. Posting it again updates the unsent card. Keys are kept apart by `origin.kind`; with no `origin.kind` the key must be unique across the org.
  - `sender` DeskCardSender
    - `mailbox` string, nullable: Email address the card sends from. Checked against the rep's send rights when the rep sends.
    - `mailbox_id` string, nullable: Not used: email sends from `mailbox`, else the rep's own mailbox.
    - `linkedin_seat_id` string, nullable: Not used: LinkedIn sends from the rep's own connected account.
  - `context` DeskCardContext
    - `why` string, nullable: The one-line reason, shown at the top of the card.
    - `persona` string, nullable
    - `facts` string[]: Bullets under what we know. Empty ones are dropped.
    - `fields` object: The columns the card was built from, shown as chips.
  - `draft` DeskCardDraft: Shaped by the channel. email: subject, body. linkedin: kind (connect or message), message, a connect note at most 300 characters. call: talking_points, questions, handoff_line.
    - `subject` string, nullable
    - `body` string, nullable
    - `kind` string, nullable
    - `message` string, nullable: A connect note is at most 300 characters.
    - `talking_points` string[]
    - `questions` string[]
    - `handoff_line` string, nullable
  - `origin` DeskCardOrigin
    - `kind` string, nullable: Who planned it, e.g. "list_plan" or "workflow".
    - `mapping` string, nullable

## Response `201`

Successful Response

- ApiResponseDeskCardResult
  - `data` DeskCardResult, required
    - `card_id` string, required
    - `status` string, nullable: "queued" until the rep acts on it.
    - `due_at` string, nullable
    - `url` string, nullable
    - `updated` boolean: True when this body updated a card already on the desk.
    - `note` 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)
