Estimate before you call: price any action for free
What you'll build: a two-step pattern that quotes the cost of a call, checks it against a budget, and only then runs it. You will end with a helper you can wrap around any action in the catalog.
| Section | API fundamentals |
|---|---|
| Actions used | POST /v1/data/{action}:estimatePOST /v1/data/contacts/find-email |
| Credits | Free — these endpoints never bill |
| Test-key safe | Yes — runs on qk_test_ for zero credits |
| Time to complete | ~5 min |
| Prerequisites | A workspace, a qk_test_ or qk_live_ key, and curl or any HTTP client. |
The estimate endpoint
Every priced action has a matching estimate. Take the action path, append :estimate, and send the same body you were going to send anyway. You get back the credit cost of that exact call and you are charged nothing for asking.
# Append :estimate to any action path. Costs nothing.
curl -X POST https://api.qannasapi.com/v1/data/contacts/find-email:estimate \
-H "Authorization: Bearer $QANNAS_API_KEY" \
-H "Content-Type: application/json" \
-d '{"query": "Jane Doe, Acme Logistics"}'Read the quote
The response uses the standard envelope — status, request_id, data — with the quote inside data. Because estimating is free, the X-Credits-Charged header on an estimate is always 0.
# < HTTP/1.1 200 OK
# < X-Credits-Charged: 0 # asking is always free
{
"status": "OK",
"request_id": "req_...",
"data": { /* the quote for this exact call */ }
}Quote, check, then run
The useful shape is a gate: estimate, compare against whatever budget you hold, and run only if it clears. Both calls take the same body, so the gate costs you one extra request and no credits.
import os, requests
API = "https://api.qannasapi.com"
HEADERS = {"Authorization": f"Bearer {os.environ['QANNAS_API_KEY']}"}
def quote_then_call(action, body, budget):
# 1 — free quote, same body as the real call
quote = requests.post(
f"{API}/v1/data/{action}:estimate", json=body, headers=HEADERS
).json()
# 2 — gate on your own budget before spending anything
if cost_of(quote) > budget:
raise RuntimeError("quote exceeds budget")
# 3 — run it
return requests.post(
f"{API}/v1/data/{action}", json=body, headers=HEADERS
)Workflows quote the same way
Server-side workflows use dry_run instead of a :estimate suffix — send dry_run: true and you get the per-step breakdown without running anything. Same idea, same zero cost.
# Workflows use dry_run instead of a :estimate suffix.
curl -X POST https://api.qannasapi.com/v1/data/workflows/lead-list \
-H "Authorization: Bearer $QANNAS_API_KEY" \
-H "Content-Type: application/json" \
-d '{"inputs": {"industry": "logistics"}, "limit": 25, "dry_run": true}'
# < X-Credits-Charged: 0 # a dry run never billsSummary
| Pattern | How |
|---|---|
| Quote an action | POST /v1/data/{action}:estimate with the body you intend to send |
| Quote a workflow | POST /v1/data/workflows/{name} with dry_run: true |
| Cost of estimating | Zero — X-Credits-Charged is always 0 on an estimate |
| Envelope | { status, request_id, data } — the same on estimates and real calls |
| What the number means | Worst case, assuming every step finds something |
Production checklist
- Gate expensive or user-triggered calls behind an estimate.
- Budget against the estimate, then reconcile against X-Credits-Charged.
- Quotes keep working at a zero balance — estimating is always free.
- Send the estimate the same body as the real call, or the quote will not match.
Next steps
Run the estimate first. It costs nothing and it is the same arithmetic the meter will apply.