Začínáme s API

Jak se přihlásit, poprvé zavolat API a zvládnout nejčastější scénáře. Všechny příklady jsou v curl, C#, JavaScriptu a TypeScriptu.

Úvod

API je dostupné na adrese https://{CompanyName}.api.ros.bclogia.cz, kde {CompanyName} je název vaší firmy (databáze). Podle této adresy API pozná, se kterou firmou pracujete.

Rozhraní jsou dvě, každé má vlastní dokumentaci a přihlášení:

RozhraníPro kohoPřihlášeníDokumentace
useraplikace, ve kterých pracuje přihlášený uživateljméno a heslo, metoda auth/login/doc/v1-user
systemsystémová integrace bez uživateleOAuth2 client credentials (ClientID a ClientSecret)/doc/v1-system

Všechna volání se posílají přes HTTPS a v JSON. Příklady v C# předpokládají HttpClient http s nastaveným BaseAddress a hlavičkou Authorization z přihlášení, příklady v JavaScriptu a TypeScriptu konstanty BASE a headers.

Přihlášení – uživatel

Zavolejte POST /api/v1/auth/login s uživatelským jménem a heslem. Odpověď obsahuje přístupový token (access_token), obnovovací token (refresh_token) a platnost přístupového tokenu v sekundách (expires_in). Přístupový token posílejte v každém dalším volání v hlavičce Authorization: Bearer ….

curl -X POST "https://{CompanyName}.api.ros.bclogia.cz/api/v1/auth/login" \
  -H "Content-Type: application/json" \
  -d '{"Username":"jan.novak","Password":"tajne-heslo"}'
using System.Net.Http.Headers;
using System.Net.Http.Json;
using System.Text.Json.Nodes;

using var http = new HttpClient { BaseAddress = new Uri("https://{CompanyName}.api.ros.bclogia.cz") };

var response = await http.PostAsJsonAsync("/api/v1/auth/login",
    new { Username = "jan.novak", Password = "tajne-heslo" });
response.EnsureSuccessStatusCode();

var tokens = await response.Content.ReadFromJsonAsync<JsonObject>();
var accessToken = (string)tokens!["access_token"]!;
var refreshToken = (string)tokens["refresh_token"]!;

http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
const BASE = "https://{CompanyName}.api.ros.bclogia.cz";

const response = await fetch(`${BASE}/api/v1/auth/login`, {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ Username: "jan.novak", Password: "tajne-heslo" }),
});
if (!response.ok) throw new Error(`Přihlášení selhalo (${response.status})`);

const tokens = await response.json();
const headers = {
  "Content-Type": "application/json",
  Authorization: `Bearer ${tokens.access_token}`,
};
const BASE = "https://{CompanyName}.api.ros.bclogia.cz";

interface TokenResponse {
  access_token: string;
  refresh_token: string;
  expires_in: number;
  token_type: string;
  scope: string;
}

const response = await fetch(`${BASE}/api/v1/auth/login`, {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ Username: "jan.novak", Password: "tajne-heslo" }),
});
if (!response.ok) throw new Error(`Přihlášení selhalo (${response.status})`);

const tokens: TokenResponse = await response.json();
const headers: Record<string, string> = {
  "Content-Type": "application/json",
  Authorization: `Bearer ${tokens.access_token}`,
};

Před vypršením přístupového tokenu ho vyměňte za nový voláním POST /api/v1/auth/refresh s tělem {"RefreshToken":"…"}. Odhlášení (POST /api/v1/auth/logout, stejné tělo) obnovovací token zneplatní.

Přihlášení – systém

Systémová integrace získá token z identity serveru metodou OAuth2 client credentials. Údaje client_id a client_secret vám přidělí správce. Kromě standardních polí pošlete i company – název firmy, pro kterou token vydáváte. Token platí pro rozhraní system (scope=ros).

curl -X POST "https://identity.bclogia.cz/connect/token" \
  -d "grant_type=client_credentials" \
  -d "client_id=MOJE_CLIENT_ID" \
  -d "client_secret=MOJE_CLIENT_SECRET" \
  -d "scope=ros" \
  -d "company={CompanyName}"
