# Faktura1 Custom API v1

Тази документация е предназначена за клиенти и разработчици, които искат сами да свържат собствен сайт, ERP, CRM, онлайн магазин или друга система с Faktura1.

API-то позволява:

- създаване на фактура и проформа;
- създаване на кредитно известие към издадена фактура;
- проверка на създаден документ;
- намиране на документ по номер на поръчка от външната система;
- промяна на статус на плащане;
- изтриване на документ, когато бизнес правилата го позволяват;
- използване на фирмени номенклатури и custom mappings;
- получаване на PDF линкове за основен и втори език;
- изпращане на документа по имейл.

## Адрес на API

Production base URL:

```text
https://api.faktura1.com/api/v1
```

Всички endpoint-и в документацията са относителни спрямо този адрес.

Например:

```text
GET https://api.faktura1.com/api/v1/meta
POST https://api.faktura1.com/api/v1/documents
```

## Създаване на API достъп

Във Faktura1 се създава интеграция от:

```text
Settings -> API integrations
```

За самостоятелно разработена връзка полето `Source` трябва да бъде:

```text
custom
```

След създаването Faktura1 показва API token. Token-ът се показва само веднъж и трябва да бъде съхранен като парола.

Препоръчителни права:

| Scope | Предназначение |
|---|---|
| `documents:create` | Създаване на фактури, проформи и кредитни известия |
| `documents:read` | Преглед и търсене на документи |
| `documents:update` | Промяна на статус на плащане |
| `documents:delete` | Изтриване на документ |
| `mappings:read` | Преглед на custom mappings |
| `mappings:write` | Създаване и редактиране на custom mappings |

Не давайте `documents:delete` и `mappings:write`, ако интеграцията няма нужда от тях.

## Authentication и HTTP headers

Всеки request трябва да съдържа:

```http
Authorization: Bearer YOUR_API_TOKEN
Accept: application/json
Content-Type: application/json
X-Faktura-Integration: custom
```

При създаване на документ трябва да се изпрати и стабилен idempotency key:

```http
Idempotency-Key: custom:invoice:order-10001
```

Пълен пример:

```bash
curl "https://api.faktura1.com/api/v1/meta?lang_id=2" \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Accept: application/json" \
  -H "X-Faktura-Integration: custom"
```

## Препоръчителен процес

1. Създайте `Custom` API integration и запазете token-а.
2. Извикайте `GET /meta?lang_id=2`.
3. Покажете върнатите номенклатури като падащи менюта в настройките на вашата система.
4. Запазете избраните `internal_id` стойности.
5. Изпратете `POST /documents`.
6. Запазете `invoice_id`, `invoice_number`, `date_from`, `pdf_url` и `external_order_id`.
7. При повторение на request използвайте същия `Idempotency-Key`.

## Endpoint-и

| Method | Endpoint | Scope | Резултат |
|---|---|---|---|
| `GET` | `/meta?lang_id=2` | валиден token | Фирмени номенклатури и настройки |
| `GET` | `/documents` | `documents:read` | Връща всички активни документи на фирмата |
| `POST` | `/documents` | `documents:create` | Създава фактура или проформа |
| `GET` | `/documents/{invoice_id}` | `documents:read` | Връща документ по Faktura1 ID |
| `GET` | `/documents/by-external/custom/{external_order_id}` | `documents:read` | Намира документ по външна поръчка |
| `POST` | `/documents/{invoice_id}/credit-note` | `documents:create` | Създава кредитно известие |
| `PATCH` | `/documents/{invoice_id}/payment-status` | `documents:update` | Променя статус на плащане |
| `PATCH` | `/documents/by-external/custom/{external_order_id}/payment-status` | `documents:update` | Променя статус по външна поръчка |
| `DELETE` | `/documents/{invoice_id}` | `documents:delete` | Изтрива документа с `hidden=1` |
| `GET` | `/mappings` | `mappings:read` или `mappings:write` | Връща custom mappings |
| `POST` | `/mappings` | `mappings:write` | Добавя или редактира mapping |
| `DELETE` | `/mappings/{mapping_id}` | `mappings:write` | Изключва mapping |

## Номенклатури: `GET /meta`

ID стойностите за кочан, склад, банкова сметка и другите номенклатури са различни за всяка фирма. Не използвайте ID, взето от друга фирма или от пример в документацията.

Request:

```http
GET /meta?lang_id=2
```

`lang_id` определя езика на имената в списъците:

- `1` - English;
- `2` - Български;
- `3` - Română.

