solarOS Organization API

Organization API

Send a draft Quote through solarOS

Sends the Quote using its configured customer delivery path. A service quote on a prepaid plan with no provider funding entered returns 409 CONFLICT with reason PREPAID_FUNDING_MISSING; the quote remains unsent. Hourly quotes return 400 VALIDATION_FAILED for invalid hourly terms, financing, a discount above the fixed-item total, or a legacy sourceProjectId add-work-order link; the quote remains unsent. Hourly quotes must use their own project or an explicit accepted parent-quote link. The optional body accepts additionalMessage; recipientEmail and message are not writable fields. A solar Quote is not sent while its calculations are running (409 CONFLICT, details.reason SOLAR_CALCULATIONS_RUNNING) or did not finish (SOLAR_CALCULATIONS_NOT_CURRENT — run POST /quotes/{id}/content/calculate first; when the last run stopped on a problem the client can fix, such as MISSING_BASELINE_TERRITORY, details.calculationErrors lists each { code, message } to fix before recalculating). A solar Quote with designed panels is not sent while its system has no base price (SOLAR_BASE_PRICE_MISSING) or while its saved base price per watt is below the price-list floor for the system size and no one confirmed it in solarOS (SOLAR_PRICE_BELOW_FLOOR). An API key cannot confirm a below-floor price. A solar Quote whose total is $0 is not sent (SOLAR_TOTAL_ZERO). A solar Quote is not sent while the plan its proposal leads with has no price (SOLAR_PLAN_NOT_PRICED): its default financing scenario is a lease or PPA with no payment, a PPA whose provider rate table has no offer for the system, or a prepaid plan with no customer share — or none of its financing scenarios has a price. Give that scenario its price (a PPA rate the provider's table offers, a prepaid plan's provider funding) or make a priced scenario the default with PATCH /quotes/{id}/content — a lease's payment is entered in solarOS — then run POST /quotes/{id}/content/calculate and send again. A solar Quote is not sent while it has more expansion batteries than its main batteries take (SOLAR_TOO_MANY_EXPANSIONS): each main battery product says how many expansions one unit takes in solarOS; remove expansions or add a main unit there, then send again. A solar Quote whose installer agreement goes out for signature by itself when the Quote is accepted is not sent while that agreement would print a value blank (AGREEMENT_VALUE_BLANK): details.blockers lists each { code, message }, the message naming the value, such as "Customer phone would print blank on the agreement." A value the agreement prints only on a condition the Quote doesn't meet is not counted, nor is one the Quote leaves blank by design. Fill each value — a Quote field with PATCH /quotes/{id}/content, a customer's with PATCH /customers/{id} — and send again. A solar Quote whose agreement prints annual production is not sent while that production is a regional estimate (SOLAR_PRODUCTION_ESTIMATED): run POST /quotes/{id}/content/calculate, then send. On a solar Quote, the response lists in warnings, each { code, message }, what solarOS only warns about; the Quote is sent regardless. SOLAR_INTERCONNECTION_YEAR_PASSED: its savings assume an interconnection application filed in a year it can no longer be filed in, and the utility credits exports differently in the year it can — POST /quotes/{id}/content/calculate on a draft prices it on that year, so run it before sending. SOLAR_STARTER_TEXT: what the homeowner receives still carries the placeholder text of the starter quote setup — its terms, scope of work, warranty or a line item description — or its payment schedule is still named as that setup named it; the message names each place. Replace the terms with PATCH /quotes/{id} and line descriptions through the content API; the scope of work, the warranty and the schedule's name are changed in solarOS.

/api/v1/quotes/{id}/send

