Estimate exports

6 endpointsEnglish only

List an estimate's hand-offs

get/estimates/{estimateId}/handoffsexperimentalSession required

Returns every recorded hand-off, newest createdAt first. Responds 403 when the caller is not a member of the estimate's workshop and 404 when no such estimate exists.

Path parameters

NameTypeRequiredConstraintsDescription
estimateIdstringYes-Estimate id

Responses

  • 200Success
    FieldTypeRequiredConstraintsDescription
    handoffsarray of HandoffYes--
    Example
    {
      "handoffs": [
        {
          "id": "handoff-1",
          "kind": "pdf-shared",
          "locale": "pl",
          "preliminary": false,
          "tone": "friendly",
          "aiDrafted": false,
          "revisionNumber": 1,
          "createdAt": "2026-08-10T14:40:00.000Z",
          "createdByUid": "uid-1",
          "pdfExportId": "pdf-export-1"
        }
      ]
    }
  • 401Not authenticated

    No body.

  • 403Forbidden

    No body.

  • 404Not found

    No body.

Example request

curl -X GET 'https://mercastra.cloud/api/estimates/<estimateId>/handoffs' \
  -H 'Cookie: session=<session cookie>'

Record that an estimate was handed off to its recipient

post/estimates/{estimateId}/handoffsexperimentalSession required

Records that a hand-off happened; it does not send anything itself, the caller performs the copy, share or open action and reports it here afterward. None of kind, tone or aiDrafted is verified against anything that actually happened, they are trusted as given. The body is strict, an unknown field is a 400. When pdfExportId is given it must identify an existing pdf export on this estimate; if it does not, this also responds 400, but with an empty body, not the ValidationError JSON a schema violation sends - the two 400 causes are otherwise indistinguishable from the status code alone. Responds 403 when the caller is not a member of the estimate's workshop and 404 when no such estimate exists.

Path parameters

NameTypeRequiredConstraintsDescription
estimateIdstringYes-Estimate id

Request body

FieldTypeRequiredConstraintsDescription
kindHandoffKindYes-How a crafter delivered an estimate to its recipient, one of these five values: message-copied, message-shared, pdf-shared, pdf-and-message-shared or email-opened. Caller-supplied and not verified against anything that actually happened, see the hand-off endpoint's own description.
localeDocumentLocaleYes-The language a generated estimate PDF or hand-off message is written in: pl (Polish) or en (English).
preliminarybooleanYes--
toneMessageToneYes-The tone of a hand-off message, formal or friendly. Recorded on every hand-off regardless of aiDrafted, not just ones whose text came from the AI draft endpoint.
aiDraftedbooleanYes--
pdfExportIdstringNominLength: 1An existing pdf export to associate with this hand-off, when one was involved.
Example
{
  "kind": "pdf-shared",
  "locale": "pl",
  "preliminary": false,
  "tone": "friendly",
  "aiDrafted": false,
  "pdfExportId": "pdf-export-1"
}

Responses

  • 201Created
    FieldTypeRequiredConstraintsDescription
    idstringYes--
    kindHandoffKindYes-How a crafter delivered an estimate to its recipient, one of these five values: message-copied, message-shared, pdf-shared, pdf-and-message-shared or email-opened. Caller-supplied and not verified against anything that actually happened, see the hand-off endpoint's own description.
    localeDocumentLocaleYes-The language a generated estimate PDF or hand-off message is written in: pl (Polish) or en (English).
    preliminarybooleanYes--
    toneMessageToneYes-The tone of a hand-off message, formal or friendly. Recorded on every hand-off regardless of aiDrafted, not just ones whose text came from the AI draft endpoint.
    aiDraftedbooleanYes-Whether the delivered message text came from POST .../message-draft rather than being hand-written.
    revisionNumbernumberYes-The estimate's latest revision number at the time of hand-off.
    createdAtstringYesformat: "date-time"-
    createdByUidstringYes--
    pdfExportIdstringNo-The pdf export this hand-off refers to, when one was involved. Absent otherwise.
    Example
    {
      "id": "handoff-1",
      "kind": "pdf-shared",
      "locale": "pl",
      "preliminary": false,
      "tone": "friendly",
      "aiDrafted": false,
      "revisionNumber": 1,
      "createdAt": "2026-08-10T14:40:00.000Z",
      "createdByUid": "uid-1",
      "pdfExportId": "pdf-export-1"
    }
  • 400Either the body fails schema validation (ValidationError JSON) or pdfExportId does not identify a pdf export belonging to this estimate (empty body) - see this endpoint's description.

    The shared validation error body, see Validation errors.

  • 401Not authenticated

    No body.

  • 403Forbidden

    No body.

  • 404Not found

    No body.

Example request

curl -X POST 'https://mercastra.cloud/api/estimates/<estimateId>/handoffs' \
  -H 'Cookie: session=<session cookie>' \
  -H 'X-CSRF-Token: <csrf-token cookie value>' \
  -H 'Content-Type: application/json' \
  -d '{"kind":"pdf-shared","locale":"pl","preliminary":false,"tone":"friendly","aiDrafted":false,"pdfExportId":"pdf-export-1"}'

List an estimate's PDF exports

get/estimates/{estimateId}/pdf-exportsexperimentalSession required

Returns every stored pdf export's metadata, newest generatedAt first. Responds 403 when the caller is not a member of the estimate's workshop and 404 when no such estimate exists.

Path parameters

NameTypeRequiredConstraintsDescription
estimateIdstringYes-Estimate id