using System.Net.Http.Headers;
using System.Net.Http.Json;
using System.Text.Json.Nodes;

using var identity = new HttpClient();
var tokenResponse = await identity.PostAsync("https://identity.bclogia.cz/connect/token",
    new FormUrlEncodedContent(new Dictionary<string, string>
    {
        ["grant_type"] = "client_credentials",
        ["client_id"] = "MOJE_CLIENT_ID",
        ["client_secret"] = "MOJE_CLIENT_SECRET",
        ["scope"] = "ros",
        ["company"] = "{CompanyName}",
    }));
tokenResponse.EnsureSuccessStatusCode();

var token = (string)(await tokenResponse.Content.ReadFromJsonAsync<JsonObject>())!["access_token"]!;

using var http = new HttpClient { BaseAddress = new Uri("https://{CompanyName}.api.ros.bclogia.cz") };
http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
const BASE = "https://{CompanyName}.api.ros.bclogia.cz";

const tokenResponse = await fetch("https://identity.bclogia.cz/connect/token", {
  method: "POST",
  headers: { "Content-Type": "application/x-www-form-urlencoded" },
  body: new URLSearchParams({
    grant_type: "client_credentials",
    client_id: "MOJE_CLIENT_ID",
    client_secret: "MOJE_CLIENT_SECRET",
    scope: "ros",
    company: "{CompanyName}",
  }),
});
if (!tokenResponse.ok) throw new Error(`Získání tokenu selhalo (${tokenResponse.status})`);

const { access_token } = await tokenResponse.json();
const headers = {
  "Content-Type": "application/json",
  Authorization: `Bearer ${access_token}`,
};
const BASE = "https://{CompanyName}.api.ros.bclogia.cz";

const tokenResponse = await fetch("https://identity.bclogia.cz/connect/token", {
  method: "POST",
  headers: { "Content-Type": "application/x-www-form-urlencoded" },
  body: new URLSearchParams({
    grant_type: "client_credentials",
    client_id: "MOJE_CLIENT_ID",
    client_secret: "MOJE_CLIENT_SECRET",
    scope: "ros",
    company: "{CompanyName}",
  }),
});
if (!tokenResponse.ok) throw new Error(`Získání tokenu selhalo (${tokenResponse.status})`);

const { access_token } = (await tokenResponse.json()) as { access_token: string };
const headers: Record<string, string> = {
  "Content-Type": "application/json",
  Authorization: `Bearer ${access_token}`,
};
Tajné údaje. client_secret nikdy neukládejte do kódu ani do webové aplikace v prohlížeči – token z identity serveru si vyžádejte na straně serveru.

První volání: seznam s řazením a stránkováním

Seznamy se získávají voláním POST …/list (například POST /api/v1/company/list). V těle pošlete filtr a pole Skip a Take pro stránkování a Sorted pro řazení (název pole z odpovědi a směr Ascending nebo Descending).

Odpověď má tvar { "result": [...], "count": 123, "requestId": "…", "cached": true }: result jsou záznamy stránky, count celkový počet záznamů ve výběru.

Další strany: kdy se používá x-request-id

Hlavičku x-request-id používejte jen u seznamů, jejichž odpověď vedle requestId obsahuje i cached = true (například seznam firem). Takový seznam si API při prvním volání připraví a další strany, řazení i stránkování se pak provádějí nad připraveným výsledkem. U ostatních seznamů (cached = false) se každé volání vyhodnotí znovu a hlavičku nepoužívejte; stránkujte jen pomocí Skip a Take.

Bez x-request-id se u připraveného seznamu vždy vrací první strana. Hodnota Skip se v takovém volání ignoruje a výsledek se připraví znovu. Chcete-li další strany, musíte poslat requestId z předchozí odpovědi v hlavičce x-request-id.

Postup u připraveného seznamu: první volání (bez hlavičky) vrátí první stranu a requestId. Další strany vyžádejte se stejným výběrem, požadovaným Skip a hodnotou requestId v hlavičce x-request-id. Pokud platnost připraveného výsledku vypršela, API ho připraví znovu a vrátí nové requestId a informační zprávu v messages; v takovém případě je vrácená strana první a další strany pak žádejte s novým requestId.

