Create estimates from your server
On this page
Use the REST API or typed SDK when another application needs to calculate and retain an estimate. This walkthrough submits a complete Moving-template answer set, pins the expected published revision and explains retry behavior.
Prepare an estimator and a scoped key
Publish a Moving estimator first. In Settings → API keys, create a key with estimators:read and estimates:write. Add estimates:read if the integration will fetch retained results and leads:read only if it needs contact records.
The full key is shown once. Put it in your server's secret configuration. Do not place it in an embed, client component, mobile browser bundle or public repository. The API uses the installation's canonical origin, not a tenant custom domain.
Set these values in your integration environment:
export OPENQUOTESTACK_BASE_URL=https://quotes.example.com
export OPENQUOTESTACK_API_KEY=your-server-side-keyThe key string above is a placeholder, not an installed credential.
Find the published identity
curl --fail-with-body "$OPENQUOTESTACK_BASE_URL/api/v1/estimators?limit=25" \
-H "Authorization: Bearer $OPENQUOTESTACK_API_KEY"Choose the intended estimator by name and fetch its detail using its returned opaque ID. Read its published definition before constructing answers. Drafts are not exposed by this API.
Lists return data and pagination.nextCursor. Continue with that cursor when non-null; do not assume the first page includes every estimator. IDs are case-sensitive and should not be parsed for business meaning.
Construct a complete request
Save this JSON as quote-request.json, replacing the estimator and published revision IDs from the detail response. The answers match the unmodified Moving template:
{
"estimatorId": "YOUR_ESTIMATOR_ID",
"revisionId": "YOUR_PUBLISHED_REVISION_ID",
"answers": {
"origin": "Boston",
"destination": "Cambridge",
"bedrooms": 3,
"distance": 22,
"elevator": true,
"boxes": 0,
"piano": true,
"packing": false,
"moving_date": "2026-10-10"
}
}If the estimator requires contact before the result, add a contact object with at least name and email. Enabled optional fields include phone, company, address and notes. Disabled capture discards contact.
Do not send a computed total. The server calculates from the selected publication and retains its revision. revisionId requires that revision to be currently published; it does not let the caller choose an arbitrary historical price.
Submit with a persistent request identity
curl --fail-with-body "$OPENQUOTESTACK_BASE_URL/api/v1/estimates" \
-H "Authorization: Bearer $OPENQUOTESTACK_API_KEY" \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: moving-order-20261010-001' \
--data-binary @quote-request.jsonA new estimate returns 201. Repeating the same key and body returns the original estimate with 200 and Idempotency-Replayed: true, even after the estimator is republished. Changing the body with the same key returns 409.
Persist both the key and original serialized request when building a retry system. JSON property order can affect the request hash. Generate a new key for a new business operation, not for each retry of the same operation. Keys are scoped to the API credential; changing credentials changes that scope.
Use the SDK instead of hand-building requests
The packages are prepared for publication but are not presented here as externally published npm releases. Build or locally pack them from the OpenQuoteStack repository, following package setup.
import { OpenQuoteStack } from "@openquotestack/sdk";
import { readFile } from "node:fs/promises";
const oqs = new OpenQuoteStack({
baseUrl: process.env.OPENQUOTESTACK_BASE_URL!,
apiKey: process.env.OPENQUOTESTACK_API_KEY!,
});
const request = JSON.parse(await readFile("quote-request.json", "utf8"));
const quote = await oqs.estimates.create(request, {
idempotencyKey: "moving-order-20261010-001",
});
console.log(quote.id);The SDK supports typed inputs; this file-reading example trusts a request you authored locally. Validate untrusted integration input before using it. The SDK does not automatically retry requests; a missing idempotency key gets a new UUID per call.
Recover by error category
| HTTP status | What to investigate |
|---|---|
| 401 | Missing, revoked or incorrect key |
| 403 | A key without the required scope |
| 404 | Missing resource, wrong organization or wrong origin |
| 409 | Changed publication or conflicting idempotency body |
| 422 | Required fields, answer types or invalid choices |
| 429 | Rate limit; honor Retry-After |
| 500 / transport failure | Retain the request identity and investigate safely |
Use X-Request-Id to correlate failures without recording credentials or full customer payloads. The SDK exposes status, code, request ID and optional retry-after in OpenQuoteStackError; transport failures have status 0.
See the REST reference for limits, representations and contact scopes. API-created estimates are stored business records; they do not create fictitious customer-view analytics.