Документация за разработчици

OpenAPI 3.0 + SDK генерация

Машинно-четима спецификация на всички операции на агента. Генерирайте client SDK за вашия език (C#, TypeScript, Java, Python, …) с openapi-generator или NSwag.

npx @openapitools/openapi-generator-cli generate \
    -i https://napi.bg/api/v1/openapi.json \
    -g csharp \
    -o ./napi-agent-sdk
Стъпка 1 — вземете токен от napi.bg (на вашия сървър)

Преди всяка операция вашият сървър разменя секретния ключ за краткотраен токен. Ключът се взима от Профил → Секретен ключ за НАПИ агент и остава само на сървъра.

1 Изпращате
POST https://napi.bg/api/v1/tokens/issue
Authorization: Bearer napi_test_…
Content-Type: application/json

{ "typeId": "vat", "eik": "123456789" }
2 Получавате
200 OK
{
  "token":     "eyJhbGciOiJSUzI1NiIs…",
  "expiresAt": "2026-04-27T12:34:56Z"
}
3 Ползвате
POST http://127.0.0.1:5123/api/v1/operations/vat
Authorization: Bearer eyJhbGciOiJSUzI1NiIs…
Изпратете токена към агента веднага — валиден е 5 секунди.
Готов код за оторизиране към napi.bg
SECRET="napi_test_XXXXXXXXXXXXXXXXX"

curl --silent --show-error --fail \
  -X POST https://napi.bg/api/v1/tokens/issue \
  -H "Authorization: Bearer $SECRET" \
  -H "Content-Type: application/json" \
  -d '{ "typeId": "vat", "eik": "123456789" }'
// Node 18+ (built-in fetch). Хвани го зад твоя endpoint
// и НИКОГА не давай SECRET на frontend-а.
const SECRET = process.env.NAPI_SECRET;   // "napi_test_…"

export async function issueNapiJwt(typeId, eik) {
  const r = await fetch("https://napi.bg/api/v1/tokens/issue", {
    method:  "POST",
    headers: {
      "Authorization": `Bearer ${SECRET}`,
      "Content-Type":  "application/json"
    },
    body: JSON.stringify({ typeId, eik })
  });
  if (!r.ok) throw new Error(`napi.bg ${r.status}: ${await r.text()}`);
  return r.json();   // { token, jti, expiresAt, issuer }
}
// ASP.NET — регистрирай HttpClient в DI с BaseAddress.
public sealed class NapiTokenClient(HttpClient http, IConfiguration cfg)
{
    private readonly string _secret = cfg["Napi:Secret"]!;  // "napi_test_…"

    public async Task<JsonDocument> IssueAsync(string typeId, string eik)
    {
        using var req = new HttpRequestMessage(HttpMethod.Post,
            "https://napi.bg/api/v1/tokens/issue");
        req.Headers.Authorization = new("Bearer", _secret);
        req.Content = JsonContent.Create(new { typeId, eik });
        using var res = await http.SendAsync(req);
        res.EnsureSuccessStatusCode();
        return JsonDocument.Parse(await res.Content.ReadAsStringAsync());
    }
}
import os, requests

SECRET = os.environ["NAPI_SECRET"]   # "napi_test_…"

def issue_napi_jwt(type_id: str, eik: str) -> dict:
    r = requests.post(
        "https://napi.bg/api/v1/tokens/issue",
        headers={
            "Authorization": f"Bearer {SECRET}",
            "Content-Type":  "application/json",
        },
        json={"typeId": type_id, "eik": eik},
        timeout=10,
    )
    r.raise_for_status()
    return r.json()   # {"token": "...", "jti": "...", "expiresAt": "...", "issuer": "napi.bg"}
<?php
// PHP 8 + curl. $SECRET идва от .env / config — не от потребителя.
$SECRET = getenv('NAPI_SECRET');   // "napi_test_…"

function issue_napi_jwt(string $typeId, string $eik): array {
    global $SECRET;
    $ch = curl_init('https://napi.bg/api/v1/tokens/issue');
    curl_setopt_array($ch, [
        CURLOPT_POST           => true,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_HTTPHEADER     => [
            "Authorization: Bearer $SECRET",
            'Content-Type: application/json',
        ],
        CURLOPT_POSTFIELDS     => json_encode(['typeId' => $typeId, 'eik' => $eik]),
    ]);
    $body   = curl_exec($ch);
    $status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);
    if ($status >= 300) throw new RuntimeException("napi.bg $status: $body");
    return json_decode($body, true);   // ['token' => …, 'jti' => …, 'expiresAt' => …, 'issuer' => …]
}
Правила
  • Нов токен за всяка операция. Токенът е валиден 5 секунди — не го пазете за по-късно.
  • typeId е точно операцията, която ще извикате. Токен за vat не отваря vat_portal_files — агентът го отхвърля с 401. Стойностите са имената на операциите в „API Декларации и справки“ по-долу.
  • Секретният ключ не стига до потребителя. Вашето приложение взима само готовия токен от вашия сървър.
  • С тестов ключ (napi_test_…) агентът връща примерни данни, без обръщения към НАП.
