uCheckeruChecker

uChecker API documentation

uChecker checks whether a mailbox actually exists, working down through DNS/MX records, an SMTP connection and provider-specific behaviour. The answer comes back as a decision, not a probability — the address either accepts mail or it does not.

Every request goes to https://api.uchecker.net. Send one address at a time, or millions in a single task.

Quick start

  1. 1. Get your API key

    The key is waiting in your dashboard the moment you register — nothing to request or wait for.

  2. 2. Send an address for validation

    curl -X POST https://api.uchecker.net/api/v1/validate/single \
      -H "x-api-key: uk_YOUR_KEY" \
      -H "Content-Type: application/json" \
      -d '{"email": "user@example.com"}'
  3. 3. Fetch the result using task_id from the response

    curl https://api.uchecker.net/api/v1/tasks/123/results \
      -H "x-api-key: uk_YOUR_KEY"

Authentication

Two methods, equal in power: both unlock every endpoint. Which one you want depends on where the request comes from.

API key

Pass the key in the x-api-key header. It never expires and stays valid until you reset it by hand, which makes it the sane choice for server-side integrations — nothing to refresh, nothing to schedule.

x-api-key: uk_xxxxxxxxxxxxx

Bearer token

Call POST /auth/login for a JWT and pass it in the Authorization header. Use this from front-end code, where shipping a permanent key to the browser is not an option.

Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
TokenLifetimePurpose
access_token1 hourAuthenticates requests
refresh_token7 daysRenews access_token via POST /auth/refresh

Your key is visible in the dashboard, and that is also where you reset it if it leaks. The old key stops working immediately. app.uchecker.net

Credits and limits

Billing runs on credits: verifying one address costs 1 credit. The credit is taken when the address enters the queue, not when the SMTP server answers.

  • There are no rate limits — send requests as fast as you need to.
  • Addresses that fail the syntax check in a batch are never charged: they are dropped before the queue and returned in invalid_details.
  • GET /api/v1/account/balance returns what is left.

Reading the result

Every address comes back with one of two values in validation_result. There is deliberately no third option — handing back a maybe would just move the decision to you.

ValueWhat it means
goodThe mailbox exists and accepts mail. Safe to keep on your list.
badThe mailbox does not exist, is disabled, or the domain refuses mail. The result field carries the reason: mailbox_not_found, domain_not_found, smtp_rejected and others.

Task lifecycle

Every validation request creates a task. Results only become available once it reaches completed.

pending → processing → completed
                    ↘ failed
StateWhat is happening
pendingTask created, waiting for the queue. Code 0.
processingAddresses are being checked; progress_percent tracks it. Code 1.
completedEvery address is checked and results are ready. Code 3.
failedThe task broke — send us the task_id and we will look. Code −1.

Polling every 5–10 seconds is plenty. Better still, pass webhook_url when you create the task and we will POST the results to you the moment it finishes, so there is nothing to poll.

Errors

Standard HTTP codes. Error bodies always carry success: false and a readable error field.

CodeWhen you see it
400Invalid parameters or a malformed request body.
401The key or token is missing, expired or wrong.
403Not enough credits on the balance.
404No such task, or it belongs to a different account.
500Our side broke — retry the request.

Endpoint reference

Every public method with its parameters and examples. Paths are shown without the base URL.

https://api.uchecker.net

Validation and tasks

Queue addresses, follow progress, collect results.

Verify a single address

POST/api/v1/validate/singleKey required

The address is syntax-checked, queued, then verified through DNS/MX, SMTP and provider-specific methods.

If the format is plainly wrong the response comes back instantly with status: "invalid" and no credit is spent. Otherwise 1 credit is taken and you get a task_id. Verification takes a few seconds to two minutes — domains with aggressive anti-spam policies are the slow ones.

Parameters

  • emailstring· body· required

    The address to verify, in RFC 5322 format.

  • webhook_urlstring· body· optional

    Where to POST the result once verification finishes. Must be reachable from outside and answer 200.

  • client_typestring· body· optional

    web | api

    Tags the source of the request. Only affects the format of internal notifications.

Request

{
  "email": "user@example.com",
  "webhook_url": "https://your-site.com/webhook/validation-complete"
}

Response 200

{
  "success": true,
  "task_id": 123,
  "email": "user@example.com",
  "status": "queued",
  "credits_used": 1,
  "credits_remaining": 999,
  "estimated_completion": "2024-01-01T12:00:30.000Z"
}

Verify a list of addresses

POST/api/v1/validate/bulkKey required

