Pipelines
The REST API endpoints for managing pipelines and their stages within your organization.
What a pipeline is
A pipeline is an ordered set of stages a deal moves through. Stages are configuration, not a fixed list: your organization names them and orders them itself, and may keep several pipelines side by side.
Every new organization is created with one pipeline, Sales, holding four stages in order — New, Qualified, Proposal and Negotiation. The names follow the language of whoever created the organization, so an organization created in Portuguese is born with Vendas and Novo, Qualificado, Proposta, Negociação. They are ordinary names once written: changing your language later renames nothing that already exists. Rename them, add your own, or delete the pipeline outright once you have built the one you want.
Every pipeline has a currency — an uppercase ISO 4217 code such as "USD"
or "BRL" — that every deal on it is expressed in. A deal has no currency of
its own: its responses report the pipeline's. A new pipeline starts in your
organization's default currency — USD until an owner or admin changes it
(see Currency) — unless you name another when creating it.
Nothing converts between currencies, and no total ever adds amounts held in
different ones.
A pipeline's currency can change at any time, even while it holds deals. The
change relabels those deals rather than converting them: a deal worth
USD 1,000 becomes EUR 1,000, its stored value untouched. Change it only to
correct the currency a pipeline was set up in, not to move money between
currencies.
A stage's position is presentation only. Reordering the stages of a pipeline
never touches a deal: a deal stays in the stage it was in, which simply appears
somewhere else in the order. Reordering renumbers the whole pipeline in one
operation — see Reorder Pipeline Stages.
Deleting a stage that still holds deals requires saying where those deals
should go. Pass moveToStageId on the delete request, naming another stage of
the same pipeline; without it the request is refused and tells you how many
deals are in the way.
Configuring pipelines in the app
Everything below is also available without the API, under Settings → Pipelines. Like the rest of the settings area it is open to organization owners and admins only; a member never sees this screen.
The screen is a list, one row per pipeline: its name, how many stages it has, and when it was created. Sort it by name or by creation date and page through it. There is no search field and no filters — an organization keeps a handful of pipelines, not a directory of them.
New pipeline, at the top right, creates one straight away: there is no form to fill in first. It arrives with a default name and the same four default stages, named in your language, and its panel opens on it, so you land where the stages are edited rather than being sent there afterwards. It is a real pipeline from that moment — closing the panel without touching anything leaves it in the list, where you delete it like any other. Names are not numbered and need not be unique, so clicking twice leaves you with two pipelines of the same name; renaming is the first thing the open panel invites.
Clicking a pipeline's name opens it in a side panel: the name and the currency at the top, the stages in order below. Save changes in the panel's footer saves the name and the currency and closes the panel; Cancel closes it and discards what you typed. Everything you do to the stages saves as you do it, so it is kept either way. From the panel you can:
- Add a stage — type its name in the field under the list. New stages append at the end.
- Rename the pipeline, in the field at the top, or any of its stages.
- Change the currency — a new pipeline arrives in your default currency and is changed here. When the pipeline already holds deals, saving a new currency first asks you to confirm, naming how many deals will be relabeled and that their values are not converted.
- Reorder stages by dragging the handle on the left of a row. The list keeps the new order immediately and saves it in the background; if the save fails the stages go back where they were and a message says so. The handle is also reachable from the keyboard: focus it, press space, move with the arrow keys, and press space again to drop.
- Delete a stage. A stage that still holds deals asks which of the
pipeline's other stages they should move to, then deletes it — the same rule
the API enforces with
moveToStageId. The moved deals count their time in the destination stage from the moment they arrive, and the deleted stage leaves their funnels along with the rest of the pipeline's history of it.
A whole pipeline is deleted from its row's ⋯ menu in the list, not from
inside the panel, and after a confirmation; deleting it takes its stages with
it. A pipeline that still holds deals anywhere in it is kept instead: the delete
reports a failure rather than taking those deals down with the stages. Empty it
first.
Endpoints
All endpoints require a valid API key in the Authorization header. See Authentication for details.
Base URL: /api/v1List Pipelines
GET /api/v1/pipelinesReturns every pipeline in your organization, each with its stages ordered by
position.
Returns { "data": [...], "total": number }.
Get Pipeline
GET /api/v1/pipelines/:idCreate Pipeline
POST /api/v1/pipelines
Content-Type: application/json| Field | Type | Required | Description |
|---|---|---|---|
name | string | No | Pipeline name (1-200 characters). Defaults to "New pipeline" |
locale | string | No | en or pt-BR. The language of the default names. Defaults to en |
currency | string | No | An uppercase ISO 4217 code from the supported list, such as EUR. Defaults to the organization's default currency |
A pipeline is created with the four default stages already in it — New,
Qualified, Proposal and Negotiation — at positions 0 to 3, and the response
carries them in stages. Add more with Create Pipeline Stage, or delete the
ones you do not want.
Pipeline names are neither numbered nor required to be unique, so creating two
pipelines without a name leaves you with two called "New pipeline".
locale decides the language of the default pipeline and stage names, and
nothing else. The Accept-Language header is ignored: name the language in
the body if you want anything other than English.
Update Pipeline
PATCH /api/v1/pipelines/:id
Content-Type: application/json| Field | Type | Required | Description |
|---|---|---|---|
name | string | No | Pipeline name (1-200 characters) |
currency | string | No | An uppercase ISO 4217 code from the supported list |
A new currency relabels every deal on the pipeline — their values are kept
as they are, never converted. A code outside the supported list, or one written
in lowercase, is refused with 422.
Delete Pipeline
DELETE /api/v1/pipelines/:idReturns { "success": true } on success. Deleting a pipeline deletes its
stages with it.
A pipeline that still holds deals — in any of its stages, whatever their status — is not deleted. Its stages would cascade away and take the deals with them, so the request is refused rather than destroying them silently, and unlike a stage delete there is nowhere on the same board to move them to. The request responds 409 with the number of deals that are in the way, counted across every stage of the pipeline, so you can report it without a second request:
{
"error": "Pipeline still has deals in it",
"dealCount": 12
}Empty the pipeline first. A deal can only be moved between stages of its own pipeline, so there is no way to carry it across to another one: delete the deals, then delete the pipeline. See Deals.
Reorder Pipeline Stages
PATCH /api/v1/pipelines/:id/stage-order
Content-Type: application/json| Field | Type | Required | Description |
|---|---|---|---|
stageIds | string[] | Yes | Every stage of the pipeline, exactly once, in the new order |
Returns { "data": [...] } — the stages renumbered from 0 with no gaps. An
order that omits a stage, repeats one, or names a stage from another pipeline is
refused with 400 and nothing is renumbered. Reordering never moves a deal.
List Pipeline Stages
GET /api/v1/pipeline-stages?pipelineId=UUID_HERE| Parameter | Type | Required | Description |
|---|---|---|---|
pipelineId | string | Yes | The pipeline whose stages to list |
Returns { "data": [...], "total": number }, ordered by position.
Create Pipeline Stage
POST /api/v1/pipeline-stages
Content-Type: application/json| Field | Type | Required | Description |
|---|---|---|---|
pipelineId | string | Yes | The pipeline the stage belongs to |
name | string | Yes | Stage name (1-200 characters) |
A new stage is always appended at the end of the pipeline. Move it with Reorder Pipeline Stages.
Get Pipeline Stage
GET /api/v1/pipeline-stages/:idUpdate Pipeline Stage
PATCH /api/v1/pipeline-stages/:id
Content-Type: application/jsonBody: name. Order is not editable here — reordering renumbers every stage of
the pipeline at once, through Reorder Pipeline Stages.
Delete Pipeline Stage
DELETE /api/v1/pipeline-stages/:id
Content-Type: application/json| Field | Type | Required | Description |
|---|---|---|---|
moveToStageId | string | When the stage holds deals | Another stage of the same pipeline to move them to |
Returns { "success": true } on success.
A stage that still holds deals cannot be deleted on its own. The request responds 409 with the number of deals that are in the way, so you can ask where to move them without a second request:
{
"error": "Pipeline stage still has deals in it",
"dealCount": 12
}Repeat the request with moveToStageId set, and the deals move to that stage as
the deleted one goes away. Each moved deal is recorded as entering the
destination at that moment, so its stage funnel keeps counting; the entries it
had for the deleted stage go with the stage.
Response shape
A pipeline:
{
"id": "0c1f5b8e-...",
"organizationId": "9a2d...",
"name": "Sales",
"currency": "USD",
"createdAt": "2026-09-01T14:54:01.000Z",
"updatedAt": "2026-09-01T14:54:01.000Z",
"stages": [
{
"id": "4b7e...",
"organizationId": "9a2d...",
"pipelineId": "0c1f5b8e-...",
"name": "New",
"position": 0,
"createdAt": "2026-09-01T14:54:01.000Z",
"updatedAt": "2026-09-01T14:54:01.000Z"
}
]
}stages holds every stage of the pipeline, in position order — four of them
on a pipeline that has just been created. currency is the code every deal's
value on this pipeline is expressed in; a pipeline created through the API
without one starts in the organization's default currency.
The stage endpoints return the stage on its own, without the surrounding pipeline.
createdAt and updatedAt are ISO 8601 in UTC, and position is a raw number.
The API is not localized, whatever language the app itself is showing you — see
Data Formats.
Examples
cURL
# List pipelines
curl https://your-app.com/api/v1/pipelines \
-H "Authorization: Bearer ba_a1b2c3d4e5f6..."
# Create a pipeline — comes back with the four default stages
curl -X POST https://your-app.com/api/v1/pipelines \
-H "Authorization: Bearer ba_a1b2c3d4e5f6..." \
-H "Content-Type: application/json" \
-d '{"name":"Renewals"}'
# Every field is optional: this one is called "Novo funil", with Portuguese stages
curl -X POST https://your-app.com/api/v1/pipelines \
-H "Authorization: Bearer ba_a1b2c3d4e5f6..." \
-H "Content-Type: application/json" \
-d '{"locale":"pt-BR"}'
# A pipeline negotiated in euros
curl -X POST https://your-app.com/api/v1/pipelines \
-H "Authorization: Bearer ba_a1b2c3d4e5f6..." \
-H "Content-Type: application/json" \
-d '{"name":"EMEA","currency":"EUR"}'
# Change a pipeline's currency — its deals are relabeled, not converted
curl -X PATCH https://your-app.com/api/v1/pipelines/UUID_HERE \
-H "Authorization: Bearer ba_a1b2c3d4e5f6..." \
-H "Content-Type: application/json" \
-d '{"currency":"BRL"}'
# Add a stage to it
curl -X POST https://your-app.com/api/v1/pipeline-stages \
-H "Authorization: Bearer ba_a1b2c3d4e5f6..." \
-H "Content-Type: application/json" \
-d '{"pipelineId":"UUID_HERE","name":"Contacted"}'
# Rename a stage
curl -X PATCH https://your-app.com/api/v1/pipeline-stages/UUID_HERE \
-H "Authorization: Bearer ba_a1b2c3d4e5f6..." \
-H "Content-Type: application/json" \
-d '{"name":"Qualified"}'
# Delete a stage, moving its deals elsewhere
curl -X DELETE https://your-app.com/api/v1/pipeline-stages/UUID_HERE \
-H "Authorization: Bearer ba_a1b2c3d4e5f6..." \
-H "Content-Type: application/json" \
-d '{"moveToStageId":"OTHER_UUID_HERE"}'JavaScript / TypeScript
const API_KEY = "ba_a1b2c3d4e5f6...";
const BASE_URL = "https://your-app.com/api/v1";
// List pipelines
const response = await fetch(`${BASE_URL}/pipelines`, {
headers: { Authorization: `Bearer ${API_KEY}` },
});
const { data, total } = await response.json();
// Add a stage to the first pipeline
const createRes = await fetch(`${BASE_URL}/pipeline-stages`, {
method: "POST",
headers: {
Authorization: `Bearer ${API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
pipelineId: data[0].id,
name: "Contacted",
}),
});
const { data: newStage } = await createRes.json();Python
import requests
API_KEY = "ba_a1b2c3d4e5f6..."
BASE_URL = "https://your-app.com/api/v1"
# List pipelines
response = requests.get(
f"{BASE_URL}/pipelines",
headers={"Authorization": f"Bearer {API_KEY}"},
)
data = response.json()
# Add a stage to the first pipeline
response = requests.post(
f"{BASE_URL}/pipeline-stages",
headers={"Authorization": f"Bearer {API_KEY}"},
json={"pipelineId": data["data"][0]["id"], "name": "Contacted"},
)
new_stage = response.json()