Sales run through two objects. An opportunity is a potential deal that advances through pipeline stages. A quote is a priced proposal attached to an opportunity that you send to the client and eventually accept or decline.
1. Create an opportunity
An opportunity belongs to a client and sits on a stage: identified, qualified, proposed, negotiation, won, or lost.
POST /opportunities HTTP/1.1
Authorization: Bearer <access_token>
Content-Type: application/json
{
"client_id": "0190a1c2-...",
"name": "Website rebuild",
"stage": "qualified",
"probability": 50,
"estimated_value": "48000.00",
"expected_close_date": "2026-03-31"
}
Returns 201 Created with an OpportunityResponse. probability is an integer 0-100.
See the pipeline as a board with GET /opportunities/board, which groups opportunities by stage. Advance a deal by updating its stage with PUT /opportunities/{id}. GET /opportunities/{id} returns the opportunity together with its client, quotes, and activities.
2. Create a quote
A quote prices an opportunity through one or more lines. A line is either rate_based (days x day rate) or fixed (a flat amount).
POST /quotes HTTP/1.1
Authorization: Bearer <access_token>
Content-Type: application/json
{
"opportunity_id": "0190a1c2-...",
"valid_until": "2026-02-28",
"lines": [
{
"kind": "rate_based",
"profile_label": "Senior Developer",
"days": "20",
"day_rate": "800.00",
"discount_percent": "0"
},
{
"kind": "fixed",
"profile_label": "Discovery workshop",
"fixed_amount": "3000.00",
"discount_percent": "0"
}
]
}
Returns 201 Created with a QuoteResponse in draft status. A new quote takes its “bill to” details from the client unless you pass a bill_to override. discount_percent is a decimal string 0-100.
3. Send, accept, or decline
Send the quote to the client:
POST /quotes/{id}/send HTTP/1.1
Authorization: Bearer <access_token>
Then record the outcome:
POST /quotes/{id}/accept HTTP/1.1
Authorization: Bearer <access_token>
POST /quotes/{id}/decline HTTP/1.1
Authorization: Bearer <access_token>
Content-Type: application/json
{ "reason": "Over budget" }
The quote status moves draft -> sent -> accepted or declined (it can also become expired). Acting out of order returns 409.
4. Download the PDF
GET /quotes/{id}/pdf HTTP/1.1
Authorization: Bearer <access_token>
Returns 200 OK with Content-Type: application/pdf and an attachment filename. This is the one endpoint that does not return JSON.
Profitability and masking
GET /quotes/{id} returns the quote plus a profitability block (revenue, cost, margin percent) and the related opportunity and client. Cost and margin are null for consultant-role callers; revenue is always visible (see Roles and permissions).
Endpoints at a glance
| Action | Method | Path | Success |
|---|---|---|---|
| List opportunities | GET | /opportunities | 200 |
| Board | GET | /opportunities/board | 200 |
| Create opportunity | POST | /opportunities | 201 |
| Update opportunity | PUT | /opportunities/{id} | 200 |
| Create quote | POST | /quotes | 201 |
| Get quote | GET | /quotes/{id} | 200 |
| Send | POST | /quotes/{id}/send | 200 |
| Accept | POST | /quotes/{id}/accept | 200 |
| Decline | POST | /quotes/{id}/decline | 200 |
| Download PDF | GET | /quotes/{id}/pdf | 200 |
When a quote is accepted, stand up delivery with Run a mission.