Responses

  • 200Success
    FieldTypeRequiredConstraintsDescription
    pdfExportsarray of PdfExportYes--
    Example
    {
      "pdfExports": [
        {
          "id": "pdf-export-1",
          "revisionNumber": 1,
          "toggles": {
            "breakdown": true,
            "showMarkup": true,
            "showDiscount": false,
            "showRecipient": true,
            "showBrandMark": true
          },
          "generatedAt": "2026-08-10T14:35:00.000Z",
          "createdByUid": "uid-1",
          "locale": "en",
          "preliminary": false
        }
      ]
    }
  • 401Not authenticated

    No body.

  • 403Forbidden

    No body.

  • 404Not found

    No body.

Example request

curl -X GET 'https://mercastra.cloud/api/estimates/<estimateId>/pdf-exports' \
  -H 'Cookie: session=<session cookie>'

Generate a PDF export of an estimate

post/estimates/{estimateId}/pdf-exportsexperimentalSession required

Renders the estimate's current content (not a specific revision) into a PDF, stores it as a file scoped to the estimate and records its metadata; every toggle and locale defaults server-side when omitted, see PdfExportOptions and DocumentLocale. The body is strict, an unknown field is a 400. Responds 201 with the new PdfExport and a Location header pointing to GET .../pdf-exports/{pdfExportId}. Responds 403 when the caller is not a member of the estimate's workshop and 404 when no such estimate exists. Responds 413 with an empty body when generating this PDF would exceed the workshop's plan file storage quota.

Path parameters

NameTypeRequiredConstraintsDescription
estimateIdstringYes-Estimate id

Request body

FieldTypeRequiredConstraintsDescription
breakdownbooleanNodefault: trueInclude the itemized line-item breakdown table.
showMarkupbooleanNodefault: falseShow the markup percentage and amount.
showDiscountbooleanNodefault: falseShow the discount percentage and amount.
showRecipientbooleanNodefault: trueShow the recipient's name.
showBrandMarkbooleanNodefault: trueShow Mercastra's brand mark. A workshop whose plan cannot disable it (see entitlements) has this forced back to true server-side regardless of what is sent.
localeDocumentLocaleNo-Defaults to Polish.
preliminarybooleanNodefault: falseMarks the PDF as a preliminary estimate, changing its disclaimer text.
Example
{
  "locale": "en",
  "showMarkup": true
}

Responses

  • 201Created
    FieldTypeRequiredConstraintsDescription
    idstringYes--
    revisionNumbernumberYes-The estimate's latest revision number at the time of export.
    togglesPdfExportOptionsYes-The visual toggles for a generated estimate PDF, each defaulting server-side when omitted.
    generatedAtstringYesformat: "date-time"-
    createdByUidstringYes--
    localeDocumentLocaleNo-Absent only on a pdf export generated before locale was tracked.
    preliminarybooleanNo-Absent only on a pdf export generated before preliminary was tracked.
    Example
    {
      "id": "pdf-export-1",
      "revisionNumber": 1,
      "toggles": {
        "breakdown": true,
        "showMarkup": true,
        "showDiscount": false,
        "showRecipient": true,
        "showBrandMark": true
      },
      "generatedAt": "2026-08-10T14:35:00.000Z",
      "createdByUid": "uid-1",
      "locale": "en",
      "preliminary": false
    }
  • 400The request body does not match the schema

    The shared validation error body, see Validation errors.

  • 401Not authenticated

    No body.

  • 403Forbidden

    No body.

  • 404Not found

    No body.

  • 413Generating this PDF would exceed the workshop's plan file storage quota

    No body.

Example request

curl -X POST 'https://mercastra.cloud/api/estimates/<estimateId>/pdf-exports' \
  -H 'Cookie: session=<session cookie>' \
  -H 'X-CSRF-Token: <csrf-token cookie value>' \
  -H 'Content-Type: application/json' \
  -d '{"locale":"en","showMarkup":true}'

Download a PDF export's bytes

get/estimates/{estimateId}/pdf-exports/{pdfExportId}experimentalSession required

Streams the stored PDF with Content-Disposition: attachment and a filename derived from the export's locale (estimate-<id>.pdf for en, wycena-<id>.pdf for pl). Responds 403 when the caller is not a member of the estimate's workshop and 404 both when no such estimate or pdf export exists and when the pdf export id belongs to a different estimate - the three cases are indistinguishable in the response.

Path parameters

NameTypeRequiredConstraintsDescription
estimateIdstringYes-Estimate id
pdfExportIdstringYes-Pdf export id

Responses

  • 200Success

    No body.

  • 401Not authenticated

    No body.

  • 403Forbidden

    No body.

  • 404Not found

    No body.

Example request

curl -X GET 'https://mercastra.cloud/api/estimates/<estimateId>/pdf-exports/<pdfExportId>' \
  -H 'Cookie: session=<session cookie>'

Delete a PDF export

delete/estimates/{estimateId}/pdf-exports/{pdfExportId}experimentalSession required

Permanently deletes the pdf export's metadata record and its stored bytes, freeing the workshop's file storage quota it used. Responds 200 with an empty body on success, not 204 - a hand-maintained doc has previously claimed 204, that was wrong, the route calls res.status(200).send(). Responds 403 when the caller is not a member of the estimate's workshop and 404 both when no such estimate or pdf export exists and when the pdf export id belongs to a different estimate - the three cases are indistinguishable in the response.

Path parameters

NameTypeRequiredConstraintsDescription
estimateIdstringYes-Estimate id
pdfExportIdstringYes-Pdf export id

Responses

  • 200Success

    No body.

  • 401Not authenticated

    No body.

  • 403Forbidden

    No body.

  • 404Not found

    No body.

Example request

curl -X DELETE 'https://mercastra.cloud/api/estimates/<estimateId>/pdf-exports/<pdfExportId>' \
  -H 'Cookie: session=<session cookie>' \
  -H 'X-CSRF-Token: <csrf-token cookie value>'