# první strana
curl -X POST "https://{CompanyName}.api.ros.bclogia.cz/api/v1/company/list" \
  -H "Authorization: Bearer ACCESS_TOKEN" -H "Content-Type: application/json" \
  -d '{"OrganizationName":"Novák","Skip":0,"Take":50,"Sorted":[{"Name":"OrganizationFullName","Direction":"Ascending"}]}'

# druhá strana: stejné tělo, jiné Skip a requestId z první odpovědi
curl -X POST "https://{CompanyName}.api.ros.bclogia.cz/api/v1/company/list" \
  -H "Authorization: Bearer ACCESS_TOKEN" -H "Content-Type: application/json" \
  -H "x-request-id: REQUEST_ID" \
  -d '{"OrganizationName":"Novák","Skip":50,"Take":50,"Sorted":[{"Name":"OrganizationFullName","Direction":"Ascending"}]}'
using System.Net.Http.Json;
using System.Text.Json.Nodes;

var filter = new
{
    OrganizationName = "Novák",
    Take = 50,
    Sorted = new[] { new { Name = "OrganizationFullName", Direction = "Ascending" } },
};

string? requestId = null;
for (var skip = 0; ; skip += 50)
{
    using var request = new HttpRequestMessage(HttpMethod.Post, "/api/v1/company/list")
    {
        Content = JsonContent.Create(new { filter.OrganizationName, Skip = skip, filter.Take, filter.Sorted }),
    };
    if (requestId is not null) request.Headers.Add("x-request-id", requestId);

    var response = await http.SendAsync(request);
    response.EnsureSuccessStatusCode();

    var page = (await response.Content.ReadFromJsonAsync<JsonObject>())!;
    requestId = (string?)page["requestId"];

    var records = page["result"]!.AsArray();
    foreach (var record in records) Console.WriteLine(record!["OrganizationFullName"]);

    if (skip + records.Count >= (int)page["count"]!) break;
}
const take = 50;
let requestId = null;

for (let skip = 0; ; skip += take) {
  const response = await fetch(`${BASE}/api/v1/company/list`, {
    method: "POST",
    headers: requestId ? { ...headers, "x-request-id": requestId } : headers,
    body: JSON.stringify({
      OrganizationName: "Novák",
      Skip: skip,
      Take: take,
      Sorted: [{ Name: "OrganizationFullName", Direction: "Ascending" }],
    }),
  });
  if (!response.ok) throw new Error(`Seznam selhal (${response.status})`);

  const page = await response.json();
  requestId = page.requestId;
  page.result.forEach((c) => console.log(c.OrganizationFullName));

  if (skip + page.result.length >= page.count) break;
}
interface Page<T> {
  result: T[];
  count: number;
  requestId: string;
  cached: boolean;
  messages: { code: string; message: string }[] | null;
}

interface CompanyRow {
  CompanyId: number;
  OrganizationFullName: string;
  CompanyRegistrationNumber: string;
}

interface Sort {
  Name: string;
  Direction: "Ascending" | "Descending";
}

const take = 50;
let requestId: string | null = null;

for (let skip = 0; ; skip += take) {
  const response: Response = await fetch(`${BASE}/api/v1/company/list`, {
    method: "POST",
    headers: requestId ? { ...headers, "x-request-id": requestId } : headers,
    body: JSON.stringify({
      OrganizationName: "Novák",
      Skip: skip,
      Take: take,
      Sorted: [{ Name: "OrganizationFullName", Direction: "Ascending" }] satisfies Sort[],
    }),
  });
  if (!response.ok) throw new Error(`Seznam selhal (${response.status})`);

  const page: Page<CompanyRow> = await response.json();
  requestId = page.requestId;
  page.result.forEach((c) => console.log(c.OrganizationFullName));

  if (skip + page.result.length >= page.count) break;
}

Ostatní seznamy (city, street, address, country, …) se volají stejně. Zda u nich hlavičku x-request-id použít, poznáte podle pole cached v odpovědi (viz výše); popis je i u příslušné metody v dokumentaci.

Synchronizace změn firem (systém)

