fake.bg
REST · SSE · v1

API документация

Анонимните заявки са безплатни и не изискват ключ; ограничени са по IP адрес, за да се предпазим от злоупотреби. API ключовете (план Developer) отключват програмното създаване на кутии, уебкуките и отделни лимити за всеки ключ. Основен адрес:

https://fake.bg

В примерите <domain> е един от домейните, които връща GET /api/v1/domains/. Активни домейни в момента: зареждане…

POST/api/v1/inboxes/generate

Създаване на кутия (анонимно, безплатно)

Тяло на заявката
{
  "domain": "<domain>",      // optional; first served domain if omitted
  "local_part": "myalias"    // optional; random if omitted
}
Отговор
{
  "id": "0416d8e4-...",
  "address": "myalias@<domain>",
  "message_count": 0,
  "unread_count": 0,
  "inbox_expires_at": "2026-10-17T11:30:00Z"
}
POST/api/v1/inboxes

Програмно създаване на кутия (Developer — изисква вход)

Тяло на заявката
{
  "domain": "<domain>",
  "local_part": "checkout-tests",     // optional
  "label": "CI checkout regression",
  "ttl_seconds": 86400,                // null = persistent (paid)
  "webhook_url": "https://your.app/email-hook"
}
Отговор
{
  "id": "...",
  "address": "checkout-tests@<domain>",
  "label": "CI checkout regression",
  "webhook_url": "https://your.app/email-hook",
  "webhook_secret": "<48 hex chars — shown ONCE, save it>"
}
Изисква Authorization: Bearer <jwt> ИЛИ X-API-Key. Съобщенията на уебкуките се подписват с HMAC-SHA256 с върнатия тук секрет (заглавка X-VaultMail-Signature).
GET/api/v1/domains/

Списък с домейни

Отговор
{
  "domains": [
    { "name": "<domain>", "is_public": true, "tier_required": 0 },
    ...
  ]
}
GET/api/v1/inboxes/{address}

Кутия и статистика

Отговор
{ "id": "...", "address": "...", "message_count": 4, "unread_count": 1, ... }
Използвайте /api/v1/inboxes/{address}/stats за обобщените броячи (непрочетени, кодове, общ размер, прикачени файлове).
GET/api/v1/inboxes/{address}/messages?limit=50

Списък с писма

Отговор
[
  {
    "id": "ecbfa70e-...",
    "from_address": "noreply@example.com",
    "subject": "Confirm your account (code 482193)",
    "received_at": "...",
    "is_read": false,
    "has_otp": true,
    "raw_size": 533,
    "link_count": 1,
    "attachment_count": 0
  }
]
GET/api/v1/messages/{id}

Четене на писмо

Отговор
{
  "id": "...",
  "from_address": "noreply@example.com",
  "subject": "...",
  "body_text": "...",
  "body_html": "...",
  "links": [{ "index": 0, "url": "https://..." }],
  "attachments": [{ "filename": "...", "size": ... }],
  "otp_detected": "482193",
  "headers": { "From": "...", "DKIM-Signature": "..." }
}
Заявката отбелязва писмото като прочетено.
GET/api/v1/messages/{id}/raw

Сваляне на суров .eml

Отговор
(application/rfc822 bytes; Content-Disposition: attachment)
GET/api/v1/messages/{id}/otp

Извличане на код

Отговор
{
  "message_id": "...",
  "otp": "482193",
  "found": true,
  "subject": "Confirm your account (code 482193)"
}
Ако писмото не е маркирано при доставката, разпознаването се изпълнява отново. Връща found=false, ако няма съвпадение.
GET/api/v1/messages/{id}/links

Списък с линкове

Отговор
{
  "message_id": "...",
  "count": 2,
  "links": [
    { "index": 0, "url": "https://example.com/verify?token=abc" },
    { "index": 1, "url": "https://example.com/help" }
  ]
}
POST/api/v1/api-keys

Създаване на API ключ

Тяло на заявката
{ "label": "CI", "expires_in_days": 90 }
Отговор
{
  "id": "...",
  "key_prefix": "abc123def456",
  "label": "CI",
  "plaintext": "vlt_abc123def456_<48 hex secret>"
}
plaintext се показва САМО веднъж. Използвайте го като заглавка X-API-Key. Изисква план 3 (Developer) или по-висок.
GET/api/v1/api-keys

Моите API ключове

Отговор
[
  { "id": "...", "key_prefix": "abc...", "label": "CI",
    "is_active": true, "last_used_at": "...", "created_at": "..." }
]
DELETE/api/v1/api-keys/{id}

Анулиране на API ключ

Отговор
204 No Content
GET/api/v1/sse/inbox/{address}

Поток в реално време (SSE)

Отговор
event: connected
data: {"address":"..."}

event: message
data: {"type":"new_message","id":"...","from":"...","subject":"...","has_otp":true}
Дълготрайна връзка. Сървърът изпраща „connected“ при абониране, „message“ при всяко доставено писмо (type: new_message) и при автоматично изтрити писма (type: messages_deleted, със списък ids), както и периодични „ping“ за поддържане на връзката. В браузъра използвайте EventSource, другаде — HTTP клиент с поточно четене.
POST/api/v1/inboxes/{address}/mark-all-read