Ако нещо не е наред
КодПричинаКакво да направите
400Липсва eik в заявката.Подайте eik (или eiks за папка).
401Секретният ключ е невалиден, изтекъл или отменен.Издайте нов ключ от профила.
402Изчерпан е лимитът на фирми за ключа — до 3 ЕИК за реален ключ без договор; тестовите ключове нямат лимит.Сключете договор за интегратор.
403ЕИК-ът не е в разрешения списък на ключа. Отговорът съдържа rejectedEiks.Свържете се с администратор на napi.bg — само той променя списъка.
Токен за няколко фирми

Операциите за цяла папка (dec1_6_batch, vat_batch, etz62_batch, etz123_batch) може да съдържат документи на няколко фирми. За тях подайте eiks вместо eik: агентът подава само документите на изброените фирми и отбелязва останалите като skipped.

{ "typeId": "dec1_6_batch", "eiks": ["999000009", "999001133", "999002267"] }
API Декларации и справки (автоматично от openapi.json)

Зареждане на методите… Ако нищо не се появи, /api/v1/openapi.json трябва да е достъпен.

HTTP headers

Всички операции минават през POST /api/v1/operations/<typeId>. Authorization: Bearer <JWT> е задължителен за операции, които изискват ЕИК; останалите headers са опционални.

Header Назначение
X-Napi-Sender-Id Кой подател от настройките на агента да се ползва. Стойността е id от GET /api/senders. Когато липсва, агентът избира сам при един подател и пита потребителя при няколко. Подаването му не отменя потвърждението — диалогът се отваря върху точно този подател, за да потвърди човекът него.
X-Napi-File-Path Абсолютен път до файл/папка за операциите, които приемат local input (напр. dec1_6, vat). Обикновено идва от резултата на /api/pick-file или /api/pick-folder. Без локален файл съдържанието може да се подаде в полетата _inlineFile / _inlineDir на заявката.
X-Napi-Batch-Id Опционален GUID за multi-step бутони. Първата операция със стойност, която агентът не е виждал (или TTL-ът от 10 минути е изтекъл), показва диалог за потвърждение на подателя; всички следващи заявки със същия GUID в рамките на TTL-а се изпълняват мълчаливо. Подава се на всяка заявка от batch-а — генерирай го веднъж в момента на click (crypto.randomUUID()) и преизползвай за цялата последователност. Различен или липсващ GUID винаги пита наново.
Ако потребителят откаже диалога — с бутон или като остави времето да изтече — отказът важи за целия batch: останалите заявки със същия GUID връщат веднага SENDER_NOT_CHOSEN, без да питат повторно.
Кои податели са настроени — GET /api/senders

Подателят е фирмата, от чието име агентът подава — заедно с нейния КЕП сертификат. Настройва се от потребителя в агента, така че интеграторът не го знае предварително. Този адрес го казва:

const r = await fetch("http://127.0.0.1:5123/api/senders");
const { senders, activeSenderId } = await r.json();

// senders: [{ id: "…", displayName: "Моята фирма ЕООД", active: true }, …]

Не иска токен и не връща сертификати, ЕГН или API ключове — само име и id, колкото да нарисувате избор в своя интерфейс. Избраното id се подава като X-Napi-Sender-Id на операцията.

Подаването на X-Napi-Sender-Id спестява избора, но не и потвърждението — агентът пита потребителя преди всяко подаване, защото иначе всяка отворена страница би могла да подписва от негово име. Потвърждението се пропуска само за останалите заявки от същия X-Napi-Batch-Id.
Няколко операции с едно потвърждение

Когато едно действие на потребителя пуска няколко операции една след друга — например подаване и после проверка на резултата, или справки за много фирми — агентът по подразбиране пита за подател при всяка от тях. За да пита само веднъж, изпращайте един и същ идентификатор в заглавката X-Napi-Batch-Id на всички заявки от това действие. Изборът на подател се помни 10 минути; нов идентификатор означава ново потвърждение. Токенът си остава отделен за всяка операция.

X-Napi-Batch-Id: 3f2b8c1e-7a4d-4e1b-9c2a-5d6e7f8a9b0c
Кодове за грешка

Всеки неуспешен отговор носи errorCode — устойчива константа, на която да стъпи вашият код. Текстът в error е за потребителя и може да се промени; константата — не.

Справка, която не е намерила нищо, връща HTTP 200 с празна таблица и NO_DATA в metadata.errorCode. Всяка друга грешка връща HTTP 502 с тяло {"error": "…", "errorCode": "…", "errorKind": "…"} — така прекъсната сесия не изглежда като „няма данни в НАП“.

Всеки код принадлежи на един вид. Ползвайте вида, когато точната причина няма значение: всяка PARAMETER грешка се поправя от извикващия, всяка CREDENTIALS иска действие от потребителя на машината, а PORTAL означава, че НАП е отговорил неочаквано.

Вид Код Значение
Зареждане…

Списъкът се чете от openapi.json (components.schemas.ErrorCode), затова е винаги в синхрон с публикувания агент.

НАПИ е независим софтуерен продукт и не е свързан официално с Националната агенция за приходите. Системата използва публично достъпните API услуги на НАП в съответствие с техническите изисквания.