Документация за разработчици
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 (на вашия сървър)
Преди всяка операция вашият сървър разменя секретния ключ за краткотраен токен. Ключът се взима от Профил → Секретен ключ за НАПИ агент и остава само на сървъра.
POST https://napi.bg/api/v1/tokens/issue
Authorization: Bearer napi_test_…
Content-Type: application/json
{ "typeId": "vat", "eik": "123456789" }
200 OK
{
"token": "eyJhbGciOiJSUzI1NiIs…",
"expiresAt": "2026-04-27T12:34:56Z"
}
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 е за потребителя и може да се промени;
константата — не.
NO_DATA в metadata.errorCode.
Всяка друга грешка връща HTTP 502 с тяло
{"error": "…", "errorCode": "…", "errorKind": "…"} —
така прекъсната сесия не изглежда като „няма данни в НАП“.
Всеки код принадлежи на един вид. Ползвайте вида,
когато точната причина няма значение: всяка
PARAMETER грешка се поправя от извикващия, всяка
CREDENTIALS иска действие от потребителя на машината,
а PORTAL означава, че НАП е отговорил неочаквано.
| Вид | Код | Значение |
|---|---|---|
| Зареждане… | ||
Списъкът се чете от
openapi.json
(components.schemas.ErrorCode), затова е винаги в
синхрон с публикувания агент.