Съкратен примерен response:

```json
{
  "status": "success",
  "data": {
    "api": {
      "base_url": "https://api.faktura1.com/api/v1",
      "public_base_url": "https://api.faktura1.com",
      "version": "v1"
    },
    "integration": {
      "source": "custom",
      "fallback_mode": "default_with_warnings",
      "defaults": {
        "kochan_id": 58,
        "warehouse_id": 10,
        "unit_id": 10,
        "currency_id": 33,
        "payment_type_id": 25,
        "payment_status_id": 85,
        "bank_account_id": 17
      },
      "scopes": [
        "documents:create",
        "documents:read"
      ]
    },
    "currencies": [
      {
        "id": 33,
        "code": "EUR",
        "name": "Euro",
        "is_default": 1
      }
    ],
    "units": [
      {
        "id": 10,
        "code": "pcs",
        "name": "бр.",
        "is_default": 1
      }
    ],
    "vat_rates": [
      {
        "id": 0,
        "name": "ДДС 20%",
        "vat_percent": 20,
        "is_default": 1
      }
    ],
    "languages": [
      {
        "id": 1,
        "code": "en",
        "name": "English"
      },
      {
        "id": 2,
        "code": "bg",
        "name": "Български"
      }
    ]
  }
}
```

Response-ът съдържа:

- `invoice_books`;
- `units`;
- `currencies`;
- `payment_methods`;
- `payment_statuses`;
- `bank_accounts`;
- `vat_rates`;
- `warehouses`;
- `languages`;
- `mappings`.

API-то валидира всяко подадено `internal_id` спрямо типа, фирмата и правата на потребителя. Например ID на банкова сметка не може да бъде използвано като склад и банкова сметка от друга фирма не може да бъде подадена с чужд token.

## Два начина за подаване на номенклатура

### Вариант A: `internal_id`

Това е препоръчителният вариант. ID-то се взема от `/meta` и се пази в настройките на интеграцията.

```json
{
  "currency": {
    "internal_id": 33
  },
  "payment_method": {
    "internal_id": 25
  }
}
```

### Вариант B: `external_code` и custom mapping

Подходящ е, когато външната система има собствени кодове, например `cod`, `bank`, `paid` или `piece`.

```json
{
  "payment_method": {
    "external_code": "cod"
  },
  "payment_status": {
    "external_code": "paid"
  }
}
```

Преди използването тези кодове трябва да бъдат свързани с Faktura1 стойности в настройките на Custom API integration или чрез `/mappings`.

Поддържани `map_type` стойности:

```text
invoice_book
unit
currency
payment_method
payment_status
language
vat_rate
warehouse
bank_account
```

## Създаване на фактура

Endpoint:

```http
POST /documents
```

Примерните ID стойности по-долу са само илюстративни. Използвайте ID стойностите от вашия `/meta` response.

```json
{
  "external_order_id": "order-10001",
  "type": "invoice",
  "issue_date": "2026-07-27",
  "tax_date": "2026-07-27",
  "due_date": "2026-08-03",
  "currency": {
    "internal_id": 33
  },
  "currency_en": {
    "internal_id": 33
  },
  "payment_method": {
    "internal_id": 25
  },
  "payment_status": {
    "internal_id": 85
  },
  "invoice_book": {
    "internal_id": 58
  },
  "bank_account": {
    "internal_id": 17
  },
  "warehouse": {
    "internal_id": 10
  },
  "vat_rate": {
    "internal_id": 0
  },
  "lang_id": 2,
  "lang2_id": 1,
  "send_email": true,
  "email_to": "client@example.com",
  "round_numbers": 2,
  "customer": {
    "name": "Примерна фирма ООД",
    "reg_number": "123456789",
    "vat_id": "BG123456789",
    "country_code": "BG",
    "city": "София",
    "postcode": "1000",
    "address": "ул. Пример 1",
    "mol": "Иван Иванов",
    "email": "client@example.com",
    "is_person": false
  },
  "items": [
    {
      "product_id": 1001,
      "name": "Абонаментна услуга",
      "description": "Месечен абонамент",
      "quantity": "1",
      "unit": {
        "internal_id": 10
      },
      "unit_price": "100.00",
      "unit_price_includes_vat": false,
      "discount_percent": "0",
      "vat_rate": {
        "internal_id": 0
      },
      "vat_percent": "20",
      "total": "100.00"
    }
  ],
  "totals": {
    "total_no_vat": "100.00",
    "total_vat": "20.00",
    "total": "120.00"
  },
  "short_comment": "Поръчка order-10001",
  "comment": "Създадена автоматично от клиентска система"
}
```