Chcete-li mít kopii firem ve svém systému, stahujte jen změny. Postup tvoří dvě volání:

  1. POST /api/v1/system/company/list_changed vrátí dávku změněných firem seřazenou podle verze (RowVersion). První dávku vyžádejte bez LastRv; do dalšího požadavku pošlete RowVersion z posledního záznamu předchozí dávky. Prázdný seznam znamená, že už žádné další změny nejsou. Velikost dávky (BatchSize) je 1 až 100, výchozí 20.
  2. POST /api/v1/system/company/read_batch vrátí detail firem podle seznamu IDS (nejvýše 100 ID).

Zrušená firma se vrací se Deleted = true; je-li nahrazena jinou, je v ReplacedById její ID. Poslední RowVersion si uložte, příště se od něj začíná.

curl -X POST "https://{CompanyName}.api.ros.bclogia.cz/api/v1/system/company/list_changed" \
  -H "Authorization: Bearer ACCESS_TOKEN" -H "Content-Type: application/json" \
  -d '{"LastRv":null,"BatchSize":100}'

curl -X POST "https://{CompanyName}.api.ros.bclogia.cz/api/v1/system/company/read_batch" \
  -H "Authorization: Bearer ACCESS_TOKEN" -H "Content-Type: application/json" \
  -d '{"IDS":[101,102,103]}'
using System.Net.Http.Json;
using System.Text.Json.Nodes;

string? lastRv = null;   // z posledního běhu; při prvním běhu null
while (true)
{
    var changedResponse = await http.PostAsJsonAsync("/api/v1/system/company/list_changed",
        new { LastRv = lastRv, BatchSize = 100 });
    changedResponse.EnsureSuccessStatusCode();

    var changed = (await changedResponse.Content.ReadFromJsonAsync<JsonArray>())!;
    if (changed.Count == 0) break;   // hotovo

    var ids = changed.Where(c => !(bool)c!["Deleted"]!).Select(c => (int)c!["CompanyId"]!).ToArray();
    if (ids.Length > 0)
    {
        var detailResponse = await http.PostAsJsonAsync("/api/v1/system/company/read_batch", new { IDS = ids });
        detailResponse.EnsureSuccessStatusCode();
        var details = (await detailResponse.Content.ReadFromJsonAsync<JsonArray>())!;
        // ... uložte details do svého systému
    }

    // zrušené firmy (Deleted, případně ReplacedById) zpracujte podle záznamů v changed
    lastRv = (string)changed[^1]!["RowVersion"]!;   // uložte si ho i trvale
}
let lastRv = null; // z posledního běhu; při prvním běhu null
for (;;) {
  const changedResponse = await fetch(`${BASE}/api/v1/system/company/list_changed`, {
    method: "POST",
    headers,
    body: JSON.stringify({ LastRv: lastRv, BatchSize: 100 }),
  });
  if (!changedResponse.ok) throw new Error(`list_changed selhal (${changedResponse.status})`);

  const changed = await changedResponse.json();
  if (changed.length === 0) break; // hotovo

  const ids = changed.filter((c) => !c.Deleted).map((c) => c.CompanyId);
  if (ids.length > 0) {
    const detailResponse = await fetch(`${BASE}/api/v1/system/company/read_batch`, {
      method: "POST",
      headers,
      body: JSON.stringify({ IDS: ids }),
    });
    const details = await detailResponse.json();
    // ... uložte details do svého systému
  }

  // zrušené firmy (Deleted, případně ReplacedById) zpracujte podle záznamů v changed
  lastRv = changed[changed.length - 1].RowVersion; // uložte si ho i trvale
}
interface ChangedCompany {
  CompanyId: number;
  RowVersion: string;
  Invalidated: boolean;
  ReplacedById: number | null;
  Deleted: boolean;
}