This is how you work with real lists. Every address is syntax-checked before queueing: malformed ones are dropped, never charged, and returned in invalid_details with a reason. You pay only for what actually got verified.

Pass an idempotency_key when a request might be retried after a network timeout — calling again with the same key returns the existing task instead of creating a second identical one.

Parameters

  • emailsstring[]· body· required

    Array of addresses. Malformed ones are excluded.

  • webhook_urlstring· body· optional

    Where to POST all results once the task finishes.

  • idempotency_keystring· body· optional

    Any unique string — an import id works well. Protects against duplicate tasks on retries.

  • client_typestring· body· optional

    web | api

    Tags the source of the request.

Request

{
  "emails": ["user1@example.com", "user2@example.com", "info@company.ru"],
  "idempotency_key": "import-2024-01-15-batch-3"
}

Response 200

{
  "success": true,
  "task_id": 124,
  "status": "queued",
  "total_emails": 100,
  "valid_emails": 95,
  "invalid_emails": 5,
  "invalid_details": [
    { "email": "bad-email", "reason": "Invalid email syntax" }
  ],
  "credits_used": 95,
  "credits_remaining": 904
}

Task status and progress

GET/api/v1/tasks/{taskId}Key required

Tells you where the task is and how many addresses are done. Results are only served for tasks in completed, so this is the endpoint you poll between queueing and downloading.

A task is only visible to the account that created it.

Parameters

  • taskIdnumber· path· required

    The task id returned by the validation request.

Response 200

{
  "success": true,
  "task_id": 123,
  "status": "processing",
  "total_emails": 100,
  "processed_emails": 45,
  "progress_percent": 45,
  "created_at": "2024-01-01T12:00:00.000Z",
  "finished_at": null
}

Validation results

GET/api/v1/tasks/{taskId}/resultsKey required

Returns the verdict for every address in the task. Addresses marked bad also carry a result field explaining why.

Parameters

  • taskIdnumber· path· required

    The task id.

  • formatstring· query· optional· default: json

    json | csv

    json returns a structure, csv returns the same data as a string with headers.

Response 200

{
  "success": true,
  "format": "json",
  "data": [
    { "email": "user1@example.com", "validation_result": "good" },
    {
      "email": "user2@example.com",
      "validation_result": "bad",
      "result": "mailbox_not_found"
    }
  ]
}

Download results as CSV

GET/api/v1/tasks/{taskId}/results/csvKey required

Same data as format=csv above, but delivered as a file with a Content-Disposition: attachment header. Handy when the export goes straight to a user's browser.

Parameters

  • taskIdnumber· path· required

    The task id.

Response 200

Content-Type: text/csv
Content-Disposition: attachment; filename="results_123.csv"

email,validation_result,result
user1@example.com,good,
user2@example.com,bad,mailbox_not_found

Download good and bad as lists

GET/api/v1/tasks/{taskId}/downloadKey required

A ZIP holding good.txt and bad.txt, one address per line. Almost every ESP imports this format directly, so there is no CSV to parse first.

Parameters

  • taskIdnumber· path· required

    The task id.

Response 200

Content-Type: application/zip
Content-Disposition: attachment; filename="task_123.zip"

task_123.zip
├── good.txt
└── bad.txt

Task analytics

GET/api/v1/tasks/{taskId}/analyticsKey required

A summary of a finished task: how many addresses are live, how many were filtered out, the resulting deliverability rate, and why addresses failed.

The reasons breakdown is the useful part — it tells you what is actually wrong with the list. A wall of domain_not_found points at typos during collection, while smtp_reject usually means the list has simply aged.

Parameters

  • taskIdnumber· path· required

    The task id.

Response 200

{
  "total": 100,
  "good": 80,
  "bad": 15,
  "unknown": 5,
  "deliverability": 80,
  "reasons": [
    { "key": "smtp_reject", "count": 12 },
    { "key": "domain_not_found", "count": 3 }
  ]
}

List tasks

GET/api/v1/tasksKey required

Paginated history of the account's tasks, newest first.

Parameters

  • pagenumber· query· optional· default: 1

    Page number, starting at 1.

  • limitnumber· query· optional· default: 10

    Tasks per page: 1 to 100.

Response 200

{
  "success": true,
  "tasks": [
    {
      "task_id": 123,
      "fileName": "bulk_100_emails",
      "status": "completed",
      "created_at": "2024-01-01T12:00:00.000Z",
      "finished_at": "2024-01-01T12:01:35.000Z"
    }
  ],
  "total": 50,
  "page": 1,
  "limit": 10
}

