Quotes and products
Product catalogue, quotes, line items, the approval chain and quote sending.
17 endpoints· 4 called live· verified against http://127.0.0.1:8741
17 endpoints
GET/api/v1/salesshift/productsCalled liveThe organization's product catalogue, searchable and active-filtered.
Query parameters
| Name | Type | Required | Notes |
|---|---|---|---|
| search | string? | optional | |
| active_only | boolean | optional | Default true. |
| limit | integer | optional | Default 200. max 1000. |
Request
curl 'https://api.vxcloud.io/api/v1/salesshift/products' \
-H "Authorization: Bearer $SS_TOKEN"Response · 200
{
"success": "bool",
"data": [
{
"id": "str",
"sku": "str",
"name": "str",
"description": "null",
"kind": "str",
"unit_price_cents": "int",
"unit_price": "str",
"unit_price_display": "str",
"currency": "str",
"cost_cents": "null",
"billing_interval": "null",
"billing_interval_count": "int",
"trial_days": "null",
"tax_rate": "float",
"is_active": "bool",
"created_at": "str"
},
"...x2"
]
}POST/api/v1/salesshift/productsVerified in sessionCreate a catalogue product; recurring products require a billing interval.
Request body (required) — ProductIn
| Field | Type | Required | Notes |
|---|---|---|---|
| sku | string? | optional | |
| name | string | required | |
| description | string? | optional | |
| kind | string | optional | Default "one_time". |
| unit_price_cents | integer | optional | Default 0. |
| currency | string | optional | Default "USD". |
| cost_cents | integer? | optional | |
| billing_interval | string? | optional | |
| billing_interval_count | integer | optional | Default 1. |
| trial_days | integer? | optional | |
| tax_rate | number | optional | Default 0. |
| is_active | boolean | optional | Default true. |
Values in the request below are typed placeholders, not sample data.
Request
curl -X POST 'https://api.vxcloud.io/api/v1/salesshift/products' \
-H "Authorization: Bearer $SS_TOKEN" \
-H 'Content-Type: application/json' \
-d '{ "sku": "<string>", "name": "<string>", "description": "<string>", "kind": "one_time", "unit_price_cents": 0, "currency": "USD", "cost_cents": 0, "billing_interval": "<string>", "billing_interval_count": 1, "trial_days": 0, "tax_rate": 0, "is_active": true }'Response · 201
{
"data": "…",
"success": "…"
}Errors
| Status | detail |
|---|---|
| 400 | Recurring products need a billing_interval |
| 409 | A product with that SKU exists |
PATCH/api/v1/salesshift/products/{product_id}Verified in sessionUpdate a catalogue product from a partial payload.
Path parameters
| Name | Type | Required | Notes |
|---|---|---|---|
| product_id | string(uuid) | required |
Request body (required)
Accepts a JSON object; the source declares no field list.
Request
curl -X PATCH 'https://api.vxcloud.io/api/v1/salesshift/products/{product_id}' \
-H "Authorization: Bearer $SS_TOKEN"Response · 200
{
"data": "…",
"success": "…"
}Errors
| Status | detail |
|---|---|
| 404 | Product not found |
DELETE/api/v1/salesshift/products/{product_id}Verified in sessionSoft delete.
Soft delete. Hard-deleting would orphan the product_id on historical lines, and those lines are financial records.
Path parameters
| Name | Type | Required | Notes |
|---|---|---|---|
| product_id | string(uuid) | required |
Request
curl -X DELETE 'https://api.vxcloud.io/api/v1/salesshift/products/{product_id}' \
-H "Authorization: Bearer $SS_TOKEN"Response · 200
{
"deactivated": "…",
"success": "…"
}Errors
| Status | detail |
|---|---|
| 404 | Product not found |
GET/api/v1/salesshift/quotesCalled liveQuotes, optionally filtered by status and deal.
Query parameters
| Name | Type | Required | Notes |
|---|---|---|---|
| status | string? | optional | |
| deal_id | string(uuid)? | optional | |
| limit | integer | optional | Default 100. max 500. |
Request
curl 'https://api.vxcloud.io/api/v1/salesshift/quotes' \
-H "Authorization: Bearer $SS_TOKEN"Response · 200
{
"success": "bool",
"data": [
{
"id": "str",
"number": "str",
"title": "str",
"deal_id": "null",
"contact_id": "str",
"company_id": "null",
"status": "str",
"currency": "str",
"subtotal_cents": "int",
"discount_cents": "int",
"tax_cents": "int",
"total_cents": "int",
"total_display": "str",
"reporting_total_cents": "int",
"fx_rate": "float",
"fx_as_of": "null",
"discount_percent": "float",
"order_discount_kind": "str",
"order_discount_value": "float",
"valid_until": "null",
"terms": "null",
"notes": "null",
"contract_doc_id": "str",
"public_token": "str",
"sent_at": "str",
"viewed_at": "null",
"accepted_at": "str",
"declined_at": "null",
"created_at": "str",
"is_locked": "bool"
},
"...x9"
]
}POST/api/v1/salesshift/quotesVerified in sessionCreate a quote with its line items and a generated document number.
Request body (required) — QuoteIn
| Field | Type | Required | Notes |
|---|---|---|---|
| title | string? | optional | |
| deal_id | string(uuid)? | optional | |
| contact_id | string(uuid)? | optional | |
| company_id | string(uuid)? | optional | |
| currency | string | optional | Default "USD". |
| valid_until | string(date)? | optional | |
| terms | string? | optional | |
| notes | string? | optional | |
| order_discount_kind | string | optional | Default "none". |
| order_discount_value | number | optional | Default 0. |
| lines | LineIn[] | optional |
Values in the request below are typed placeholders, not sample data.
Request
curl -X POST 'https://api.vxcloud.io/api/v1/salesshift/quotes' \
-H "Authorization: Bearer $SS_TOKEN" \
-H 'Content-Type: application/json' \
-d '{ "title": "<string>", "deal_id": "<string(uuid)>", "contact_id": "<string(uuid)>", "company_id": "<string(uuid)>", "currency": "USD", "valid_until": "<string(date)>", "terms": "<string>", "notes": "<string>", "order_discount_kind": "none", "order_discount_value": 0, "lines": [ "<LineIn>" ] }'Response · 201
{
"data": "…",
"success": "…"
}Errors
| Status | detail |
|---|---|
| 400 | computed at runtime: err |
| 400 | Product {...} not found |
GET/api/v1/salesshift/quotes/{quote_id}Called liveOne quote with its lines and approval history.
Path parameters
| Name | Type | Required | Notes |
|---|---|---|---|
| quote_id | string(uuid) | required |
Request
curl 'https://api.vxcloud.io/api/v1/salesshift/quotes/{quote_id}' \
-H "Authorization: Bearer $SS_TOKEN"Response · 200
{
"success": "bool",
"data": {
"id": "str",
"number": "str",
"title": "str",
"deal_id": "null",
"contact_id": "str",
"company_id": "null",
"status": "str",
"currency": "str",
"subtotal_cents": "int",
"discount_cents": "int",
"tax_cents": "int",
"total_cents": "int",
"total_display": "str",
"reporting_total_cents": "int",
"fx_rate": "float",
"fx_as_of": "null",
"discount_percent": "float",
"order_discount_kind": "str",
"order_discount_value": "float",
"valid_until": "null",
"terms": "null",
"notes": "null",
"contract_doc_id": "str",
"public_token": "str",
"sent_at": "str",
"viewed_at": "null",
"accepted_at": "str",
"declined_at": "null",
"created_at": "str",
"is_locked": "bool",
"lines": [
{
"id": "...",
"product_id": "...",
"name": "...",
"description": "...",
"quantity": "...",
"unit_price_cents": "...",
"discount_kind": "...",
"discount_value": "...",
"tax_rate": "...",
"subtotal_cents": "...",
"discount_cents": "...",
"tax_cents": "...",
"total_cents": "...",
"total_display": "...",
"billing_interval": "...",
"billing_interval_count": "...",
"position": "..."
},
"...x2"
],
"approvals": [
"<empty list>"
],
"approval_required": "null"
}
}PATCH/api/v1/salesshift/quotes/{quote_id}Verified in sessionEdit a quote; sent, accepted and voided quotes are locked.
Path parameters
| Name | Type | Required | Notes |
|---|---|---|---|
| quote_id | string(uuid) | required |
Request body (required) — QuotePatch
| Field | Type | Required | Notes |
|---|---|---|---|
| title | string? | optional | |
| deal_id | string(uuid)? | optional | |
| contact_id | string(uuid)? | optional | |
| company_id | string(uuid)? | optional | |
| currency | string? | optional | |
| valid_until | string(date)? | optional | |
| terms | string? | optional | |
| notes | string? | optional | |
| order_discount_kind | string? | optional | |
| order_discount_value | number? | optional |
Values in the request below are typed placeholders, not sample data.
Request
curl -X PATCH 'https://api.vxcloud.io/api/v1/salesshift/quotes/{quote_id}' \
-H "Authorization: Bearer $SS_TOKEN" \
-H 'Content-Type: application/json' \
-d '{ "title": "<string>", "deal_id": "<string(uuid)>", "contact_id": "<string(uuid)>", "company_id": "<string(uuid)>", "currency": "<string>", "valid_until": "<string(date)>", "terms": "<string>", "notes": "<string>", "order_discount_kind": "<string>", "order_discount_value": 0 }'Response · 200
{
"data": "…",
"success": "…"
}Errors
| Status | detail |
|---|---|
| 400 | A {...} quote cannot be edited — duplicate it instead |
DELETE/api/v1/salesshift/quotes/{quote_id}Verified in sessionVoid a quote; an accepted quote cannot be voided.
Path parameters
| Name | Type | Required | Notes |
|---|---|---|---|
| quote_id | string(uuid) | required |
Request
curl -X DELETE 'https://api.vxcloud.io/api/v1/salesshift/quotes/{quote_id}' \
-H "Authorization: Bearer $SS_TOKEN"Response · 200
{
"data": "…",
"success": "…"
}Errors
| Status | detail |
|---|---|
| 400 | An accepted quote cannot be voided |
POST/api/v1/salesshift/quotes/{quote_id}/approveVerified in sessionApprove a quote that is awaiting approval.
Path parameters
| Name | Type | Required | Notes |
|---|---|---|---|
| quote_id | string(uuid) | required |
Request body (required) — ApprovalDecision
| Field | Type | Required | Notes |
|---|---|---|---|
| reason | string? | optional |
Values in the request below are typed placeholders, not sample data.
Request
curl -X POST 'https://api.vxcloud.io/api/v1/salesshift/quotes/{quote_id}/approve' \
-H "Authorization: Bearer $SS_TOKEN" \
-H 'Content-Type: application/json' \
-d '{ "reason": "<string>" }'Response · 200
{
"data": "…",
"success": "…"
}Errors
| Status | detail |
|---|---|
| 400 | Quote is not awaiting approval |
POST/api/v1/salesshift/quotes/{quote_id}/decline-approvalVerified in sessionDecline the pending approval on a quote.
Path parameters
| Name | Type | Required | Notes |
|---|---|---|---|
| quote_id | string(uuid) | required |
Request body (required) — ApprovalDecision
| Field | Type | Required | Notes |
|---|---|---|---|
| reason | string? | optional |
Values in the request below are typed placeholders, not sample data.
Request
curl -X POST 'https://api.vxcloud.io/api/v1/salesshift/quotes/{quote_id}/decline-approval' \
-H "Authorization: Bearer $SS_TOKEN" \
-H 'Content-Type: application/json' \
-d '{ "reason": "<string>" }'Response · 200
{
"data": "…",
"success": "…"
}PUT/api/v1/salesshift/quotes/{quote_id}/linesVerified in sessionBulk replace — the editor sends the whole grid and the server re-prices it.
Bulk replace — the editor sends the whole grid and the server re-prices it. Simpler and safer than per-line PATCH, which can leave a document half-updated if one call fails.
Path parameters
| Name | Type | Required | Notes |
|---|---|---|---|
| quote_id | string(uuid) | required |
Request body (required)
Untyped Dict[str, Any] body — there is no Pydantic model, so these are the field names the handler actually reads.
lines
Values in the request below are typed placeholders, not sample data.
Request
curl -X PUT 'https://api.vxcloud.io/api/v1/salesshift/quotes/{quote_id}/lines' \
-H "Authorization: Bearer $SS_TOKEN" \
-H 'Content-Type: application/json' \
-d '{ "lines": "<value>" }'Response · 200
{
"data": "…",
"success": "…"
}Errors
| Status | detail |
|---|---|
| 400 | A {...} quote cannot be re-priced |
| 400 | lines must be a list |
| 400 | Max 200 line items per quote |
| 400 | computed at runtime: err |
POST/api/v1/salesshift/quotes/{quote_id}/sendVerified in sessionFreeze the quote into a contract document, email it, and hand it to the e-sign flow.
Freeze the quote into a contract document, email it, and hand it to the e-sign flow. Two rules this endpoint exists to keep: * **Sending means delivering.** The quote is only marked `sent` once the mail server has actually accepted the message. When delivery fails the document and its signing link are still created — so the link can be copied and delivered by hand — but the quote stays where it was and the caller is told why, instead of a status that claims a customer has it. * **One document per quote.** A second send reuses the existing `ss_contract_docs` row. Minting a fresh one would leave the first row's signing tokens live on a superseded price, and the customer could sign either of them.
Path parameters
| Name | Type | Required | Notes |
|---|---|---|---|
| quote_id | string(uuid) | required |
Request body (optional)
Untyped Dict[str, Any] body — there is no Pydantic model, so these are the field names the handler actually reads.
party_a_emailparty_a_nameparty_b_emailparty_b_name
Values in the request below are typed placeholders, not sample data.
Request
curl -X POST 'https://api.vxcloud.io/api/v1/salesshift/quotes/{quote_id}/send' \
-H "Authorization: Bearer $SS_TOKEN" \
-H 'Content-Type: application/json' \
-d '{ "party_a_email": "<value>", "party_a_name": "<value>", "party_b_email": "<value>", "party_b_name": "<value>" }'Response · 200
{
"contract_doc_id": "…",
"data": "…",
"delivered": "…",
"delivery_error": "…",
"public_url": "…",
"sent_to": "…",
"sign_url_b": "…",
"success": "…"
}Errors
| Status | detail |
|---|---|
| 400 | Quote is already {...} |
| 400 | Approval required before sending: {...} |
| 400 | Add at least one line item |
| 400 | A recipient email is required to send a quote |
| 409 | This quote's contract was voided — duplicate the quote to issue a new one |
POST/api/v1/salesshift/quotes/{quote_id}/submit-for-approvalVerified in sessionSubmit a quote for approval, or approve it outright when no rule is triggered.
Path parameters
| Name | Type | Required | Notes |
|---|---|---|---|
| quote_id | string(uuid) | required |
Request
curl -X POST 'https://api.vxcloud.io/api/v1/salesshift/quotes/{quote_id}/submit-for-approval' \
-H "Authorization: Bearer $SS_TOKEN"Response · 200
{
"approved_automatically": "…",
"data": "…",
"reason": "…",
"success": "…"
}GET/api/v1/salesshift/quotes/public/{token}url-secret-onlyVerified in sessionUnauthenticated read for the recipient's link.
Unauthenticated read for the recipient's link. Returns only what belongs on a customer-facing page — no internal ids, owner or FX lineage. `preview=1` is the seller opening their own "Customer view" from the quote editor. Without it, that click stamps `viewed_at` and flips the quote to `viewed` — a read receipt the seller manufactured by looking at their own document, which the list then reports as "with the customer".
The quote's public token is the credential.
Path parameters
| Name | Type | Required | Notes |
|---|---|---|---|
| token | string | required |
Query parameters
| Name | Type | Required | Notes |
|---|---|---|---|
| preview | boolean | optional | Default false. |
Request
curl 'https://api.vxcloud.io/api/v1/salesshift/quotes/public/{token}'Response · 200
{
"data": "…",
"success": "…"
}Errors
| Status | detail |
|---|---|
| 404 | Quote not found |
GET/api/v1/salesshift/settings/quotesCalled liveQuote settings for the organization (numbering, tax, approval thresholds).
Request
curl 'https://api.vxcloud.io/api/v1/salesshift/settings/quotes' \
-H "Authorization: Bearer $SS_TOKEN"Response · 200
{
"success": "bool",
"data": {
"reporting_currency": "str",
"send_pool_rotation_enabled": "bool",
"quote_approval_discount_pct": "int"
}
}PUT/api/v1/salesshift/settings/quotesVerified in sessionSet approval thresholds / reporting currency.
Set approval thresholds / reporting currency. Only known keys are stored — an unrecognised key would look configured in the UI while silently doing nothing.
Request body (required)
Accepts a JSON object; the source declares no field list.
Request
curl -X PUT 'https://api.vxcloud.io/api/v1/salesshift/settings/quotes' \
-H "Authorization: Bearer $SS_TOKEN"Response · 200
{
"data": "…",
"success": "…"
}Errors
| Status | detail |
|---|---|
| 400 | Unknown setting(s): {...} |