let lastRv: string | null = null; // z posledního běhu; při prvním běhu null
for (;;) {
  const changedResponse: Response = await fetch(`${BASE}/api/v1/system/company/list_changed`, {
    method: "POST",
    headers,
    body: JSON.stringify({ LastRv: lastRv, BatchSize: 100 }),
  });
  if (!changedResponse.ok) throw new Error(`list_changed selhal (${changedResponse.status})`);

  const changed: ChangedCompany[] = await changedResponse.json();
  if (changed.length === 0) break; // hotovo

  const ids = changed.filter((c) => !c.Deleted).map((c) => c.CompanyId);
  if (ids.length > 0) {
    const detailResponse = await fetch(`${BASE}/api/v1/system/company/read_batch`, {
      method: "POST",
      headers,
      body: JSON.stringify({ IDS: ids }),
    });
    const details: Record<string, unknown>[] = await detailResponse.json();
    // ... uložte details do svého systému
  }

  // zrušené firmy (Deleted, případně ReplacedById) zpracujte podle záznamů v changed
  lastRv = changed[changed.length - 1].RowVersion; // uložte si ho i trvale
}

Zakládání a změna firem (uživatel)

1. Nejdřív dohledejte údaje

Při zakládání firmy API nic nedohledává; všechny hodnoty musíte předat již zjištěné. Před založením proto zavolejte:

2. Založení podle typu firmy

MetodaTyp firmy
POST /api/v1/company/create/companypodnikající, s IČ
POST /api/v1/company/create/company-noidpodnikající, bez IČ
POST /api/v1/company/create/foreignpodnikající, zahraniční subjekt
POST /api/v1/company/create/municipalityobec
POST /api/v1/company/create/citizennepodnikající, občan
POST /api/v1/company/create/unknownbez rozlišení typu (vyžaduje zvláštní oprávnění)

Odpověď (201) obsahuje celý záznam založené firmy včetně jejího CompanyId. Sídlo se zakládá s prázdným nebo nulovým číslem provozovny; u provozovny zadejte její číslo a identifikátor organizace sídla (ParentOrganization).

curl -X POST "https://{CompanyName}.api.ros.bclogia.cz/api/v1/company/create/company" \
  -H "Authorization: Bearer ACCESS_TOKEN" -H "Content-Type: application/json" \
  -d '{
    "CompanyRegistrationNumber": "12345678",
    "OrganizationName": "Novák a syn s.r.o.",
    "VatNumber": "CZ12345678",
    "WinyxAddressId": 1001,
    "CityId": 55,
    "CityStreetId": 3021,
    "PostalCode": "11000",
    "HouseNumber": "12"
  }'
using System.Net.Http.Json;
using System.Text.Json.Nodes;

var response = await http.PostAsJsonAsync("/api/v1/company/create/company", new
{
    CompanyRegistrationNumber = "12345678",
    OrganizationName = "Novák a syn s.r.o.",
    VatNumber = "CZ12345678",
    WinyxAddressId = 1001,   // AddressId z address/geography/resolve
    CityId = 55,
    CityStreetId = 3021,
    PostalCode = "11000",
    HouseNumber = "12",
});

if (response.IsSuccessStatusCode)
{
    var company = (await response.Content.ReadFromJsonAsync<JsonObject>())!;
    Console.WriteLine($"Založena firma {company["CompanyId"]}");
}
// 409 s ConfirmationRequired viz kapitola Chyby a potvrzení
const response = await fetch(`${BASE}/api/v1/company/create/company`, {
  method: "POST",
  headers,
  body: JSON.stringify({
    CompanyRegistrationNumber: "12345678",
    OrganizationName: "Novák a syn s.r.o.",
    VatNumber: "CZ12345678",
    WinyxAddressId: 1001, // AddressId z address/geography/resolve
    CityId: 55,
    CityStreetId: 3021,
    PostalCode: "11000",
    HouseNumber: "12",
  }),
});

if (response.ok) {
  const company = await response.json();
  console.log(`Založena firma ${company.CompanyId}`);
}
// 409 s ConfirmationRequired viz kapitola Chyby a potvrzení
interface NewCompany {
  CompanyRegistrationNumber: string;
  OrganizationName: string;
  VatNumber?: string;
  WinyxAddressId?: number;
  CityId?: number;
  CityStreetId?: number;
  PostalCode?: string;
  HouseNumber?: string;
}

const newCompany: NewCompany = {
  CompanyRegistrationNumber: "12345678",
  OrganizationName: "Novák a syn s.r.o.",
  VatNumber: "CZ12345678",
  WinyxAddressId: 1001, // AddressId z address/geography/resolve
  CityId: 55,
  CityStreetId: 3021,
  PostalCode: "11000",
  HouseNumber: "12",
};