Account and billing

Credit balance, lifetime statistics and payment history.

Account balance

GET/api/v1/account/balanceKey required

Remaining credits, account id and a masked key. Worth calling before a large batch so you do not hit a 403 halfway through.

Parameters

No parameters.

Response 200

{
  "success": true,
  "account_id": 123456,
  "credits_remaining": 1000,
  "api_key": "uk_xxxxx..."
}

Account statistics

GET/api/v1/account/statsKey required

Tasks created, addresses verified all-time, and average deliverability. last_list breaks down the most recent task.

Parameters

No parameters.

Response 200

{
  "tasks_count": 42,
  "emails_checked": 12500,
  "avg_deliverability": 78,
  "last_list": { "total": 100, "good": 80, "bad": 15, "unknown": 5 }
}

Payment history

GET/api/v1/billing/historyKey required

Paginated transactions, newest first: amount, status, what was bought and the payment id.

Parameters

  • pagenumber· query· optional· default: 1

    Page number, starting at 1.

  • limitnumber· query· optional· default: 10

    Records per page.

Response 200

{
  "data": [
    {
      "id": 1,
      "amount": 1000,
      "status": "completed",
      "product_details": "5000 addresses (5k) via freekassa from web",
      "creation_date": "2024-01-01T12:00:00.000Z",
      "payment_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
    }
  ],
  "pagination": { "page": 1, "limit": 10, "total": 5, "totalPages": 1 }
}

ESP providers

For partners who create accounts and resell credits to their own customers. Authenticated with a separate token rather than an account key.

Calculate credit pricing

GET/api/v1/esp/priceESP token

Returns the price for a given volume under the partner's current rate card. No need to compute it yourself — the per-address price depends on volume.

Parameters

  • x-esp-tokenstring· header· required

    Partner token, issued when you are onboarded.

  • countnumber· query· required

    Number of credits, minimum 1.

Response 200

{
  "success": true,
  "credits": 10000,
  "price": 2000,
  "price_per_email": 0.2,
  "currency": "RUB"
}

Create an account or top up credits

POST/api/v1/esp/provisionESP token

One method for both cases: if no account exists for that address it is created and issued a key, and if one does exist the credits are simply added. The is_new_account flag tells you which happened.

Send an external_id — your own order number. We use it to reject a repeated payment and return is_duplicate: true instead of crediting twice.

Parameters

  • x-esp-tokenstring· header· required

    Partner token, issued when you are onboarded.

  • emailstring· body· required

    Address of the new or existing account.

  • creditsnumber· body· required

    How many credits to add.

  • external_idstring· body· optional

    Order identifier on the partner's side. Guards against double crediting.

Request

{
  "email": "user@example.com",
  "credits": 10000,
  "external_id": "order_12345"
}

Response 200

{
  "success": true,
  "account_id": 123,
  "email": "user@example.com",
  "api_key": "uk_xxxxxxxxxxxxx",
  "credits_added": 10000,
  "total_credits": 10000,
  "is_new_account": true,
  "is_duplicate": false
}

Try it live

This page is built for reading and searching. When you want to fire a real request from the browser and look at a real response, open the interactive sandbox — it takes your key and gives every endpoint a send button.

Open the API sandbox

Frequently asked questions

What does it cost to verify one address through the API?

One verification costs 1 credit, taken when the address enters the queue. Addresses that fail the syntax check in a batch are not charged at all — they are dropped beforehand and returned in invalid_details.

Are there rate limits?

No. Nothing throttles how fast you send. The only ceiling is your credit balance, and hitting it returns a 403.

How many addresses fit in one request?

The bulk endpoint accepts millions of addresses in a single task, so there is no need to slice a list up just to get around limits.

How do I know a task finished without polling?

Pass webhook_url when you create the task and we POST the results to that URL once every address is checked. If webhooks do not fit, poll GET /api/v1/tasks/:taskId every 5–10 seconds.

API key or Bearer token — what is the difference?

They grant the same access; the difference is lifetime. The x-api-key header never expires, which suits server-side integrations. A JWT lasts an hour and is renewed with a refresh token, which is what you want when a permanent key must not reach the browser.

What should I do about a task that failed?

Email support@uchecker.net with the task_id — it shows exactly where the task broke. Credits for work that never ran are returned.

Can I test the API before paying?

Yes. Registering puts free credits on the balance, and every endpoint can be called straight from the browser in the interactive sandbox.

Support

Integration questions go to support@uchecker.net. If a task ends in failed, include the task_id — it points straight at what went wrong.