Пример с cURL:

```bash
curl -X POST "https://api.faktura1.com/api/v1/documents" \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -H "X-Faktura-Integration: custom" \
  -H "Idempotency-Key: custom:invoice:order-10001" \
  --data @invoice.json
```

### Задължителни данни

| Поле | Забележка |
|---|---|
| `external_order_id` | Уникален номер на поръчката във външната система |
| `type` | `invoice` или `proforma` |
| `customer.name` | Име на фирма или физическо лице |
| `customer.reg_number` | Задължително за фирма |
| `customer.is_person` | `false` за фирма, `true` за физическо лице |
| `items` | Поне един ред |
| `items[].name` | Име на продукта/услугата |
| `items[].quantity` | Не може да бъде нула |
| `items[].unit` | Или валидна default unit в integration настройките |
| `currency` | Или валидна default currency |
| `payment_method` | Или валиден default payment method |
| `payment_status` | Или валиден default payment status |
| `invoice_book` | Задължителен за фактура, освен ако има default invoice book |

`issue_date` по подразбиране е текущата дата. `tax_date` по подразбиране е равна на `issue_date`, но е препоръчително и двете да се подават изрично.

`totals` е незадължителен. Ако бъде подаден, API-то го сравнява с изчислените стойности от редовете и връща `TOTALS_MISMATCH` при съществена разлика.

### Фирма

За фирма:

```json
{
  "customer": {
    "name": "Примерна фирма ООД",
    "reg_number": "123456789",
    "vat_id": "BG123456789",
    "country_code": "BG",
    "city": "София",
    "postcode": "1000",
    "address": "ул. Пример 1",
    "mol": "Иван Иванов",
    "email": "office@example.com",
    "is_person": false
  }
}
```

Задължителни са `name` и `reg_number`. `vat_id` и `mol` са незадължителни. Държава, град, пощенски код и адрес е препоръчително да се подават винаги.

### Физическо лице

```json
{
  "customer": {
    "name": "Иван Петров",
    "country_code": "BG",
    "city": "София",
    "address": "ул. Пример 2",
    "email": "ivan@example.com",
    "is_person": true
  }
}
```

При `is_person: true` може да не се подава `reg_number`.

## Създаване на проформа

Използва се същият endpoint:

```http
POST /documents
```

Основната разлика е:

```json
{
  "external_order_id": "order-10001-proforma",
  "type": "proforma"
}
```

Проформа не изисква `invoice_book`. Останалите данни за клиент, валута, плащане и редове са същите.

Препоръчителен idempotency key:

```text
custom:proforma:order-10001
```

## Езици и изпращане по имейл

`lang_id` е основният език на документа.

`lang2_id` е вторият език. Ако е различен от основния, response-ът съдържа и `links.pdf_secondary`.

Пример:

```json
{
  "lang_id": 2,
  "lang2_id": 1,
  "currency_en": {
    "internal_id": 33
  },
  "send_email": true,
  "email_to": "client@example.com"
}
```

Ако езикът на клиента не присъства в `/meta.languages`, използвайте английския език с `code: "en"`.

При `send_email: true` и валиден получател документът се добавя в опашката за изпращане. Когато има втори език, към клиента се изпраща вторичната езикова версия.

Допълнителни полета:

```json
{
  "email_to": "client@example.com",
  "email_copy": "accounting@example.com",
  "email_subject": "Фактура за поръчка order-10001",
  "email_description": "Благодарим за поръчката."
}
```

## Успешен response при създаване

Нов документ връща HTTP `201 Created`:

