QannasAPI

Clean an email list in bulk

What you'll build: a submit-and-collect pipeline that verifies a list of addresses as one bulk job, then pulls the graded results — no polling loop of your own to maintain.

SectionServer-side workflows
Actions usedPOST /v1/data/workflows/clean-list:estimatePOST /v1/bulk/jobsGET /v1/bulk/jobs/{job_id}/result
Credits1 per address checked (worst case)
Test-key safeNo — needs a live key
Time to complete~15 min
PrerequisitesA qk_live_ key with a balance, and a list of addresses as an array or CSV text.

Submit the list as one job

Post the action and your addresses to the bulk endpoint as either a queries array or csv_text. You get a job id back immediately rather than holding a connection open for the whole list.

submit.sh
# Exactly one input source: queries[] or csv_text.
curl -X POST https://api.qannasapi.com/v1/bulk/jobs \
  -H "Authorization: Bearer $QANNAS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
        "action": "verify_email",
        "queries": ["jane@acme.example", "sam@acme.example"],
        "webhook_url": "https://yourapp.example/hooks/qannas"
      }'

# < 200  { "status": "OK", "data": { /* job id */ } }

Collect the results

Fetch the job to see its state, then fetch the result once it has finished. Supply a webhook_url on submission and we will call you instead, which is the better shape for anything above a few hundred addresses.

collect.sh
# Poll the job …
curl https://api.qannasapi.com/v1/bulk/jobs/$JOB_ID \
  -H "Authorization: Bearer $QANNAS_API_KEY"

# … then pull the graded results once it has finished.
curl https://api.qannasapi.com/v1/bulk/jobs/$JOB_ID/result \
  -H "Authorization: Bearer $QANNAS_API_KEY"

# Supplying webhook_url on submission skips this loop entirely.

Read the grades, not just the verdicts

Verification returns a grade per address rather than a bare yes or no. Decide your own threshold: mailing an address graded risky is a deliverability choice, not an API one.

Re-verify on a schedule

Address quality decays as people change jobs. Re-running a list quarterly and diffing the grades gives you a decay rate for your own data, which is usually more useful than any single verification pass.

decay.py
# Re-run quarterly and diff the grades. Your own decay rate is
# more useful than any single verification pass.
previous = load_grades("2026-Q2")
current = load_grades("2026-Q3")

decayed = [e for e in previous
           if ok(previous[e]) and not ok(current.get(e))]

print(f"{len(decayed) / len(previous):.1%} decayed this quarter")

Summary

PatternHow
SubmitPOST /v1/bulk/jobs with action and queries or csv_text
PollGET /v1/bulk/jobs/{job_id}
CollectGET /v1/bulk/jobs/{job_id}/result
Skip pollingPass webhook_url on submission
Cost shapePer address resolved, not per address submitted

Production checklist

  • Use a webhook for anything above a few hundred addresses.
  • Store the grade, not a boolean — you will want to re-threshold later.
  • Quote the run before submitting a large list.
  • Re-verify quarterly and track your own decay rate.

Next steps

Run the estimate first. It costs nothing and it is the same arithmetic the meter will apply.