Estimates

12 endpointówTylko po angielsku

Read an estimate

get/estimates/{estimateId}experimentalWymagana sesja

Returns the estimate directly, not wrapped in an envelope object (unlike the list endpoint's estimates key). Responds 403 when the caller is not a member of the estimate's workshop and 404 when no such estimate exists.

Parametry ścieżki

NazwaTypWymaganeOgraniczeniaOpis
estimateIdstringTak-Estimate id

Odpowiedzi

  • 200Success
    PoleTypWymaganeOgraniczeniaOpis
    idstringTak--
    workshopIdstringTak--
    namestringTak--
    recipientNamestringNie-Absent when the estimate has no named recipient
    createdAtstringTakformat: "date-time"-
    updatedAtstringTakformat: "date-time"-
    markupPercentnumberTak--
    discountPercentnumberTak--
    lineItemsarray of LineItemTak--
    Przykład
    {
      "id": "estimate-1",
      "workshopId": "workshop-1",
      "name": "Bespoke chair estimate",
      "recipientName": "Anna Kowalska",
      "createdAt": "2026-08-01T09:00:00.000Z",
      "updatedAt": "2026-08-10T14:30:00.000Z",
      "markupPercent": 10,
      "discountPercent": 0,
      "lineItems": [
        {
          "id": "line-item-1",
          "label": "Oak plank, 2m",
          "unit": "sqm",
          "unitPrice": 120,
          "quantity": 4
        }
      ]
    }
  • 401Not authenticated

    Brak treści.

  • 403Forbidden

    Brak treści.

  • 404Not found

    Brak treści.

Przykładowe żądanie

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

Update an estimate's name, recipient, markup or discount

patch/estimates/{estimateId}experimentalWymagana sesja

Updates any subset of name, recipientName, markupPercent and discountPercent; unlike the order equivalent, an empty body is accepted, it only bumps updatedAt. The body is strict, an unknown field is a 400. A provided name must be non-blank after trimming; a provided recipientName may be an empty string, which clears the field entirely rather than storing blank text. Line items are not editable through this endpoint, see the line-item endpoints below. None of these fields counts as a new revision, call confirm to snapshot the result. Responds 403 when the caller is not a member of the estimate's workshop and 404 when no such estimate exists.

Parametry ścieżki

NazwaTypWymaganeOgraniczeniaOpis
estimateIdstringTak-Estimate id

Treść żądania

PoleTypWymaganeOgraniczeniaOpis
namestringNieminLength: 1-
recipientNamestringNie--
markupPercentnumberNieminimum: 0-
discountPercentnumberNieminimum: 0maximum: 100-
Przykład
{
  "markupPercent": 15
}

Odpowiedzi

  • 200Success
    PoleTypWymaganeOgraniczeniaOpis
    idstringTak--
    workshopIdstringTak--
    namestringTak--
    recipientNamestringNie-Absent when the estimate has no named recipient
    createdAtstringTakformat: "date-time"-
    updatedAtstringTakformat: "date-time"-
    markupPercentnumberTak--
    discountPercentnumberTak--
    lineItemsarray of LineItemTak--
    Przykład
    {
      "id": "estimate-1",
      "workshopId": "workshop-1",
      "name": "Bespoke chair estimate",
      "recipientName": "Anna Kowalska",
      "createdAt": "2026-08-01T09:00:00.000Z",
      "updatedAt": "2026-08-10T14:30:00.000Z",
      "markupPercent": 15,
      "discountPercent": 0,
      "lineItems": [
        {
          "id": "line-item-1",
          "label": "Oak plank, 2m",
          "unit": "sqm",
          "unitPrice": 120,
          "quantity": 4
        }
      ]
    }
  • 400The request body does not match the schema

    Wspólna treść błędu walidacji, zobacz Błędy walidacji.

  • 401Not authenticated

    Brak treści.

  • 403Forbidden

    Brak treści.

  • 404Not found

    Brak treści.

Przykładowe żądanie

curl -X PATCH 'https://mercastra.cloud/api/estimates/<estimateId>' \
  -H 'Cookie: session=<session cookie>' \
  -H 'X-CSRF-Token: <csrf-token cookie value>' \
  -H 'Content-Type: application/json' \
  -d '{"markupPercent":15}'

Delete an estimate

delete/estimates/{estimateId}experimentalWymagana sesja

Permanently deletes the estimate, its revision history, its PDF exports and its hand-off history, and frees the workshop's file storage quota used by its stored PDFs; the PDF files themselves are deleted too. Responds with an empty body on success. Responds 403 when the caller is not a member of the estimate's workshop and 404 when no such estimate exists.

Parametry ścieżki

NazwaTypWymaganeOgraniczeniaOpis
estimateIdstringTak-Estimate id

Odpowiedzi

  • 200Success

    Brak treści.

  • 401Not authenticated

    Brak treści.

  • 403Forbidden

    Brak treści.

  • 404Not found

    Brak treści.

Przykładowe żądanie

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

Snapshot an estimate's current content as a new revision

post/estimates/{estimateId}/confirmexperimentalWymagana sesja

Estimate creation and duplication already write revision 1; confirm snapshots the estimate's current name, markup, discount and line items as the next revisionNumber. After writing it, revisions beyond the workshop's plan cap (GET /workshops/{workshopId}/entitlements's revisions.limitPerEstimate, null on Pro) are pruned silently, oldest first - there is no error response for exceeding the cap, the history is simply trimmed. The cap is only enforced at confirm time, so a plan downgrade does not retroactively prune existing history until the next confirm. Responds 403 when the caller is not a member of the estimate's workshop and 404 when no such estimate exists.

Parametry ścieżki

NazwaTypWymaganeOgraniczeniaOpis
estimateIdstringTak-Estimate id

Odpowiedzi

  • 201Created
    PoleTypWymaganeOgraniczeniaOpis
    estimateEstimateTak-An estimate as returned to a member. Confirming it (see the confirm endpoint) snapshots this content into a new revision; nothing about the estimate itself records whether it has ever been confirmed.
    revisionEstimateRevisionTak-A snapshot of an estimate's content at the moment it was confirmed, numbered from 1.
    Przykład
    {
      "estimate": {
        "id": "estimate-1",
        "workshopId": "workshop-1",
        "name": "Bespoke chair estimate",
        "recipientName": "Anna Kowalska",
        "createdAt": "2026-08-01T09:00:00.000Z",
        "updatedAt": "2026-08-10T14:30:00.000Z",
        "markupPercent": 10,
        "discountPercent": 0,
        "lineItems": [
          {
            "id": "line-item-1",
            "label": "Oak plank, 2m",
            "unit": "sqm",
            "unitPrice": 120,
            "quantity": 4
          }
        ]
      },
      "revision": {
        "id": "revision-1",
        "revisionNumber": 1,
        "createdAt": "2026-08-01T09:00:00.000Z",
        "createdByUid": "uid-1",
        "name": "Bespoke chair estimate",
        "markupPercent": 10,
        "discountPercent": 0,
        "lineItems": [
          {
            "id": "line-item-1",
            "label": "Oak plank, 2m",
            "unit": "sqm",
            "unitPrice": 120,
            "quantity": 4
          }
        ]
      }
    }
  • 401Not authenticated

    Brak treści.

  • 403Forbidden

    Brak treści.

  • 404Not found

    Brak treści.

Przykładowe żądanie

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

Duplicate an estimate

post/estimates/{estimateId}/duplicateexperimentalWymagana sesja

Creates a new estimate copying name (suffixed with " (copy)"), recipientName, markupPercent, discountPercent and every line item, each assigned a fresh id; the source's revision history is not copied, the duplicate gets its own fresh revision 1 immediately, same as a plain create. Responds 403 when the caller is not a member of the estimate's workshop and 404 when no such estimate exists.

Parametry ścieżki

NazwaTypWymaganeOgraniczeniaOpis
estimateIdstringTak-Estimate id

Odpowiedzi

  • 201Created
    PoleTypWymaganeOgraniczeniaOpis
    idstringTak--
    workshopIdstringTak--
    namestringTak--
    recipientNamestringNie-Absent when the estimate has no named recipient
    createdAtstringTakformat: "date-time"-
    updatedAtstringTakformat: "date-time"-
    markupPercentnumberTak--
    discountPercentnumberTak--
    lineItemsarray of LineItemTak--
    Przykład
    {
      "id": "estimate-2",
      "workshopId": "workshop-1",
      "name": "Bespoke chair estimate (copy)",
      "recipientName": "Anna Kowalska",
      "createdAt": "2026-08-01T09:00:00.000Z",
      "updatedAt": "2026-08-10T14:30:00.000Z",
      "markupPercent": 10,
      "discountPercent": 0,
      "lineItems": [
        {
          "id": "line-item-1",
          "label": "Oak plank, 2m",
          "unit": "sqm",
          "unitPrice": 120,
          "quantity": 4
        }
      ]
    }
  • 401Not authenticated

    Brak treści.

  • 403Forbidden

    Brak treści.

  • 404Not found

    Brak treści.

Przykładowe żądanie

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

Add a line item to an estimate

post/estimates/{estimateId}/line-itemsexperimentalWymagana sesja

Appends one line item, assigned a fresh id, and returns the whole updated estimate, not just the new item. The body is not strict, an unknown field is silently dropped rather than rejected. sourceButtonId (a grid button id) is stored as given, not validated against the workshop's grids. Responds 403 when the caller is not a member of the estimate's workshop and 404 when no such estimate exists.

Parametry ścieżki

NazwaTypWymaganeOgraniczeniaOpis
estimateIdstringTak-Estimate id

Treść żądania

PoleTypWymaganeOgraniczeniaOpis
labelstringTakminLength: 1-
unitLineItemUnitTak-A line item's unit of measure, one of these seven values.
unitPricenumberTakminimum: 0-
quantitynumberTakexclusiveMinimum: 0-
sourceButtonIdstringNie--
Przykład
{
  "label": "Oak plank, 2m",
  "unit": "sqm",
  "unitPrice": 120,
  "quantity": 4
}

Odpowiedzi

  • 201Created
    PoleTypWymaganeOgraniczeniaOpis
    idstringTak--
    workshopIdstringTak--
    namestringTak--
    recipientNamestringNie-Absent when the estimate has no named recipient
    createdAtstringTakformat: "date-time"-
    updatedAtstringTakformat: "date-time"-
    markupPercentnumberTak--
    discountPercentnumberTak--
    lineItemsarray of LineItemTak--
    Przykład
    {
      "id": "estimate-1",
      "workshopId": "workshop-1",
      "name": "Bespoke chair estimate",
      "recipientName": "Anna Kowalska",
      "createdAt": "2026-08-01T09:00:00.000Z",
      "updatedAt": "2026-08-10T14:30:00.000Z",
      "markupPercent": 10,
      "discountPercent": 0,
      "lineItems": [
        {
          "id": "line-item-1",
          "label": "Oak plank, 2m",
          "unit": "sqm",
          "unitPrice": 120,
          "quantity": 4
        }
      ]
    }
  • 400The request body does not match the schema

    Wspólna treść błędu walidacji, zobacz Błędy walidacji.

  • 401Not authenticated

    Brak treści.

  • 403Forbidden

    Brak treści.

  • 404Not found

    Brak treści.

Przykładowe żądanie

curl -X POST 'https://mercastra.cloud/api/estimates/<estimateId>/line-items' \
  -H 'Cookie: session=<session cookie>' \
  -H 'X-CSRF-Token: <csrf-token cookie value>' \
  -H 'Content-Type: application/json' \
  -d '{"label":"Oak plank, 2m","unit":"sqm","unitPrice":120,"quantity":4}'

Update a line item

patch/estimates/{estimateId}/line-items/{lineItemId}experimentalWymagana sesja

Updates any subset of a line item's label, unit, unitPrice, quantity and sourceButtonId, and returns the whole updated estimate, not just the changed item. The body is strict, an unknown field is a 400. Responds 403 when the caller is not a member of the estimate's workshop and 404 both when no such estimate exists and when the estimate exists but has no line item with this id - the two cases are indistinguishable in the response.

Parametry ścieżki

NazwaTypWymaganeOgraniczeniaOpis
estimateIdstringTak-Estimate id
lineItemIdstringTak-Line item id

Treść żądania

PoleTypWymaganeOgraniczeniaOpis
labelstringNieminLength: 1-
unitLineItemUnitNie-A line item's unit of measure, one of these seven values.
unitPricenumberNieminimum: 0-
quantitynumberNieexclusiveMinimum: 0-
sourceButtonIdstringNie--
Przykład
{
  "quantity": 3
}

Odpowiedzi

  • 200Success
    PoleTypWymaganeOgraniczeniaOpis
    idstringTak--
    workshopIdstringTak--
    namestringTak--
    recipientNamestringNie-Absent when the estimate has no named recipient
    createdAtstringTakformat: "date-time"-
    updatedAtstringTakformat: "date-time"-
    markupPercentnumberTak--
    discountPercentnumberTak--
    lineItemsarray of LineItemTak--
    Przykład
    {
      "id": "estimate-1",
      "workshopId": "workshop-1",
      "name": "Bespoke chair estimate",
      "recipientName": "Anna Kowalska",
      "createdAt": "2026-08-01T09:00:00.000Z",
      "updatedAt": "2026-08-10T14:30:00.000Z",
      "markupPercent": 10,
      "discountPercent": 0,
      "lineItems": [
        {
          "id": "line-item-1",
          "label": "Oak plank, 2m",
          "unit": "sqm",
          "unitPrice": 120,
          "quantity": 4
        }
      ]
    }
  • 400The request body does not match the schema

    Wspólna treść błędu walidacji, zobacz Błędy walidacji.

  • 401Not authenticated

    Brak treści.

  • 403Forbidden

    Brak treści.

  • 404Not found

    Brak treści.

Przykładowe żądanie

curl -X PATCH 'https://mercastra.cloud/api/estimates/<estimateId>/line-items/<lineItemId>' \
  -H 'Cookie: session=<session cookie>' \
  -H 'X-CSRF-Token: <csrf-token cookie value>' \
  -H 'Content-Type: application/json' \
  -d '{"quantity":3}'

Remove a line item

delete/estimates/{estimateId}/line-items/{lineItemId}experimentalWymagana sesja

Removes one line item and returns the whole updated estimate, not an empty body (unlike deleting the estimate itself). Responds 403 when the caller is not a member of the estimate's workshop and 404 both when no such estimate exists and when the estimate exists but has no line item with this id - the two cases are indistinguishable in the response.

Parametry ścieżki

NazwaTypWymaganeOgraniczeniaOpis
estimateIdstringTak-Estimate id
lineItemIdstringTak-Line item id

Odpowiedzi

  • 200Success
    PoleTypWymaganeOgraniczeniaOpis
    idstringTak--
    workshopIdstringTak--
    namestringTak--
    recipientNamestringNie-Absent when the estimate has no named recipient
    createdAtstringTakformat: "date-time"-
    updatedAtstringTakformat: "date-time"-
    markupPercentnumberTak--
    discountPercentnumberTak--
    lineItemsarray of LineItemTak--
    Przykład
    {
      "id": "estimate-1",
      "workshopId": "workshop-1",
      "name": "Bespoke chair estimate",
      "recipientName": "Anna Kowalska",
      "createdAt": "2026-08-01T09:00:00.000Z",
      "updatedAt": "2026-08-10T14:30:00.000Z",
      "markupPercent": 10,
      "discountPercent": 0,
      "lineItems": [
        {
          "id": "line-item-1",
          "label": "Oak plank, 2m",
          "unit": "sqm",
          "unitPrice": 120,
          "quantity": 4
        }
      ]
    }
  • 401Not authenticated

    Brak treści.

  • 403Forbidden

    Brak treści.

  • 404Not found

    Brak treści.

Przykładowe żądanie

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

List an estimate's revisions

get/estimates/{estimateId}/revisionsexperimentalWymagana sesja

Returns every stored revision, newest revisionNumber first, already trimmed to the workshop's plan cap (see the confirm endpoint). Responds 403 when the caller is not a member of the estimate's workshop and 404 when no such estimate exists.

Parametry ścieżki

NazwaTypWymaganeOgraniczeniaOpis
estimateIdstringTak-Estimate id

Odpowiedzi

  • 200Success
    PoleTypWymaganeOgraniczeniaOpis
    revisionsarray of EstimateRevisionTak--
    Przykład
    {
      "revisions": [
        {
          "id": "revision-1",
          "revisionNumber": 1,
          "createdAt": "2026-08-01T09:00:00.000Z",
          "createdByUid": "uid-1",
          "name": "Bespoke chair estimate",
          "markupPercent": 10,
          "discountPercent": 0,
          "lineItems": [
            {
              "id": "line-item-1",
              "label": "Oak plank, 2m",
              "unit": "sqm",
              "unitPrice": 120,
              "quantity": 4
            }
          ]
        }
      ]
    }
  • 401Not authenticated

    Brak treści.

  • 403Forbidden

    Brak treści.

  • 404Not found

    Brak treści.

Przykładowe żądanie

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

List a workshop's estimates

get/workshops/{workshopId}/estimatesexperimentalWymagana sesja

Returns every estimate belonging to the workshop; the collection has no defined order. Responds 403 when the caller is not a member of the workshop; there is no 404 for an unknown workshop id, a non-member sees 403 either way.

Parametry ścieżki

NazwaTypWymaganeOgraniczeniaOpis
workshopIdstringTak-Workshop id

Odpowiedzi

  • 200Success
    PoleTypWymaganeOgraniczeniaOpis
    estimatesarray of EstimateTak--
    Przykład
    {
      "estimates": [
        {
          "id": "estimate-1",
          "workshopId": "workshop-1",
          "name": "Bespoke chair estimate",
          "recipientName": "Anna Kowalska",
          "createdAt": "2026-08-01T09:00:00.000Z",
          "updatedAt": "2026-08-10T14:30:00.000Z",
          "markupPercent": 10,
          "discountPercent": 0,
          "lineItems": [
            {
              "id": "line-item-1",
              "label": "Oak plank, 2m",
              "unit": "sqm",
              "unitPrice": 120,
              "quantity": 4
            }
          ]
        }
      ]
    }
  • 401Not authenticated

    Brak treści.

  • 403Forbidden

    Brak treści.

Przykładowe żądanie

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

Create an estimate in a workshop

post/workshops/{workshopId}/estimatesexperimentalWymagana sesja

Creates a new estimate. `name` defaults to "Untitled estimate" when omitted or blank after trimming; markupPercent and discountPercent default to 0 and lineItems to none, each submitted line item is assigned a fresh id. The body is not strict, an unknown field is silently dropped rather than rejected. Creating an estimate also writes its first revision (revisionNumber 1) immediately, before any call to confirm. Responds 403 when the caller is not a member of the workshop; there is no 404 for an unknown workshop id, a non-member sees 403 either way.

Parametry ścieżki

NazwaTypWymaganeOgraniczeniaOpis
workshopIdstringTak-Workshop id

Treść żądania

PoleTypWymaganeOgraniczeniaOpis
namestringNie--
recipientNamestringNie--
markupPercentnumberNieminimum: 0-
discountPercentnumberNieminimum: 0maximum: 100-
lineItemsarray of objectNie--
lineItems[].labelstringTakminLength: 1-
lineItems[].unitLineItemUnitTak-A line item's unit of measure, one of these seven values.
lineItems[].unitPricenumberTakminimum: 0-
lineItems[].quantitynumberTakexclusiveMinimum: 0-
lineItems[].sourceButtonIdstringNie--
Przykład
{
  "name": "Bespoke chair estimate",
  "lineItems": [
    {
      "label": "Oak plank, 2m",
      "unit": "sqm",
      "unitPrice": 120,
      "quantity": 4
    }
  ]
}

Odpowiedzi

  • 201Created
    PoleTypWymaganeOgraniczeniaOpis
    idstringTak--
    workshopIdstringTak--
    namestringTak--
    recipientNamestringNie-Absent when the estimate has no named recipient
    createdAtstringTakformat: "date-time"-
    updatedAtstringTakformat: "date-time"-
    markupPercentnumberTak--
    discountPercentnumberTak--
    lineItemsarray of LineItemTak--
    Przykład
    {
      "id": "estimate-1",
      "workshopId": "workshop-1",
      "name": "Bespoke chair estimate",
      "recipientName": "Anna Kowalska",
      "createdAt": "2026-08-01T09:00:00.000Z",
      "updatedAt": "2026-08-10T14:30:00.000Z",
      "markupPercent": 10,
      "discountPercent": 0,
      "lineItems": [
        {
          "id": "line-item-1",
          "label": "Oak plank, 2m",
          "unit": "sqm",
          "unitPrice": 120,
          "quantity": 4
        }
      ]
    }
  • 400The request body does not match the schema

    Wspólna treść błędu walidacji, zobacz Błędy walidacji.

  • 401Not authenticated

    Brak treści.

  • 403Forbidden

    Brak treści.

Przykładowe żądanie

curl -X POST 'https://mercastra.cloud/api/workshops/<workshopId>/estimates' \
  -H 'Cookie: session=<session cookie>' \
  -H 'X-CSRF-Token: <csrf-token cookie value>' \
  -H 'Content-Type: application/json' \
  -d '{"name":"Bespoke chair estimate","lineItems":[{"label":"Oak plank, 2m","unit":"sqm","unitPrice":120,"quantity":4}]}'

List the workshop's most-used line items

get/workshops/{workshopId}/estimates/most-used-line-itemsexperimentalWymagana sesja

Aggregates every line item across the workshop's estimates by label and unit (case-insensitive, trimmed), ranked by how many estimates use them and, as a tiebreaker, by the most recent estimate update. An out-of-range or non-numeric limit responds 400 with an empty body, not a JSON error. Responds 403 when the caller is not a member of the workshop; there is no 404 for an unknown workshop id, a non-member sees 403 either way.

Parametry ścieżki

NazwaTypWymaganeOgraniczeniaOpis
workshopIdstringTak-Workshop id

Parametry zapytania

NazwaTypWymaganeOgraniczeniaOpis
limitintegerNieminimum: 1maximum: 50Defaults to 15

Odpowiedzi

  • 200Success
    PoleTypWymaganeOgraniczeniaOpis
    lineItemsarray of objectTak--
    lineItems[].labelstringTak--
    lineItems[].unitLineItemUnitTak-A line item's unit of measure, one of these seven values.
    lineItems[].unitPricenumberTak--
    lineItems[].countnumberTak-How many of the workshop's estimates use a line item with this label and unit
    lineItems[].lastUsedAtstringTakformat: "date-time"The updatedAt of the most recent estimate that uses it
    Przykład
    {
      "lineItems": [
        {
          "label": "Oak plank, 2m",
          "unit": "sqm",
          "unitPrice": 120,
          "count": 6,
          "lastUsedAt": "2026-08-10T14:30:00.000Z"
        }
      ]
    }
  • 400limit is present but not an integer between 1 and 50

    Brak treści.

  • 401Not authenticated

    Brak treści.

  • 403Forbidden

    Brak treści.

Przykładowe żądanie

curl -X GET 'https://mercastra.cloud/api/workshops/<workshopId>/estimates/most-used-line-items?limit=<limit>' \
  -H 'Cookie: session=<session cookie>'