```json
{
  "status": "success",
  "data": {
    "invoice_id": 2075501,
    "document_type": "invoice",
    "parent_invoice_id": 2075501,
    "for_invoice_id": 0,
    "invoice_number": 300000021,
    "date_from": "2026-07-27",
    "date_danuchno": "2026-07-27",
    "lang_id": 2,
    "lang2_id": 1,
    "currency_id": 33,
    "currency_id_en": 33,
    "totals": {
      "total_no_vat": 100,
      "total_vat": 20,
      "total": 120
    },
    "hidden": 0,
    "accounted_status": 0,
    "payment_status_id": 85,
    "managed_by_current_integration": true,
    "external_order_id": "order-10001",
    "external_refund_id": "",
    "idempotency_key": "custom:invoice:order-10001",
    "pdf_url": "https://www2.faktura1.com/view-pdf/ENCRYPTED_VALUE",
    "links": {
      "pdf": "https://www2.faktura1.com/view-pdf/ENCRYPTED_VALUE",
      "pdf_original": "https://www2.faktura1.com/view-pdf/ENCRYPTED_VALUE",
      "pdf_copy": "https://www2.faktura1.com/view-pdf/ENCRYPTED_VALUE",
      "pdf_secondary": "https://www2.faktura1.com/view-pdf/ENCRYPTED_VALUE",
      "pdf_secondary_copy": "https://www2.faktura1.com/view-pdf/ENCRYPTED_VALUE"
    },
    "idempotent": false,
    "warnings": [],
    "email_queued": true
  }
}
```

Във външната система трябва да се запазят поне:

```text
data.invoice_id
data.invoice_number
data.date_from
data.external_order_id
data.pdf_url
```

`invoice_id` е вътрешният уникален ID на записа. Номерът, който трябва да се показва на потребителя, е `invoice_number`, заедно с `date_from`.

## Защита от дублиране

Използвайте един и същ `Idempotency-Key` при повторен опит за същата операция.

```text
custom:invoice:order-10001
```

Ако request-ът се повтори със същия key и същия payload, API-то връща съществуващия документ с HTTP `200`:

```json
{
  "status": "success",
  "data": {
    "invoice_id": 2075501,
    "invoice_number": 300000021,
    "idempotent": true,
    "matched_by": "idempotency_key"
  }
}
```

Ако същият key бъде използван с различен payload:

```json
{
  "status": "error",
  "code": "IDEMPOTENCY_CONFLICT",
  "message": "Idempotency-Key was already used with different payload."
}
```

## Списък с издадени документи

Endpoint:

```http
GET /documents
```

Този endpoint връща:

- активни документи с `hidden=0`;
- документи на фирмата, към която принадлежи API token-ът;
- документи независимо дали са създадени ръчно, от текущата API интеграция или от друга интеграция.

Документи на друга фирма и изтрити документи с `hidden=1` не се връщат.

Всеки документ съдържа `managed_by_current_integration`:

- `true` - документът е създаден и се управлява от текущата API интеграция;
- `false` - документът може да бъде прочетен, но не може да бъде променян, изтриван или използван за кредитно известие от текущата интеграция.

Поддържани query параметри:

| Параметър | По подразбиране | Описание |
|---|---:|---|
| `page` | `1` | Номер на страницата, започващ от `1` |
| `limit` | `50` | Брой записи; максималната използвана стойност е `100` |
| `type` | всички | `invoice`, `proforma`, `credit_note` или `stock` |
| `date_from` | няма | Документи с дата от тази дата включително |
| `date_to` | няма | Документи с дата до тази дата включително |
| `external_order_id` | няма | Точно съвпадение по външна поръчка на текущата интеграция |
| `invoice_number` | няма | Точно съвпадение с номера на документа |
| `reg_number` | няма | Точно съвпадение с `contractors.reg_number` (Булстат/ЕИК на клиента) |
| `payment_status` | няма | Точно съвпадение с един активен код от `GET /meta` → `payment_statuses` |

Датите могат да бъдат подадени като `YYYY-MM-DD`, например `2026-07-01`. Резултатите са подредени от най-новите към най-старите.

Пример за всички активни документи, издадени на клиент с Булстат/ЕИК `123456789`:

```http
GET /documents?reg_number=123456789
```

`reg_number` се сравнява точно. Когато съдържа букви или други специални знаци, стойността трябва да бъде URL encoded.

Пример за документи с един платежен статус:

```http
GET /documents?payment_status=paid
```

Подаваният `payment_status` трябва да е един код от `GET /meta`, полето `data.payment_statuses`. Не се приемат няколко статуса или списък със стойности. При непознат или неактивен код API-то връща HTTP `422` с `code: "INVALID_PAYMENT_STATUS"`.

Примерно извикване:

```bash
curl --request GET \
  --url "https://api.faktura1.com/api/v1/documents?page=1&limit=20&type=invoice&date_from=2026-07-01&date_to=2026-07-31" \
  --header "Authorization: Bearer YOUR_API_TOKEN" \
  --header "X-Faktura-Integration: custom" \
  --header "Accept: application/json"
```

Съкратен примерен response:

