createDeal
Create a deal in a pipeline stage.
Create a deal in a pipeline stage. Requires title and stageId (listPipelineStages); optional priceCents, closeDate, contact/property/user ids. Entering the stage can text or email a real person, as an updateDeal move can: confirm as updateDeal says. The stage must be in a pipeline owned by or shared with your organization, and any linked contactIds/userIds must belong to your organization. userIds is the deal’s TEAM, and My deals shows only deals the viewer is on. The app’s Create Deal form puts its creator on the team, so when acting for a person include the key owner (getMyProfile) unless they name a different team. MONEY AND CUSTOM FIELDS (the whole input is one variable, so discovery cannot expand it). Commission takes ONE source per side, never both: commissionAmountCents (a fixed dollar figure in cents) XOR commissionPercentBps (basis points (300 = 3%) applied to priceCents); and teamSplitAmountCents XOR teamSplitPercentBps (basis points applied to the resulting COMMISSION, not the price). Sending both halves of a pair with values is rejected; sending one explicitly as null CLEARS that side. The server derives commissionCents (GCI) and returns it: read it there. A percent re-derives when priceCents changes; an amount does not. participantSplits is [{ userId, splitAmountCents | splitPercentBps }] allocating what remains after the team fee, and it must not exceed the pool. customFieldValues is [{ customFieldId (from listDealCustomFields (deal fields are PER-PIPELINE), and exactly ONE of textValue (TEXT/LINK) | numberValue (NUMBER/DOLLAR, a plain number, NOT cents) | dateValue (DATE, ISO 8601) | dropdownSelectedValues (DROPDOWN) the option LABEL strings, not option ids) matching the field’s type }]. The response echoes the money and custom fields: confirm the write from it.
curl https://server.goliathdata.com/api/v1/call \
-H "Authorization: Bearer $GOLIATH_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"operationId": "createDeal",
"variables": {
"input": {
"title": "<String>",
"stageId": "<stageId>"
}
}
}'import { GoliathClient } from '@goliath-data/sdk'
const goliath = new GoliathClient({
apiKey: process.env.GOLIATH_API_KEY!,
baseUrl: 'https://server.goliathdata.com',
})
const result = await goliath.deals.createDeal(
{
input: {
title: '<String>',
stageId: '<stageId>'
}
},
{ idempotencyKey: crypto.randomUUID() },
)const res = await fetch('https://server.goliathdata.com/api/v1/call', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.GOLIATH_API_KEY}`,
'Content-Type': 'application/json',
'Idempotency-Key': crypto.randomUUID(),
},
body: JSON.stringify({
operationId: 'createDeal',
variables: {
input: {
title: '<String>',
stageId: '<stageId>'
}
}
}),
})
const { data, errors } = await res.json()Variables
The example sends only what the schema requires. Some operations also need one of several optional variables (one filter or another, say); the description above says which.
Every field, all the way down
[
{
"name": "input",
"type": "CreatePipelineItemInput!",
"required": true,
"description": null,
"defaultValue": null,
"fields": [
{
"name": "title",
"type": "String!",
"required": true,
"description": null,
"defaultValue": null
},
{
"name": "stageId",
"type": "ID!",
"required": true,
"description": null,
"defaultValue": null
},
{
"name": "description",
"type": "String",
"required": false,
"description": null,
"defaultValue": null
},
{
"name": "priceCents",
"type": "BigInt",
"required": false,
"description": null,
"defaultValue": null
},
{
"name": "commissionAmountCents",
"type": "BigInt",
"required": false,
"description": null,
"defaultValue": null
},
{
"name": "commissionPercentBps",
"type": "Int",
"required": false,
"description": null,
"defaultValue": null
},
{
"name": "teamSplitAmountCents",
"type": "BigInt",
"required": false,
"description": null,
"defaultValue": null
},
{
"name": "teamSplitPercentBps",
"type": "Int",
"required": false,
"description": null,
"defaultValue": null
},
{
"name": "participantSplits",
"type": "[PipelineItemParticipantSplitInput!]",
"required": false,
"description": null,
"defaultValue": null,
"fields": [
{
"name": "userId",
"type": "ID!",
"required": true,
"description": null,
"defaultValue": null
},
{
"name": "splitAmountCents",
"type": "BigInt",
"required": false,
"description": null,
"defaultValue": null
},
{
"name": "splitPercentBps",
"type": "Int",
"required": false,
"description": null,
"defaultValue": null
}
]
},
{
"name": "closeDate",
"type": "DateTime",
"required": false,
"description": null,
"defaultValue": null
},
{
"name": "propertyIds",
"type": "[ID!]",
"required": false,
"description": null,
"defaultValue": null
},
{
"name": "propertyStrings",
"type": "[String!]",
"required": false,
"description": null,
"defaultValue": null
},
{
"name": "contactIds",
"type": "[ID!]",
"required": false,
"description": null,
"defaultValue": null
},
{
"name": "userIds",
"type": "[ID!]",
"required": false,
"description": null,
"defaultValue": null
},
{
"name": "fileIds",
"type": "[ID!]",
"required": false,
"description": null,
"defaultValue": null
},
{
"name": "customFieldValues",
"type": "[PipelineItemCustomFieldValueInput!]",
"required": false,
"description": null,
"defaultValue": null,
"fields": [
{
"name": "customFieldId",
"type": "ID!",
"required": true,
"description": null,
"defaultValue": null
},
{
"name": "textValue",
"type": "String",
"required": false,
"description": null,
"defaultValue": null
},
{
"name": "dateValue",
"type": "DateTime",
"required": false,
"description": null,
"defaultValue": null
},
{
"name": "numberValue",
"type": "Float",
"required": false,
"description": null,
"defaultValue": null
},
{
"name": "dropdownSelectedValues",
"type": "[String!]",
"required": false,
"description": null,
"defaultValue": null
}
]
}
]
}
]Response
What comes back under data: a list shows as one entry, and a value as its type.
{
"data": {
"dealMutation": {
"createDeal": {
"id": "<ID>",
"title": "…",
"priceCents": "…",
"closeDate": "2026-01-01T00:00:00.000Z",
"isArchived": true,
"updatedAt": "2026-01-01T00:00:00.000Z",
"createdAt": "2026-01-01T00:00:00.000Z",
"commissionCents": "…",
"commissionAmountCents": "…",
"commissionPercentBps": 0,
"teamSplitAmountCents": "…",
"teamSplitPercentBps": 0,
"participantSplits": [
{
"user": {
"id": "<ID>",
"firstName": "…",
"lastName": "…"
},
"splitAmountCents": "…",
"splitPercentBps": 0
}
],
"customFieldValues": [
{
"id": "<ID>",
"textValue": "…",
"dateValue": "2026-01-01T00:00:00.000Z",
"numberValue": 0,
"dropdownSelectedValues": [
"…"
],
"dealCustomField": {
"id": "<ID>",
"name": "…",
"type": "DROPDOWN"
}
}
],
"stage": {
"id": "<ID>",
"name": "…"
},
"pipeline": {
"id": "<ID>",
"name": "…"
},
"contacts": [
{
"id": "<ID>",
"name": "…"
}
],
"properties": [
{
"id": "<ID>",
"address": {
"addressFull": "…"
}
}
]
}
}
}
}Errors
unknown_variableA variable the operation does not declare. A typo is refused rather than silently dropped.
unauthorizedNo bearer header, or the key is invalid or revoked.
insufficient_scopeThe key lacks the scope the operation requires.
forbiddenA referenced record does not exist, was deleted, or is outside your organization. The gateway cannot tell those apart.
idempotency_conflictA request with this Idempotency-Key is still in flight.
Idempotency
Send an Idempotency-Key of 1 to 255 characters. A finished request retried with the same key replays its stored response, marked Idempotency-Replayed: true; one still in flight returns 409 idempotency_conflict. Keys expire after 24 hours. Write without fear →