Sends the Quote using its configured customer delivery path. A service quote on a prepaid plan with no provider funding entered returns 409 CONFLICT with reason PREPAID_FUNDING_MISSING; the quote remains unsent. Hourly quotes return 400 VALIDATION_FAILED for invalid hourly terms, financing, a discount above the fixed-item total, or a legacy sourceProjectId add-work-order link; the quote remains unsent. Hourly quotes must use their own project or an explicit accepted parent-quote link. The optional body accepts additionalMessage; recipientEmail and message are not writable fields. A solar Quote is not sent while its calculations are running (409 CONFLICT, details.reason SOLAR_CALCULATIONS_RUNNING) or did not finish (SOLAR_CALCULATIONS_NOT_CURRENT — run POST /quotes/{id}/content/calculate first; when the last run stopped on a problem the client can fix, such as MISSING_BASELINE_TERRITORY, details.calculationErrors lists each { code, message } to fix before recalculating). A solar Quote with designed panels is not sent while its system has no base price (SOLAR_BASE_PRICE_MISSING) or while its saved base price per watt is below the price-list floor for the system size and no one confirmed it in solarOS (SOLAR_PRICE_BELOW_FLOOR). An API key cannot confirm a below-floor price. A solar Quote whose total is $0 is not sent (SOLAR_TOTAL_ZERO). A solar Quote is not sent while the plan its proposal leads with has no price (SOLAR_PLAN_NOT_PRICED): its default financing scenario is a lease or PPA with no payment, a PPA whose provider rate table has no offer for the system, or a prepaid plan with no customer share — or none of its financing scenarios has a price. Give that scenario its price (a PPA rate the provider's table offers, a prepaid plan's provider funding) or make a priced scenario the default with PATCH /quotes/{id}/content — a lease's payment is entered in solarOS — then run POST /quotes/{id}/content/calculate and send again. A solar Quote is not sent while it has more expansion batteries than its main batteries take (SOLAR_TOO_MANY_EXPANSIONS): each main battery product says how many expansions one unit takes in solarOS; remove expansions or add a main unit there, then send again. A solar Quote whose installer agreement goes out for signature by itself when the Quote is accepted is not sent while that agreement would print a value blank (AGREEMENT_VALUE_BLANK): details.blockers lists each { code, message }, the message naming the value, such as "Customer phone would print blank on the agreement." A value the agreement prints only on a condition the Quote doesn't meet is not counted, nor is one the Quote leaves blank by design. Fill each value — a Quote field with PATCH /quotes/{id}/content, a customer's with PATCH /customers/{id} — and send again. A solar Quote whose agreement prints annual production is not sent while that production is a regional estimate (SOLAR_PRODUCTION_ESTIMATED): run POST /quotes/{id}/content/calculate, then send. On a solar Quote, the response lists in warnings, each { code, message }, what solarOS only warns about; the Quote is sent regardless. SOLAR_INTERCONNECTION_YEAR_PASSED: its savings assume an interconnection application filed in a year it can no longer be filed in, and the utility credits exports differently in the year it can — POST /quotes/{id}/content/calculate on a draft prices it on that year, so run it before sending. SOLAR_STARTER_TEXT: what the homeowner receives still carries the placeholder text of the starter quote setup — its terms, scope of work, warranty or a line item description — or its payment schedule is still named as that setup named it; the message names each place. Replace the terms with PATCH /quotes/{id} and line descriptions through the content API; the scope of work, the warranty and the schedule's name are changed in solarOS.

Send a draft Quote through solarOS. Use an organization API key with quotes:send. Check the response envelope and meta.requestId before triggering dependent workflow steps.

Required scopes

quotes:sendIdempotency-Key required

Parameters

NameInTypeDescription
idrequiredpathstringQuote identifier returned by the Organization API.

Request body

object
FieldTypeDescription
additionalMessagestring | nullOptional unless marked required.
sendquoteRequest
{
  "additionalMessage": "Here is the updated solar proposal for review."
}

Responses

200

Quote send action envelope, with warnings listing what solarOS only warns about

#/components/schemas/OrgApiActionEnvelope

401

Missing or invalid organization API key

#/components/schemas/OrgApiError

402

Organization billing access or an endpoint-specific business quota denied the request

#/components/schemas/OrgApiError

403

Missing required scope: quotes:send

#/components/schemas/OrgApiError

409

Quote is not a draft, travel pricing needs review, its solar calculations are not current, its solar system price is missing, below the floor unconfirmed, or $0, the plan its proposal leads with has no price, its agreement would print a value blank, or the production its agreement prints is a lookup estimate

#/components/schemas/OrgApiError

429

Rate limit exceeded

#/components/schemas/OrgApiError

Errors

401 Missing or invalid organization API key402 Organization billing access or an endpoint-specific business quota denied the request403 Missing required scope: quotes:send409 Quote is not a draft, travel pricing needs review, its solar calculations are not current, its solar system price is missing, below the floor unconfirmed, or $0, the plan its proposal leads with has no price, its agreement would print a value blank, or the production its agreement prints is a lookup estimate429 Rate limit exceeded

Static examples

curl -X POST "$SOLAROS_BASE_URL/api/v1/quotes/$SOLAROS_QUOTE/send" \
  -H "Authorization: Bearer $SOLAROS_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $SOLAROS_IDEMPOTENCY_KEY" \
  --data '{"additionalMessage":"Here is the updated solar proposal for review."}'