```json
{
  "status": "success",
  "data": {
    "documents": [
      {
        "invoice_id": 2075501,
        "document_type": "invoice",
        "invoice_number": 300000021,
        "date_from": "2026-07-25",
        "totals": {
          "total_no_vat": 100,
          "total_vat": 20,
          "total": 120
        },
        "hidden": 0,
        "managed_by_current_integration": true,
        "external_order_id": "order-10001",
        "pdf_url": "https://www2.faktura1.com/view-pdf/..."
      }
    ],
    "pagination": {
      "page": 1,
      "limit": 20,
      "total_items": 1,
      "total_pages": 1,
      "has_next_page": false,
      "has_previous_page": false
    },
    "filters": {
      "type": "invoice",
      "date_from": "2026-07-01",
      "date_to": "2026-07-31",
      "external_order_id": null,
      "invoice_number": null,
      "reg_number": null,
      "payment_status": "paid"
    }
  }
}
```

При ръчно създаден документ `managed_by_current_integration` е `false`, а `external_order_id`, `external_refund_id` и `idempotency_key` са `null`.

С готовия PHP клиент:

```php
$response = $api->listDocuments([
    'page' => 1,
    'limit' => 20,
    'type' => 'invoice',
    'date_from' => '2026-07-01',
    'date_to' => '2026-07-31',
    'reg_number' => '123456789',
    'payment_status' => 'paid',
]);

foreach ($response['data']['documents'] as $document) {
    echo $document['invoice_number'] . PHP_EOL;
    echo $document['pdf_url'] . PHP_EOL;
}
```

## Преглед на документ

По `invoice_id`:

```http
GET /documents/2075501
```

Това извикване може да върне всеки активен документ на фирмата, независимо как е създаден.

По външен номер на поръчка:

```http
GET /documents/by-external/custom/order-10001
```

Path стойностите трябва да бъдат URL encoded.

Търсенето по външен номер остава ограничено до текущата API интеграция, защото ръчно създадените документи нямат `external_order_id`. Изтритите документи са с `hidden=1` и не се връщат от тези endpoint-и.

## Промяна на статус на плащане

По `invoice_id`:

```http
PATCH /documents/2075501/payment-status
```

```json
{
  "payment_status": {
    "internal_id": 86
  }
}
```

По външен номер на поръчка:

```http
PATCH /documents/by-external/custom/order-10001/payment-status
```

```json
{
  "document_type": "invoice",
  "payment_status": {
    "external_code": "paid"
  }
}
```

## Изтриване на документ

```http
DELETE /documents/2075501
```

При успешно изтриване API-то задава `hidden=1` и връща документа с:

```json
{
  "status": "success",
  "data": {
    "invoice_id": 2075501,
    "invoice_number": 300000021,
    "hidden": 1
  }
}
```

Осчетоводен документ не може да бъде изтрит и връща:

```json
{
  "status": "error",
  "code": "DOCUMENT_ACCOUNTED",
  "message": "Accounted document cannot be deleted."
}
```

След изтриване може да бъде създаден нов документ със същия `external_order_id`. Скритият запис няма да бъде използван повторно.

## Създаване на кредитно известие

```http
POST /documents/2075501/credit-note
```

```json
{
  "external_refund_id": "refund-5001",
  "issue_date": "2026-07-28",
  "comment": "Върната стока",
  "items": [
    {
      "name": "Абонаментна услуга",
      "quantity": "1",
      "unit": {
        "internal_id": 10
      },
      "unit_price": "100.00",
      "vat_percent": "20",
      "total": "100.00"
    }
  ],
  "totals": {
    "total_no_vat": "100.00",
    "total_vat": "20.00",
    "total": "120.00"
  }
}
```

Липсващите данни за клиент, плащане, валута и кочан се наследяват от фактурата. Използвайте отделен idempotency key:

```text
custom:credit-note:refund-5001
```

## Custom mappings

Mappings са налични само за integration със `Source: custom`.

Създаване или редактиране:

```http
POST /mappings
```

```json
{
  "map_type": "payment_method",
  "external_code": "cod",
  "external_label": "Cash on delivery",
  "internal_id": 25,
  "is_default": false
}
```

Примерен response:

```json
{
  "status": "success",
  "data": {
    "api_mapping_id": 501,
    "map_type": "payment_method",
    "external_code": "cod",
    "external_label": "Cash on delivery",
    "internal_id": 25,
    "is_default": 0,
    "status": 1
  }
}
```

След това документ може да подаде:

```json
{
  "payment_method": "cod"
}
```

или:

