Developer docs
Omni API
High-speed email tunnel and extraction API for bot developers.
How it works
Your bot does not need to manage IMAP connections, passwords, or proxies. Users import their Outlook accounts into their Omni dashboard once.
Omni acts as the high-speed tunnel into those inboxes. When your software needs an OTP code, a verification link, or custom email data:
| Step | Action |
|---|---|
| 1. Arm task | Call POST /api/otp/tasks with the email before (or as) the site sends it. Omni instantly opens a dedicated watcher on that mailbox. |
| 2. Wait | Call GET /api/otp/tasks/{id}?wait=1 to hold until the mail lands. Omni extracts the requested data (in ~200-500ms) and returns it. |
| 3. Continue | Your bot uses the code or link and moves to the next step. For multi-step flows (e.g. OTP code first, then confirmation link), create another task for each step. |
Auth
Base URL: https://omninbox.io. Get your Omni API key from Settings → Omni API (starts with omni-…).
Authorization: Bearer omni-…or x-api-key: omni-…
1. Create a task
POST /api/otp/tasks: Create this before (or right as) you trigger the email.
{
"email": "user@outlook.com",
"since": 1735689600,
"timeout": 900
}| Field | Type | Required | Description |
|---|---|---|---|
email | string | yes | The Outlook address already imported in the user’s Omni dashboard. |
since | number | yes | Unix timestamp in seconds (e.g. Math.floor(Date.now() / 1000)). Only mail received at or after this moment will match. This prevents stale codes from matching. |
timeout | number | no | Max seconds to wait before timing out. Default 900 (15 min), max 3600. |
match | object | no | Filters which incoming email to read (e.g. sender or subject substring). See below. |
extract | object | no | Specifies what to extract (codes, links, or custom regex). Defaults to auto-detecting OTP codes. |
clientId | string | no | Your bot/worker identifier (e.g. "bot-worker-4") to query open tasks after a restart. |
Response (HTTP 201):
{ "id": "5224d3d3-4966-4e74-84d9-0e16ad807932", "status": "pending" }If the account is unhealthy (e.g. needs re-authentication or dead proxy), the response returns status: "error" immediately with an explanation so your bot can fail fast without waiting.
2. Wait for data
GET /api/otp/tasks/{id}?wait=1: Holds the HTTP connection until the matching email arrives.
{
"id": "5224d3d3-4966-4e74-84d9-0e16ad807932",
"status": "success",
"code": "877668",
"data": {
"code": "877668"
},
"receivedAt": "2026-09-12T02:05:58.000Z"
}The response fields:
| Field | Description |
|---|---|
status | "success" when matched · "timeout" if no mail arrived · "error" on failure (check error.message). |
code | Primary extracted value. Contains the OTP digits, verification URL, or regex match. |
data | Key-value map containing all named fields configured in your extract object. |
receivedAt | ISO timestamp when the email landed in the mailbox. |
Targeting emails & scraping data
By default, Omni automatically detects 4-8 digit verification codes, Microsoft security codes, and labeled PINs. If you need to filter specific emails or scrape links and custom text, use match and extract:
Filter (match) | Example | Behavior |
|---|---|---|
match.fromIncludes | "ticketmaster" or "noreply@" | Only matches mail whose sender address contains this substring (case-insensitive). |
match.subjectIncludes | "verification" or "welcome" | Only matches mail whose subject line contains this substring (case-insensitive). |
Extractor (extract) | Syntax | Result |
|---|---|---|
| Auto OTP | "otp" or "code" | Extracts verification codes (default when extract is omitted). |
| Activation link | "link" | Extracts the primary clickable URL from the email body. |
| Custom regex | { "type": "regex", "pattern": "…", "formatter": "numeric" } | Applies regex to the email text (already case-insensitive — do not add (?i)). Captures group 1 (or entire match). Optional formatter: "numeric" strips non-digits — use for spaced codes like 6 9 5 6 2 7 → 695627. |
Examples for bot software
1. Custom Regex
// Spaced auth codes (e.g. Ticketmaster "6 9 5 6 2 7" → 695627)
// Arm the task BEFORE the site sends mail. Pattern is already /i — do not add (?i).
const task = await fetch('https://omninbox.io/api/otp/tasks', {
method: 'POST',
headers: {
Authorization: `Bearer ${API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
email: 'buyer@outlook.com',
since: Math.floor(Date.now() / 1000) - 2,
match: {
subjectIncludes: 'Confirm Your Account',
},
extract: {
code: {
type: 'regex',
pattern: 'Authentication Code:\\s*((?:\\d\\s*){5}\\d)',
formatter: 'numeric', // strip spaces → "695627"
},
},
}),
}).then((r) => r.json());
const done = await fetch(`https://omninbox.io/api/otp/tasks/${task.id}?wait=1`, {
headers: { Authorization: `Bearer ${API_KEY}` },
}).then((r) => r.json());
if (done.status === 'success') {
console.log('code:', done.code); // 695627
}2. Complete Universal Handler
const BASE = 'https://omninbox.io';
const API_KEY = process.env.OMNI_API_KEY;
/**
* Universal helper for bot software.
* Works for OTP codes, signup verification links, and custom regex extraction.
*/
async function waitForEmailData({ email, match, extract, timeout = 900 }) {
const headers = {
Authorization: `Bearer ${API_KEY}`,
'Content-Type': 'application/json',
};
// 1. Create task BEFORE triggering the email
const createRes = await fetch(`${BASE}/api/otp/tasks`, {
method: 'POST',
headers,
body: JSON.stringify({
email,
since: Math.floor(Date.now() / 1000) - 2, // slight skew buffer; ignore older mail
timeout,
...(match ? { match } : {}),
...(extract ? { extract } : {}),
}),
});
const task = await createRes.json();
if (!createRes.ok || task.status === 'error') {
throw new Error(task.error?.message || task.message || `Create failed (${createRes.status})`);
}
// 2. Hold connection until email arrives and data is extracted
const waitRes = await fetch(`${BASE}/api/otp/tasks/${task.id}?wait=1`, {
headers,
});
const done = await waitRes.json();
if (done.status !== 'success') {
throw new Error(done.error?.message || `Task ended with status: ${done.status}`);
}
// done.code has primary extracted string; done.data has all named fields
return {
code: done.code,
data: done.data,
receivedAt: done.receivedAt,
};
}3. Primary Links
// Signup flow: bot waits for activation link to finish registration
const task = await fetch('https://omninbox.io/api/otp/tasks', {
method: 'POST',
headers: {
Authorization: `Bearer ${API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
email: 'new-account@outlook.com',
since: Math.floor(Date.now() / 1000),
match: {
fromIncludes: 'noreply@',
subjectIncludes: 'confirm',
},
extract: {
verifyLink: 'link', // Extracts the primary activation / magic URL
},
}),
}).then((r) => r.json());
// Long-poll until email lands
const done = await fetch(`https://omninbox.io/api/otp/tasks/${task.id}?wait=1`, {
headers: { Authorization: `Bearer ${API_KEY}` },
}).then((r) => r.json());
if (done.status === 'success') {
const url = done.data.verifyLink;
console.log('Activation URL:', url);
// Bot proceeds to navigate to url
}Errors
HTTP failures often return error as a string code plus message. When a task finishes with status: "error", error is an object:{ "code": "…", "message": "…" }. In Go, do not declare Error string — use the APIError type from the Universal Handler example (accepts both shapes).
| Code | Reason | Fix |
|---|---|---|
| 400 | account_not_found | This Outlook address has not been imported into the user’s Omni dashboard yet. The user must add the account to Omni before the bot can read it. |
| 400 | email_required / since_required | Make sure both email and a unix timestamp since are provided. |
| 401 | unauthorized | Missing or invalid Omni API key in the Authorization header. |
| 429 | too_many_requests / too_many_open_tasks | Rate limit reached (15,000 req/min) or more than 6,000 open tasks concurrently on this key. |
Concurrency & waves
Omni spins up a dedicated watcher for every unique mailbox being polled with wait=1. Multiple tasks on the same email share one efficient watcher.
When running high-volume bot fleets (hundreds or thousands of accounts), organize your runs into waves of hundreds of concurrent tasks rather than holding tens of thousands of connections indefinitely. Always use timeout: 900 (or higher) so the tail of a wave does not expire before mail arrives.
To inspect tasks without waiting: GET /api/otp/tasks/{id}
To recover pending tasks after a bot crash: GET /api/otp/tasks?clientId=my-bot&status=pending
