openapi: 3.1.0 info: title: API Key accounts quotes API version: 1.0.0 servers: - url: https://api.coperniq.io/v1 description: Production server tags: - name: quotes paths: /quotes: get: operationId: list-quotes summary: List Quotes description: 'Retrieve a paginated list of quotes. Supports: - Pagination (`page_size`, `page`) - Date filtering (`updated_after`, `updated_before`) - Status filtering (`status`) - Sorting (`order_by`) ' tags: - quotes parameters: - name: page_size in: query description: Number of items per page (max 100) required: false schema: type: integer default: 20 - name: page in: query description: Page number (1-based) required: false schema: type: integer default: 1 - name: order_by in: query description: Sort order for results required: false schema: $ref: '#/components/schemas/QuotesGetParametersOrderBy' - name: updated_after in: query description: Filter items updated after this timestamp (ISO 8601) required: false schema: type: string format: date-time - name: updated_before in: query description: Filter items updated before this timestamp (ISO 8601) required: false schema: type: string format: date-time - name: status in: query description: Filter quotes by status required: false schema: $ref: '#/components/schemas/QuotesGetParametersStatus' - name: x-api-key in: header required: true schema: type: string responses: '200': description: List of quotes content: application/json: schema: $ref: '#/components/schemas/QuoteListResponse' '400': description: Invalid request content: application/json: schema: $ref: '#/components/schemas/ListQuotesRequestBadRequestError' '401': description: Authentication failed content: application/json: schema: $ref: '#/components/schemas/ListQuotesRequestUnauthorizedError' post: operationId: create-quote summary: Create Quote description: 'Create a new quote on an opportunity (project/deal/account record). - `opportunityId`: Required — record ID used by the payment service. - `sections` or `lineItems`: At least one item required - `discountType` / `discountValue`: Optional discount - `taxId`: Optional tax - `issueDate` / `status`: `issueDate` only allowed with `status` of `SENT` or `APPROVED` To add or replace line items on a **work order**, use `PUT /work-orders/{workOrderId}/line-items` instead. ' tags: - quotes parameters: - name: x-api-key in: header required: true schema: type: string responses: '201': description: Quote created successfully content: application/json: schema: $ref: '#/components/schemas/Quote' '400': description: Invalid request content: application/json: schema: $ref: '#/components/schemas/CreateQuoteRequestBadRequestError' '401': description: Authentication failed content: application/json: schema: $ref: '#/components/schemas/CreateQuoteRequestUnauthorizedError' requestBody: content: application/json: schema: $ref: '#/components/schemas/QuoteCreate' /opportunities/{opportunityId}/quotes: get: operationId: get-opportunity-quotes summary: Get Opportunity Quotes description: Retrieve quotes for a specific opportunity. tags: - quotes parameters: - name: opportunityId in: path description: Opportunity ID required: true schema: type: integer - name: page_size in: query description: Number of items per page (max 100) required: false schema: type: integer default: 20 - name: page in: query description: Page number (1-based) required: false schema: type: integer default: 1 - name: order_by in: query description: Sort order for results required: false schema: $ref: '#/components/schemas/OpportunitiesOpportunityIdQuotesGetParametersOrderBy' - name: updated_after in: query description: Filter items updated after this timestamp (ISO 8601) required: false schema: type: string format: date-time - name: updated_before in: query description: Filter items updated before this timestamp (ISO 8601) required: false schema: type: string format: date-time - name: x-api-key in: header required: true schema: type: string responses: '200': description: List of quotes for the opportunity content: application/json: schema: $ref: '#/components/schemas/QuoteListResponse' '401': description: Authentication failed content: application/json: schema: $ref: '#/components/schemas/GetOpportunityQuotesRequestUnauthorizedError' /quotes/{quoteId}: get: operationId: get-quote summary: Get Quote description: Retrieve a specific quote by ID tags: - quotes parameters: - name: quoteId in: path required: true schema: type: string - name: x-api-key in: header required: true schema: type: string responses: '200': description: Quote details content: application/json: schema: $ref: '#/components/schemas/Quote' '401': description: Authentication failed content: application/json: schema: $ref: '#/components/schemas/GetQuoteRequestUnauthorizedError' '404': description: Resource not found content: application/json: schema: $ref: '#/components/schemas/GetQuoteRequestNotFoundError' patch: operationId: update-quote summary: Update Quote description: 'Update an existing quote. Supports partial updates. **`isArchived`**: pass `true` to archive or `false` to unarchive. When setting **`status`** to anything other than **`DRAFT`**, you must supply **`issueDate`** unless the quote already has one. **`sharedWithPortal`**: pass `true` or `false` to show or hide the quote on the client portal. ' tags: - quotes parameters: - name: quoteId in: path required: true schema: type: string - name: x-api-key in: header required: true schema: type: string responses: '200': description: Quote updated successfully content: application/json: schema: $ref: '#/components/schemas/Quote' '400': description: Invalid request content: application/json: schema: $ref: '#/components/schemas/UpdateQuoteRequestBadRequestError' '401': description: Authentication failed content: application/json: schema: $ref: '#/components/schemas/UpdateQuoteRequestUnauthorizedError' '404': description: Resource not found content: application/json: schema: $ref: '#/components/schemas/UpdateQuoteRequestNotFoundError' requestBody: content: application/json: schema: $ref: '#/components/schemas/QuoteUpdate' delete: operationId: delete-quote summary: Delete Quote description: 'Delete a quote. The quote is archived first (required by the payment service), then permanently deleted. ' tags: - quotes parameters: - name: quoteId in: path required: true schema: type: string - name: x-api-key in: header required: true schema: type: string responses: '200': description: Successful response '401': description: Authentication failed content: application/json: schema: $ref: '#/components/schemas/DeleteQuoteRequestUnauthorizedError' '404': description: Resource not found content: application/json: schema: $ref: '#/components/schemas/DeleteQuoteRequestNotFoundError' /quotes/{quoteId}/pdf: get: operationId: get-quote-pdf summary: Get Quote PDF description: 'Returns the quote PDF. Response is JSON with base64-encoded PDF (`pdfBase64`) and `contentType`. ' tags: - quotes parameters: - name: quoteId in: path required: true schema: type: string - name: x-api-key in: header required: true schema: type: string responses: '200': description: PDF payload (base64 JSON) content: application/json: schema: $ref: '#/components/schemas/Quotes_getQuotePdf_Response_200' '401': description: Authentication failed content: application/json: schema: $ref: '#/components/schemas/GetQuotePdfRequestUnauthorizedError' '403': description: API key valid but does not have access to this resource content: application/json: schema: $ref: '#/components/schemas/GetQuotePdfRequestForbiddenError' '404': description: Resource not found content: application/json: schema: $ref: '#/components/schemas/GetQuotePdfRequestNotFoundError' /quotes/{quoteId}/send: post: operationId: send-quote summary: Send Quote description: 'Email the quote to the customer and transition **DRAFT** to **SENT** when applicable. ' tags: - quotes parameters: - name: quoteId in: path required: true schema: type: string - name: x-api-key in: header required: true schema: type: string responses: '200': description: Quote sent; returns the updated quote content: application/json: schema: $ref: '#/components/schemas/Quote' '401': description: Authentication failed content: application/json: schema: $ref: '#/components/schemas/SendQuoteRequestUnauthorizedError' '404': description: Resource not found content: application/json: schema: $ref: '#/components/schemas/SendQuoteRequestNotFoundError' components: schemas: QuoteCreate: type: object properties: opportunityId: type: integer description: Opportunity / project / account record ID sections: type: array items: $ref: '#/components/schemas/QuoteSection' description: Grouped line items. Required if `lineItems` is not provided. lineItems: type: array items: $ref: '#/components/schemas/QuoteLineItemCreate' description: Flat line items (grouped into one section automatically). Required if `sections` is not provided. discountType: $ref: '#/components/schemas/QuoteCreateDiscountType' description: Discount type discountValue: type: number format: double description: Discount amount or percentage taxId: type: integer description: Tax configuration ID issueDate: type: string format: date-time description: Issue date. Requires `status` to be `SENT` or `APPROVED`. status: $ref: '#/components/schemas/QuoteCreateStatus' description: Initial status (defaults to DRAFT) description: type: - string - 'null' title: type: string description: Quote title (defaults to "Quote") servicePlanInstanceId: type: integer siteId: type: integer required: - opportunityId description: '`opportunityId` is the record (project/opportunity/account) id used by the payment service. When the quote is for a **Client (Account)** record, provide **`siteId`** (or use work-order line items via `PUT /work-orders/{workOrderId}/line-items`) so the quote can be linked correctly. ' title: QuoteCreate QuotesGetParametersStatus: type: string enum: - DRAFT - SENT - APPROVED - CHANGES_REQUESTED - DECLINED title: QuotesGetParametersStatus ListQuotesRequestBadRequestError: type: object properties: message: type: string code: $ref: '#/components/schemas/QuotesGetResponsesContentApplicationJsonSchemaCode' field: type: string description: Field that caused the validation error (if applicable) title: ListQuotesRequestBadRequestError QuoteSection: type: object properties: id: type: integer description: Section identifier name: type: - string - 'null' description: Section name (1–500 chars) lineItems: type: array items: $ref: '#/components/schemas/QuoteLineItemCreate' description: Line items within this section (at least one required) required: - lineItems title: QuoteSection QuotesGetResponsesContentApplicationJsonSchemaCode: type: string enum: - UNAUTHORIZED title: QuotesGetResponsesContentApplicationJsonSchemaCode GetQuotePdfRequestForbiddenError: type: object properties: message: type: string code: $ref: '#/components/schemas/QuotesQuoteIdPdfGetResponsesContentApplicationJsonSchemaCode' title: GetQuotePdfRequestForbiddenError GetQuotePdfRequestUnauthorizedError: type: object properties: message: type: string code: $ref: '#/components/schemas/QuotesQuoteIdPdfGetResponsesContentApplicationJsonSchemaCode' title: GetQuotePdfRequestUnauthorizedError QuoteRecord: type: object properties: id: type: integer title: type: - string - 'null' type: type: - string - 'null' title: QuoteRecord SendQuoteRequestUnauthorizedError: type: object properties: message: type: string code: $ref: '#/components/schemas/QuotesQuoteIdSendPostResponsesContentApplicationJsonSchemaCode' title: SendQuoteRequestUnauthorizedError QuotesQuoteIdPdfGetResponsesContentApplicationJsonSchemaCode: type: string enum: - NOT_FOUND title: QuotesQuoteIdPdfGetResponsesContentApplicationJsonSchemaCode QuoteStatus: type: string enum: - DRAFT - SENT - APPROVED - CHANGES_REQUESTED - DECLINED description: Quote status title: QuoteStatus OpportunitiesOpportunityIdQuotesGetParametersOrderBy: type: string enum: - asc - desc default: asc title: OpportunitiesOpportunityIdQuotesGetParametersOrderBy QuoteListResponse: type: array items: $ref: '#/components/schemas/Quote' title: QuoteListResponse ListQuotesRequestUnauthorizedError: type: object properties: message: type: string code: $ref: '#/components/schemas/QuotesGetResponsesContentApplicationJsonSchemaCode' title: ListQuotesRequestUnauthorizedError UpdateQuoteRequestBadRequestError: type: object properties: message: type: string code: $ref: '#/components/schemas/QuotesQuoteIdPatchResponsesContentApplicationJsonSchemaCode' field: type: string description: Field that caused the validation error (if applicable) title: UpdateQuoteRequestBadRequestError CreateQuoteRequestUnauthorizedError: type: object properties: message: type: string code: $ref: '#/components/schemas/QuotesPostResponsesContentApplicationJsonSchemaCode' title: CreateQuoteRequestUnauthorizedError QuoteCreateStatus: type: string enum: - DRAFT - SENT - APPROVED - CHANGES_REQUESTED - DECLINED description: Initial status (defaults to DRAFT) title: QuoteCreateStatus QuoteUpdateStatus: type: string enum: - DRAFT - SENT - APPROVED - CHANGES_REQUESTED - DECLINED title: QuoteUpdateStatus SendQuoteRequestNotFoundError: type: object properties: message: type: string code: $ref: '#/components/schemas/QuotesQuoteIdSendPostResponsesContentApplicationJsonSchemaCode' title: SendQuoteRequestNotFoundError GetOpportunityQuotesRequestUnauthorizedError: type: object properties: message: type: string code: $ref: '#/components/schemas/OpportunitiesOpportunityIdQuotesGetResponsesContentApplicationJsonSchemaCode' title: GetOpportunityQuotesRequestUnauthorizedError QuoteLineItemCreate: type: object properties: id: type: integer description: Existing line item ID (omit for new items) catalogItemId: type: integer description: Catalog item identifier quantity: type: integer description: Quantity unitCost: type: number format: double description: Cost per unit (dollars, cent precision) unitPrice: type: number format: double description: Price per unit (dollars, must be positive with cent precision) description: type: - string - 'null' description: Line item description required: - catalogItemId - quantity - unitCost - unitPrice title: QuoteLineItemCreate QuoteLineItemCatalogItem: type: object properties: id: type: integer name: type: string sku: type: - string - 'null' type: type: - string - 'null' manufacturer: type: - string - 'null' title: QuoteLineItemCatalogItem UpdateQuoteRequestNotFoundError: type: object properties: message: type: string code: $ref: '#/components/schemas/QuotesQuoteIdPatchResponsesContentApplicationJsonSchemaCode' title: UpdateQuoteRequestNotFoundError QuoteCreateDiscountType: type: string enum: - PERCENTAGE - FIXED description: Discount type title: QuoteCreateDiscountType OpportunitiesOpportunityIdQuotesGetResponsesContentApplicationJsonSchemaCode: type: string enum: - UNAUTHORIZED title: OpportunitiesOpportunityIdQuotesGetResponsesContentApplicationJsonSchemaCode Quote: type: object properties: id: type: integer description: Quote identifier quoteNumber: type: - integer - 'null' description: Unique quote number (uid) description: type: - string - 'null' description: Quote description status: $ref: '#/components/schemas/QuoteStatus' description: Quote status amount: type: number format: double description: Total quote amount (dollars) discountType: oneOf: - $ref: '#/components/schemas/QuoteDiscountType' - type: 'null' description: Discount type discountValue: type: - number - 'null' format: double description: Discount amount or percentage value taxId: type: - integer - 'null' description: Tax configuration ID issueDate: type: - string - 'null' format: date-time description: Issue date when the quote has been issued subtotal: type: - number - 'null' format: double description: Subtotal in dollars (before tax) taxRate: type: - number - 'null' format: double description: Applied tax rate isArchived: type: boolean description: Whether the quote is archived sharedWithPortal: type: boolean description: Whether the quote is shared with the client portal createdAt: type: string format: date-time description: Creation timestamp updatedAt: type: string format: date-time description: Last update timestamp account: oneOf: - $ref: '#/components/schemas/QuoteAccount' - type: 'null' record: oneOf: - $ref: '#/components/schemas/QuoteRecord' - type: 'null' lineItems: type: array items: $ref: '#/components/schemas/QuoteLineItem' required: - id - status - amount - createdAt - updatedAt title: Quote GetQuotePdfRequestNotFoundError: type: object properties: message: type: string code: $ref: '#/components/schemas/QuotesQuoteIdPdfGetResponsesContentApplicationJsonSchemaCode' title: GetQuotePdfRequestNotFoundError Quotes_getQuotePdf_Response_200: type: object properties: pdfBase64: type: string contentType: type: string title: Quotes_getQuotePdf_Response_200 QuotesQuoteIdDeleteResponsesContentApplicationJsonSchemaCode: type: string enum: - NOT_FOUND title: QuotesQuoteIdDeleteResponsesContentApplicationJsonSchemaCode QuoteLineItem: type: object properties: id: type: integer description: Line item identifier quantity: type: integer description: Quantity description: type: - string - 'null' description: Line item description unitCost: type: number format: double description: Cost per unit (dollars) totalCost: type: number format: double description: Total cost (dollars) unitPrice: type: number format: double description: Price per unit (dollars) totalPrice: type: number format: double description: Total price (dollars) sectionName: type: - string - 'null' description: Line item section name; null when the item is ungrouped catalogItem: oneOf: - $ref: '#/components/schemas/QuoteLineItemCatalogItem' - type: 'null' required: - id - quantity - unitCost - unitPrice title: QuoteLineItem QuotesQuoteIdSendPostResponsesContentApplicationJsonSchemaCode: type: string enum: - NOT_FOUND title: QuotesQuoteIdSendPostResponsesContentApplicationJsonSchemaCode UpdateQuoteRequestUnauthorizedError: type: object properties: message: type: string code: $ref: '#/components/schemas/QuotesQuoteIdPatchResponsesContentApplicationJsonSchemaCode' title: UpdateQuoteRequestUnauthorizedError CreateQuoteRequestBadRequestError: type: object properties: message: type: string code: $ref: '#/components/schemas/QuotesPostResponsesContentApplicationJsonSchemaCode' field: type: string description: Field that caused the validation error (if applicable) title: CreateQuoteRequestBadRequestError QuoteUpdate: type: object properties: sections: type: array items: $ref: '#/components/schemas/QuoteSection' lineItems: type: array items: $ref: '#/components/schemas/QuoteLineItemCreate' discountType: $ref: '#/components/schemas/QuoteUpdateDiscountType' discountValue: type: number format: double taxId: type: - integer - 'null' issueDate: type: string format: date-time status: $ref: '#/components/schemas/QuoteUpdateStatus' description: type: - string - 'null' sharedWithPortal: type: boolean description: When true, the quote is visible on the client portal; when false, it is hidden. isArchived: type: boolean description: Set true to archive or false to unarchive (maps to payment service archive/unarchive routes) title: QuoteUpdate QuoteDiscountType: type: string enum: - PERCENTAGE - FIXED description: Discount type title: QuoteDiscountType QuotesQuoteIdPatchResponsesContentApplicationJsonSchemaCode: type: string enum: - NOT_FOUND title: QuotesQuoteIdPatchResponsesContentApplicationJsonSchemaCode GetQuoteRequestUnauthorizedError: type: object properties: message: type: string code: $ref: '#/components/schemas/QuotesQuoteIdGetResponsesContentApplicationJsonSchemaCode' title: GetQuoteRequestUnauthorizedError QuoteAccount: type: object properties: id: type: integer title: type: - string - 'null' title: QuoteAccount DeleteQuoteRequestNotFoundError: type: object properties: message: type: string code: $ref: '#/components/schemas/QuotesQuoteIdDeleteResponsesContentApplicationJsonSchemaCode' title: DeleteQuoteRequestNotFoundError QuotesPostResponsesContentApplicationJsonSchemaCode: type: string enum: - UNAUTHORIZED title: QuotesPostResponsesContentApplicationJsonSchemaCode GetQuoteRequestNotFoundError: type: object properties: message: type: string code: $ref: '#/components/schemas/QuotesQuoteIdGetResponsesContentApplicationJsonSchemaCode' title: GetQuoteRequestNotFoundError QuoteUpdateDiscountType: type: string enum: - PERCENTAGE - FIXED title: QuoteUpdateDiscountType DeleteQuoteRequestUnauthorizedError: type: object properties: message: type: string code: $ref: '#/components/schemas/QuotesQuoteIdDeleteResponsesContentApplicationJsonSchemaCode' title: DeleteQuoteRequestUnauthorizedError QuotesQuoteIdGetResponsesContentApplicationJsonSchemaCode: type: string enum: - NOT_FOUND title: QuotesQuoteIdGetResponsesContentApplicationJsonSchemaCode QuotesGetParametersOrderBy: type: string enum: - asc - desc default: asc title: QuotesGetParametersOrderBy securitySchemes: BasicAuth: type: http scheme: basic