```json
{
  "payment_method": {
    "external_code": "cod"
  }
}
```

## Грешки

Всички грешки имат единен формат:

```json
{
  "status": "error",
  "code": "INVALID_NOMENCLATURE_ID",
  "message": "Invalid internal nomenclature id.",
  "details": {
    "field": "bank_account",
    "map_type": "bank_account",
    "internal_id": 99999
  }
}
```

Основни HTTP status кодове:

| HTTP status | Значение |
|---|---|
| `200` | Успешен request или idempotent повторение |
| `201` | Нов документ е създаден |
| `400` | Невалиден JSON/request |
| `401` | Липсващ или невалиден token |
| `403` | Липсващо право, грешен source или забранено IP |
| `404` | Документът не е намерен или е изтрит |
| `409` | Конфликт, дублиране или бизнес ограничение |
| `422` | Невалидни полета, номенклатури или суми |
| `500` | Вътрешна грешка |

Често срещани `code` стойности:

```text
AUTHORIZATION_MISSING
TOKEN_INVALID
INTEGRATION_DISABLED
INTEGRATION_SOURCE_MISMATCH
IP_NOT_ALLOWED
SCOPE_FORBIDDEN
INVALID_JSON
EXTERNAL_ORDER_ID_REQUIRED
INVALID_DOCUMENT_TYPE
INVALID_PAYMENT_STATUS
CUSTOMER_NAME_REQUIRED
CUSTOMER_REG_NUMBER_REQUIRED
DOCUMENT_LINES_REQUIRED
INVALID_NOMENCLATURE_ID
NOMENCLATURE_NOT_MAPPED
TOTALS_MISMATCH
IDEMPOTENCY_CONFLICT
DOCUMENT_NOT_FOUND
DOCUMENT_NOT_OWNED_BY_INTEGRATION
DOCUMENT_ACCOUNTED
DOCUMENT_NUMBER_EXISTS
INVOICE_BOOK_FULL
```

Клиентският код трябва да проверява едновременно HTTP status и JSON полето `status`.

## Готов PHP клиент

Готовият framework-independent PHP клиент е:

```text
docs/api-public/examples/Faktura1ApiClient.php
```

Той предоставя методи за:

```php
$api->getMeta();
$api->listDocuments();
$api->createDocument();
$api->getDocument();
$api->findDocument();
$api->createCreditNote();
$api->updatePaymentStatus();
$api->updatePaymentStatusByExternal();
$api->deleteDocument();
$api->getMappings();
$api->saveMapping();
$api->deleteMapping();
```

Минимална употреба:

```php
<?php

require_once __DIR__ . '/Faktura1ApiClient.php';

$api = new Faktura1ApiClient(
    'YOUR_API_TOKEN',
    'custom',
    'https://api.faktura1.com/api/v1'
);

$response = $api->createDocument(
    $payload,
    'custom:invoice:order-10001'
);

$document = $response['data'];

echo $document['invoice_number'];
echo $document['pdf_url'];
```

Пълният работещ пример е:

```text
docs/api-public/examples/create-document.php
```

PowerShell:

```powershell
$env:F1_API_TOKEN = "YOUR_API_TOKEN"
$env:F1_EXTERNAL_ORDER_ID = "order-10001"
$env:F1_SEND_EMAIL = "0"

php docs/api-public/examples/create-document.php invoice
php docs/api-public/examples/create-document.php proforma
```

Linux/macOS:

```bash
export F1_API_TOKEN="YOUR_API_TOKEN"
export F1_EXTERNAL_ORDER_ID="order-10001"
export F1_SEND_EMAIL="0"

php docs/api-public/examples/create-document.php invoice
php docs/api-public/examples/create-document.php proforma
```

За реална интеграция заменете demo customer/items данните в примера с данните от поръчката. `external_order_id` и `Idempotency-Key` трябва да останат стабилни при повторен опит.

## Production checklist

- Token-ът не се записва в публичен source control.
- Използва се HTTPS.
- Token-ът е с минимално необходимите scopes.
- По възможност са зададени `Allowed IPs`.
- Номенклатурите се вземат от `/meta`.
- Запазва се `invoice_id`, а на потребителя се показват `invoice_number` и `date_from`.
- Всеки write request използва стабилен `Idempotency-Key`.
- Обработват се HTTP грешките и JSON полето `code`.
- При timeout request-ът се повтаря със същия idempotency key.
- Изтритите документи не се приемат за активни.
- Token-ът се регенерира при съмнение за компрометиране.
