Update handover sub-stage
Move a contracted product line through the handover sub-stages: "Meet Pending", "Meet Scheduled", "Meet Rescheduled", "Meet Cancelled", "Meet Done" and "Drop off". "Meet Done" moves the product line to Onboarding and creates its checklist.
This is a general stage endpoint for a rooftop (team), a product line (product_line), a product (product) or an add-on (addon). In this stage the console uses it with entityType: "product_line" and a sub_stage value, as described here. Stage changes (stage), live_date, and the team, product and add-on levels are not covered on this page.
For a product line sub-stage update, the call:
- Loads the product line and its lifecycle configuration from the product line registry. The lifecycle lists the valid stages and sub-stages, the allowed transitions, the special transitions (for example a sub-stage that also changes the stage) and the
meta_datafields each sub-stage requires. - Validates the sub-stage and its required
meta_datafields, then checks that the move from the current sub-stage is allowed. - If the product line is already at the target sub-stage, returns
200withskipped: trueand changes nothing. - Writes the new stage and sub-stage to the product line and to its products. If the stage changes to
Onboarding, it:- records the onboarding date,
- sets an onboarding SLA from the platforms (it falls back to 7 if that fails),
- assigns
meta_data.ob_manageras the onboarding POC.
- While the stage stays
Onboarding,ob_managerandcs_manager/cs_idare still saved as POCs. - Writes a stage log entry.
- Runs the sub-stage handler:
- "Meet Done": calls Create the onboarding checklist with the caller's token. If that call fails, the failure is only logged and the response is still
200. It then recalculates the rooftop, dealer group and organisation stages. - "Meet Scheduled" / "Meet Rescheduled": no calendar event is created and the meeting details in
meta_dataare not stored. That handler is disabled in code. The rooftop stage is recalculated. - "Drop off": churns the product line. From
Contractedthis records "Sales Drop off", and fromOnboarding"OB Drop off". It also runs the churn handling (contract amendment, notices). Churn is not covered on this page. - Other sub-stages: the rooftop stage is recalculated.
- "Meet Done": calls Create the onboarding checklist with the caller's token. If that call fails, the failure is only logged and the response is still
- If the product line enters
Onboarding, a separate fire-and-forget step clears demo conversation data for Sales Inbound.
Send the bearer token of a signed-in user in Authorization: Bearer YOUR_TOKEN. JWT and BASE64 tokens are both accepted and checked against the user records. The same token is forwarded to the lifecycle lookup and to the checklist call.
Before you call it
- The contract must be
Contracted, so the product line exists with stageContracted. - You need the product line ID (
entityId), the same value asproduct_line_details_idin Save handover details. - Valid sub-stages, allowed transitions and required
meta_datafields come from the product line's lifecycle configuration. They are data, not code, so the exact rules per product line are to be confirmed against that configuration. - The product line record accepts these sub-stages:
Meet Pending,Meet Scheduled,Meet Done,Meet Cancelled,Meet Rescheduled,OB To Be Scheduled,In Implementation,Under Review,Client Unresponsive,CSM Handover Pending,OB Live,Sales Drop off,OB Drop off. - In the console, "Meet Scheduled" and "Meet Rescheduled" are sent after Save handover details. "Meet Done" is sent after an onboarding manager is chosen.
- The call is synchronous. It waits for the lifecycle lookup, the product line and product writes, the stage log and the sub-stage handler (including the checklist call on "Meet Done"). A failure in the checklist call does not fail the request. The rate limit is not documented.
Request
- Authenticate: send the
Authorization: Bearer YOUR_TOKENandContent-Type: application/jsonheaders. - Name the product line: set
entityTypetoproduct_lineandentityIdto the product line ID. - Pick the change: send exactly one of
sub_stageorstage. This page usessub_stage. - Add the meta data: add the
meta_datafields your sub-stage needs (see below). - Check for a skip: check
data.skipped. When it istrue, nothing changed.
The console sends these meta_data fields per sub-stage:
- "Meet Scheduled" / "Meet Rescheduled":
start_time,end_time,time_zone,attendees,meet_required: true, plusnotesfor a reschedule. - "Meet Done":
ob_manager, andmeet_required: falsewhen no call was needed. - "Meet Cancelled":
enterprise_id,team_id,team_name,notes. - "Drop off" from
Onboarding:churn_reasonis required.
Headers
| Parameter | Type | Required | Description |
|---|---|---|---|
Authorization | string | Required | Bearer YOUR_TOKEN |
Content-Type | string | Required | application/json |
Body
| Parameter | Type | Required | Description |
|---|---|---|---|
entityType | string | Required | product_line for this use. Also accepts product, team and addon (not covered here). |
entityId | string | Required | Product line ID when entityType is product_line. |
| Change to make | — | Required | Send sub_stage or stage, not both. |
sub_stage | string | Only one | New sub-stage, for example Meet Scheduled or Meet Done. Must be valid in the lifecycle configuration. |
stage | string | Only one | New stage. Not covered on this page. |
meta_data | object | Required if | The sub-stage requires fields. Fields listed as required for the sub-stage in the lifecycle configuration must be present and non-empty. |
meta_data.ob_manager | string | Optional | Onboarding manager (user ID from the onboarding POC list). Saved as the product line's onboarding POC when the stage is or becomes Onboarding. |
meta_data.cs_manager, cs_id | string | Optional | Customer success manager. Saved as the CS POC while in Onboarding or Live. |
meta_data.platforms | string[] | Optional | Used for the onboarding SLA when the stage becomes Onboarding. Falls back to the platforms saved by the handover call. |
meta_data.churn_reason | string | Required if | "Drop off" from Onboarding. Reason for the drop-off. |
meta_data.start_time, end_time, time_zone, attendees, meet_required, notes, team_name, enterprise_id, team_id | various | Optional | Sent by the console for meeting sub-stages. They are checked only if the lifecycle configuration marks them required. They are not stored by this endpoint. |
meta_data.reason | string | Optional | Used as the stage log reason. |
live_date | string | Optional | YYYY-MM-DD, only with stage: "Live". Not covered on this page. |
Sample request
curl -X POST "https://api.spyne.ai/user-management/v1/product-line/update-stage" \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"entityType": "product_line",
"entityId": "PRODUCT_LINE_ID",
"sub_stage": "Meet Done",
"meta_data": { "ob_manager": "USER_ID", "meet_required": false }
}'import requests
res = requests.post(
"https://api.spyne.ai/user-management/v1/product-line/update-stage",
json={
"entityType": "product_line",
"entityId": "PRODUCT_LINE_ID",
"sub_stage": "Meet Scheduled",
"meta_data": {
"start_time": "2026-01-01T15:00:00.000Z",
"end_time": "2026-01-01T15:30:00.000Z",
"time_zone": "America/New_York",
"attendees": ["[email protected]"],
"meet_required": True,
},
},
headers={"Authorization": "Bearer YOUR_TOKEN"},
)
print(res.status_code, res.json())const res = await fetch("https://api.spyne.ai/user-management/v1/product-line/update-stage", {
method: "POST",
headers: { Authorization: "Bearer YOUR_TOKEN", "Content-Type": "application/json" },
body: JSON.stringify({
entityType: "product_line",
entityId: "PRODUCT_LINE_ID",
sub_stage: "Meet Done",
meta_data: { ob_manager: "USER_ID" },
}),
});
console.log(res.status, await res.json());Sample response
{
"message": "Stage/substage updated successfully",
"error": false,
"code": "",
"details": null,
"data": {
"level": "product_line",
"entityId": "PRODUCT_LINE_ID",
"team_id": "TEAM_ID",
"enterprise_id": "ENTERPRISE_ID",
"product_details_id": null,
"product_line_details_id": "PRODUCT_LINE_ID",
"previousStage": "Contracted",
"previousSubStage": "Meet Scheduled",
"newStage": "Onboarding",
"newSubStage": "<subStage>",
"parentUpdates": null,
"updatedProductLineDetails": 1,
"updatedProductDetails": 2
}
}{
"message": "Already at substage \"Meet Scheduled\". No update needed.",
"error": false,
"code": "",
"details": null,
"data": {
"level": "product_line",
"entityId": "PRODUCT_LINE_ID",
"currentStage": "Contracted",
"currentSubStage": "Meet Scheduled",
"skipped": true
}
}{
"message": "Cannot transition from substage \"Meet Pending\" to \"Meet Done\". Allowed transitions: <allowedSubStages>",
"error": true,
"code": "ERROR_BAD_REQ_400",
"details": null,
"data": null
}{ "message": "Invalid Token", "error": true, "code": "ERROR_AUTH_401", "details": null, "data": null }{
"message": "Internal server error while updating stage",
"error": "<error message>",
"code": "",
"details": null,
"data": null
}Response parameters
| Parameter | Type | Description |
|---|---|---|
data.level | string | product_line. |
data.entityId | string | Product line ID. |
data.team_id | string | Rooftop ID. |
data.enterprise_id | string | Dealer group ID. |
data.product_line_details_id | string | Product line ID. |
data.product_details_id | null | Not used at product line level. |
data.previousStage, data.previousSubStage | string | Stage and sub-stage before the update. |
data.newStage, data.newSubStage | string | Stage and sub-stage the update targeted. For "Meet Done", the stage comes from the lifecycle configuration (the console expects Onboarding). The exact newSubStage after "Meet Done" is to be confirmed against the configuration. |
data.updatedProductLineDetails | number | 1 when the product line was written. |
data.updatedProductDetails | number | Rows written for the product line's products. |
data.parentUpdates | null | Not set at product line level. |
data.churnAmendmentContractId | string | Present only when a drop-off created a contract amendment. |
data.skipped, data.currentStage, data.currentSubStage | boolean, string | Present only on the "no update needed" response. |
Errors
| Status | Message | When it happens |
|---|---|---|
400 | (framework) | entityType or entityId is missing, or entityType is not product, product_line, team or addon. The body is not documented in code. |
400 | Either stage or sub_stage must be provided | Neither was sent. |
400 | Only one of stage or sub_stage can be updated at a time | Both were sent. |
400 | Product line details not found for id: ID | No product line has this ID. This is 400, not 404. |
400 | Invalid substage "X". Valid substages: ... | The sub-stage is not in the lifecycle configuration. |
400 | Required fields missing for substage "X": ... | A meta_data field the lifecycle configuration requires is missing or empty. |
400 | churn_reason is required when Drop off is triggered from Onboarding | "Drop off" from Onboarding without churn_reason. |
400 | Cannot transition from substage "A" to "B". Allowed transitions: ... | The move is not allowed by the lifecycle configuration. |
400 | Cannot move to OB Live. All onboarding tasks must be completed first. | sub_stage is OB Live and a required checklist task is not COMPLETED. |
401 | Invalid or missing token | No Bearer token, or the token failed validation. |
500 | Failed to fetch lifecycle configuration from ... | The lifecycle lookup failed. The message ends with an internal service name. In this response code is the number 500. |
500 | Internal server error while updating stage | Any other error. The underlying message is in the error field. |
What happens next
After "Meet Done", the product line is in Onboarding and its checklist exists. Check it with the endpoints in Checklist. If you call Create the onboarding checklist again for the same product line, it skips tasks that already exist.
Called before: Save handover details. Called after: Create the onboarding checklist, triggered automatically on "Meet Done".
Updated about 2 hours ago