const response = await fetch(`${BASE}/api/v1/company/create/company`, {
  method: "POST",
  headers,
  body: JSON.stringify(newCompany),
});

if (response.ok) {
  const company: { CompanyId: number } = await response.json();
  console.log(`Založena firma ${company.CompanyId}`);
}
// 409 s ConfirmationRequired viz kapitola Chyby a potvrzení

3. Změna a odstranění

# načtení, úprava (v souboru firma.json) a odeslání celého záznamu
curl "https://{CompanyName}.api.ros.bclogia.cz/api/v1/company?CompanyId=101" \
  -H "Authorization: Bearer ACCESS_TOKEN" -o firma.json

curl -X PUT "https://{CompanyName}.api.ros.bclogia.cz/api/v1/company?CompanyId=101" \
  -H "Authorization: Bearer ACCESS_TOKEN" -H "Content-Type: application/json" \
  --data-binary @firma.json
using System.Net.Http.Json;
using System.Text.Json.Nodes;

var company = (await http.GetFromJsonAsync<JsonObject>("/api/v1/company?CompanyId=101"))!;
company["Note"] = "Nová poznámka";   // upravte, co potřebujete

var response = await http.PutAsJsonAsync("/api/v1/company?CompanyId=101", company);   // celý záznam
response.EnsureSuccessStatusCode();
const getResponse = await fetch(`${BASE}/api/v1/company?CompanyId=101`, { headers });
const company = await getResponse.json();
company.Note = "Nová poznámka"; // upravte, co potřebujete

const response = await fetch(`${BASE}/api/v1/company?CompanyId=101`, {
  method: "PUT",
  headers,
  body: JSON.stringify(company), // celý záznam
});
if (!response.ok) throw new Error(`Změna selhala (${response.status})`);
type Company = { CompanyId: number } & Record<string, unknown>;

const getResponse = await fetch(`${BASE}/api/v1/company?CompanyId=101`, { headers });
const company: Company = await getResponse.json();
company.Note = "Nová poznámka"; // upravte, co potřebujete

const response = await fetch(`${BASE}/api/v1/company?CompanyId=101`, {
  method: "PUT",
  headers,
  body: JSON.stringify(company), // celý záznam
});
if (!response.ok) throw new Error(`Změna selhala (${response.status})`);

Chyby a potvrzení

Chyby mají vždy stejný tvar (application/problem+json). Pole code slouží ke zpracování ve vaší aplikaci, title a detail můžete zobrazit uživateli, traceId uveďte při hlášení problému. U neplatných dat jsou v issues jednotlivé chyby i s polem, kterého se týkají (field).

StavVýznamCo dělat
400neplatný požadavek nebo dataopravte data podle issues; potvrdit to nelze
401chybí nebo je neplatný tokenpřihlaste se znovu nebo obnovte token
403chybí oprávněnípožádejte správce o oprávnění
404záznam neexistujezkontrolujte identifikátor
409záznam už existuje, nelze odstranit, nebo se vyžaduje potvrzeníviz níže
500chyba na straně serveruzkuste to znovu; při opakování nahlaste traceId

Potvrzení upozornění

Některé operace (založení nebo změna firmy) najdou upozornění, které může uživatel potvrdit – například jinou firmu se stejným IČ nebo DIČ. Operace se pak neprovede a API vrátí 409 s kódem ConfirmationRequired:

{
  "status": 409,
  "code": "ConfirmationRequired",
  "warnings": [ { "code": "DuplicateRegistrationNumber", "text": "Firma se stejným IČ už existuje." } ],
  "confirmationToken": "…",
  "confirmationTokenExpiresAt": "2026-10-07T12:30:00Z"
}
  1. Zobrazte warnings uživateli.
  2. Pokud souhlasí, pošlete úplně stejný požadavek znovu a token přidejte do hlavičky X-Confirmation-Token.
  3. Token platí omezenou dobu (standardně 10 minut), jen pro tato data a jen pro vás. Pokud se data změní nebo se najde další upozornění, API vrátí znovu 409 s novým tokenem – postup opakujte.