Всички като прочетени

Отговор
204 No Content
POST/api/v1/inboxes/{address}/clear

Изчистване на кутията

Отговор
204 No Content
Изтрива всички писма. Адресът остава активен.
DELETE/api/v1/messages/{id}

Изтриване на писмо

Отговор
204 No Content

Услугата за временни телефони все още работи в демо режим — ендпойнтите отговарят, но не се купуват истински номера.

GET/api/v1/phones/services?q=goog

Временен телефон — услуги и цени

Отговор
[
  {
    "service_name": "google",
    "display_name": "Google",
    "capability": "sms",
    "number_type": "mobile",
    "price_usd": "0.85",
    "is_test": false
  }
]
Отговаря от нашия кеширан каталог. Показват се само услуги с цена, които са включени.
POST/api/v1/phones/verifications

Временен телефон — наемане на номер

Тяло на заявката
{
  "service_name": "google",
  "label": "signup test",          // optional
  "area_codes": ["205"]            // optional preference
}
Отговор
{
  "id": "9f1c...",
  "number": "+15551230000",
  "service_name": "google",
  "status": "pending",
  "price_usd": "0.85",
  "otp_code": null,
  "ends_at": "2026-10-17T12:20:00Z"
}
Таксува портфейла ви с посочената цена. Номерата са еднократни и изтичат сами. Ако не пристигне SMS — отменили сте или времето е изтекло — цената се възстановява автоматично. 402 означава недостатъчен кредит (нищо не е купено).
GET/api/v1/phones/verifications/{id}/otp?wait=180

Временен телефон — изчакване на кода

Отговор
{
  "verification_id": "9f1c...",
  "number": "+15551230000",
  "code": "482913",
  "status": "completed",
  "waited_seconds": 21.4
}
Изчаква (long-poll), докато пристигне SMS или изтекат `wait` секунди (ограничено от сървъра). При изтичане връща code:null — извикайте отново, за да продължите да чакате.
POST/api/v1/phones/verifications/{id}/cancel

Временен телефон — освобождаване и възстановяване

Отговор
{
  "id": "9f1c...",
  "status": "canceled",
  "price_usd": "0.85",
  "refunded_usd": "0.85"
}
Пълно възстановяване, ако не е получен SMS. След като е пристигнал код, продажбата е окончателна и refunded_usd остава 0.00.
GET/api/v1/phones/wallet

Временен телефон — наличен кредит

Отговор
{
  "balance_usd": "12.60",
  "lifetime_topup_usd": "20.00",
  "lifetime_spend_usd": "7.40",
  "currency": "USD",
  "entries": [
    {"kind": "purchase", "amount_usd": "-0.85", "balance_after_usd": "12.60", ...}
  ]
}
GET/api/v1/sse/phone/{verification_id}

Временен телефон — SMS в реално време (SSE)

Отговор
event: connected
data: {"verification_id":"9f1c...","number":"+15551230000"}

event: sms
data: {"type":"sms","from":"Google","content":"G-482913 is your code","code":"482913"}
Само за собственика. Същият модел на връзка като потока на кутията.

Пример от край до край

# 1. Създайте кутия
ADDR=$(curl -s -X POST https://fake.bg/api/v1/inboxes/generate \
  -H 'Content-Type: application/json' -d '{}' | jq -r .address)
echo "Inbox: $ADDR"

# 2. Следете за писма в друг терминал
curl -N https://fake.bg/api/v1/sse/inbox/$ADDR

# 3. Изпратете тестово писмо до адреса (от който и да е доставчик)

# 4. Списък с писмата
curl https://fake.bg/api/v1/inboxes/$ADDR/messages

# 5. Прочетете кода
curl https://fake.bg/api/v1/messages/<id>/otp | jq

Доставка чрез уебкуки

Когато пристигне писмо в кутия със зададен webhook_url, изпращаме JSON с POST към този адрес. До 4 опита (първоначален + 3 повторни) с изчакване 5 s → 25 s → 125 s. Проверявайте X-VaultMail-Signature, преди да се доверите на съдържанието.

Заглавки

X-VaultMail-Signature: sha256=<hex hmac of raw body, key = webhook_secret>
X-VaultMail-Event: message.received
X-VaultMail-Attempt: 1
Content-Type: application/json

Проверка (Python)

import hmac, hashlib

def verify(secret: str, raw_body: bytes, signature_header: str) -> bool:
    expected = "sha256=" + hmac.new(
        secret.encode(), raw_body, hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(expected, signature_header)

Проверка (Node)

import { createHmac, timingSafeEqual } from "node:crypto";

export function verify(secret: string, rawBody: Buffer, header: string) {
  const expected = "sha256=" + createHmac("sha256", secret)
    .update(rawBody).digest("hex");
  return timingSafeEqual(Buffer.from(expected), Buffer.from(header));
}