# stejný požadavek jako původně, navíc hlavička s tokenem
curl -X POST "https://{CompanyName}.api.ros.bclogia.cz/api/v1/company/create/company" \
  -H "Authorization: Bearer ACCESS_TOKEN" -H "Content-Type: application/json" \
  -H "X-Confirmation-Token: CONFIRMATION_TOKEN" \
  -d @novafirma.json
using System.Net;
using System.Net.Http.Json;
using System.Text.Json.Nodes;

async Task<HttpResponseMessage> CreateAsync(JsonObject body, Func<JsonArray, bool> userConfirms)
{
    string? token = null;
    while (true)
    {
        using var request = new HttpRequestMessage(HttpMethod.Post, "/api/v1/company/create/company")
        {
            Content = JsonContent.Create(body),
        };
        if (token is not null) request.Headers.Add("X-Confirmation-Token", token);

        var response = await http.SendAsync(request);
        if (response.StatusCode != HttpStatusCode.Conflict) return response;

        var error = (await response.Content.ReadFromJsonAsync<JsonObject>())!;
        if ((string?)error["code"] != "ConfirmationRequired") return response;   // jiný 409

        if (!userConfirms(error["warnings"]!.AsArray())) return response;        // uživatel nesouhlasí
        token = (string)error["confirmationToken"]!;                              // opakujte se stejnými daty
    }
}
async function createCompany(body, userConfirms) {
  let token = null;
  for (;;) {
    const response = await fetch(`${BASE}/api/v1/company/create/company`, {
      method: "POST",
      headers: token ? { ...headers, "X-Confirmation-Token": token } : headers,
      body: JSON.stringify(body),
    });
    if (response.status !== 409) return response;

    const error = await response.json();
    if (error.code !== "ConfirmationRequired") return response; // jiný 409

    if (!(await userConfirms(error.warnings))) return response; // uživatel nesouhlasí
    token = error.confirmationToken; // opakujte se stejnými daty
  }
}
interface ApiWarning {
  code: string;
  text: string;
}

interface ApiError {
  code?: string;
  warnings?: ApiWarning[];
  confirmationToken?: string;
}

async function createCompany(
  body: object,
  userConfirms: (warnings: ApiWarning[]) => Promise<boolean>,
): Promise<Response> {
  let token: string | null = null;
  for (;;) {
    const response: Response = await fetch(`${BASE}/api/v1/company/create/company`, {
      method: "POST",
      headers: token ? { ...headers, "X-Confirmation-Token": token } : headers,
      body: JSON.stringify(body),
    });
    if (response.status !== 409) return response;

    const error: ApiError = await response.json();
    if (error.code !== "ConfirmationRequired") return response; // jiný 409

    if (!(await userConfirms(error.warnings ?? []))) return response; // uživatel nesouhlasí
    token = error.confirmationToken ?? null; // opakujte se stejnými daty
  }
}

Import kalkulací (uživatel)

POST /api/v1/calculation/import naimportuje kalkulaci jednoho provozu: hlavičku a tři bloky řádků (WasteRows – odpady, SecondaryRows – druhotné suroviny, ServiceRows – ostatní služby). Počet řádků není omezen.

curl -X POST "https://{CompanyName}.api.ros.bclogia.cz/api/v1/calculation/import" \
  -H "Authorization: Bearer ACCESS_TOKEN" -H "Content-Type: application/json" \
  -d '{
    "CompanyRegistrationNumber": "12345678",
    "PeriodFrom": "2026-01-01",
    "PeriodTo": "2026-12-31",
    "Abc": "B",
    "Division": "Čechy",
    "Plant": "Pardubice",
    "WasteRows": [
      { "WasteID": 1503, "ActivityCode": 110, "Unit": "t", "QtyPerPickup": 0.1, "PickupsPerPeriod": 12, "DisposalPerUnit": 3295, "RevPerUnit": 13650 }
    ],
    "SecondaryRows": [],
    "ServiceRows": [
      { "Item": "Školení", "Unit": "ks", "QtyPerPeriod": 2, "CostPerUnit": 1200, "RevPerUnit": 2000 }
    ]
  }'
using System.Net.Http.Json;
using System.Text.Json.Nodes;

var response = await http.PostAsJsonAsync("/api/v1/calculation/import", new
{
    CompanyRegistrationNumber = "12345678",
    PeriodFrom = "2026-01-01",
    PeriodTo = "2026-12-31",
    Abc = "B",
    Division = "Čechy",
    Plant = "Pardubice",
    WasteRows = new[]
    {
        new { WasteID = 1503, ActivityCode = 110, Unit = "t", QtyPerPickup = 0.1m, PickupsPerPeriod = 12m, DisposalPerUnit = 3295m, RevPerUnit = 13650m },
    },
    SecondaryRows = Array.Empty<object>(),
    ServiceRows = new[]
    {
        new { Item = "Školení", Unit = "ks", QtyPerPeriod = 2m, CostPerUnit = 1200m, RevPerUnit = 2000m },
    },
});
response.EnsureSuccessStatusCode();

var result = (await response.Content.ReadFromJsonAsync<JsonObject>())!;
Console.WriteLine($"Kalkulace {result["CalculationId"]}, nahrazena: {result["Replaced"]}");
const response = await fetch(`${BASE}/api/v1/calculation/import`, {
  method: "POST",
  headers,
  body: JSON.stringify({
    CompanyRegistrationNumber: "12345678",
    PeriodFrom: "2026-01-01",
    PeriodTo: "2026-12-31",
    Abc: "B",
    Division: "Čechy",
    Plant: "Pardubice",
    WasteRows: [
      { WasteID: 1503, ActivityCode: 110, Unit: "t", QtyPerPickup: 0.1, PickupsPerPeriod: 12, DisposalPerUnit: 3295, RevPerUnit: 13650 },
    ],
    SecondaryRows: [],
    ServiceRows: [
      { Item: "Školení", Unit: "ks", QtyPerPeriod: 2, CostPerUnit: 1200, RevPerUnit: 2000 },
    ],
  }),
});
if (!response.ok) throw new Error(`Import selhal (${response.status})`);

const result = await response.json();
console.log(`Kalkulace ${result.CalculationId}, nahrazena: ${result.Replaced}`);
interface WasteRow {
  WasteID: number;
  ActivityCode?: number;
  Unit?: string;
  QtyPerPickup?: number;
  PickupsPerPeriod?: number;
  DisposalPerUnit?: number;
  RevPerUnit?: number;
}

interface ServiceRow {
  Item?: string;
  Unit?: string;
  QtyPerPeriod?: number;
  CostPerUnit?: number;
  RevPerUnit?: number;
}

interface CalculationImport {
  CompanyRegistrationNumber: string;
  PeriodFrom: string; // yyyy-mm-dd
  PeriodTo: string;
  Abc?: "A" | "B" | "C";
  Division?: string;
  Plant?: string;
  WasteRows: WasteRow[];
  SecondaryRows: object[];
  ServiceRows: ServiceRow[];
}

const calculation: CalculationImport = {
  CompanyRegistrationNumber: "12345678",
  PeriodFrom: "2026-01-01",
  PeriodTo: "2026-12-31",
  Abc: "B",
  Division: "Čechy",
  Plant: "Pardubice",
  WasteRows: [
    { WasteID: 1503, ActivityCode: 110, Unit: "t", QtyPerPickup: 0.1, PickupsPerPeriod: 12, DisposalPerUnit: 3295, RevPerUnit: 13650 },
  ],
  SecondaryRows: [],
  ServiceRows: [
    { Item: "Školení", Unit: "ks", QtyPerPeriod: 2, CostPerUnit: 1200, RevPerUnit: 2000 },
  ],
};

const response = await fetch(`${BASE}/api/v1/calculation/import`, {
  method: "POST",
  headers,
  body: JSON.stringify(calculation),
});
if (!response.ok) throw new Error(`Import selhal (${response.status})`);

const result: { CalculationId: number; Replaced: boolean } = await response.json();
console.log(`Kalkulace ${result.CalculationId}, nahrazena: ${result.Replaced}`);

Popis všech polí najdete v dokumentaci rozhraní user.