InvoiceHub dokumentace

Základní adresa staging prostředí je https://invoicehub-api.zeabur.app/api/v1. Consent obrazovku, na kterou uživatele posíláte, hostuje web aplikace: https://invoicehub-web.zeabur.app/oauth/souhlas.

Kdy OAuth a kdy API klíč

API klíčOAuth 2.0
Kdo ho vydá vy ve své firmě každý zákazník sám, ve své firmě
K čí data se dostanete ke svým k datům toho, kdo souhlas udělil
Odvolání smazáním klíče uživatel kdykoliv sám v Nastavení → API a integrace
Životnost dokud klíč nesmažete access token 1 hodinu, refresh token 30 dní

Existující API klíče OAuth nijak neruší ani nemění — fungují přesně jako dosud, jen jim od této verze chodí navíc hlavičky s kvótou.

Registrace aplikace

Aplikaci zaregistrujete v aplikaci InvoiceHub: Nastavení → API a integrace → OAuth aplikace → Nová aplikace. Potřebujete k tomu právo zápisu do sekce Nastavení firmy.

Vyplňujete:

  • Název a popis – uvidí je uživatel na consent obrazovce. Napište pravdivě, co s daty děláte; je to jediné, podle čeho se rozhoduje.
  • Návratové adresy (redirect_uri) – jedna na řádek. Musí být https (výjimkou je localhost pro vývoj), nesmí obsahovat fragment # a porovnávají se na přesnou shodu celého řetězce. Adresa, která se liší jediným znakem nebo parametrem v query, projde jako neplatná.
  • Rozsah – scopes, o které aplikace vůbec smí požádat. Uživatel pak schvaluje jejich podmnožinu.
  • Důvěrný klient – zapnuto u serverových aplikací, které client_secret uchrání. Aplikace běžící v prohlížeči nebo v mobilu ho uchránit nemůže: nechte volbu vypnutou a použijte PKCE.

Tajemství dostanete jen jednou

client_secret se zobrazí pouze v odpovědi na registraci. V databázi z něj zůstane jen bcrypt otisk, takže ho znovu nezobrazí ani podpora. Ztratíte-li ho, vydejte si tlačítkem Nové tajemství další — staré tím okamžitě přestane platit. Vydané access tokeny to nezneplatní (klient se jimi neautentizuje), jen přestane fungovat výměna kódu a obnova, dokud si nové tajemství nedoplníte.

Scopes a oprávnění sekcí

Scope má tvar <sekce>:<read|write> a odpovídá jedna ku jedné sekcím, po kterých se v InvoiceHub rozdávají oprávnění uživatelům. write v sobě obsahuje i read — kdo smí doklad založit, musí ho umět i zobrazit.

SekceScopesCo pokrývá
documents documents:read, documents:write faktury, zálohové faktury, nabídky, objednávky, dodací listy, dobropisy, ceník
costs costs:read, costs:write přijaté náklady
contacts contacts:read, contacts:write odběratelé a dodavatelé
reports reports:read, reports:write reporty, statistiky, uložené pohledy a exporty
company_settings company_settings:read, company_settings:write fakturační údaje, daně, číselné řady, vzhled dokladu, šablony
payments payments:read, payments:write bankovní účty, pohyby, párování plateb, platební brána
automations automations:read, automations:write automatizační pravidla a webhooky

Aktuální katalog i s popisky, které uvidí uživatel, vrací GET /api/v1/oauth/scopes (bez autentizace).

Scope není nová pravomoc, ale strop. Výsledný přístup tokenu je průnik tří věcí: schválených scopes, oprávnění sekcí uživatele, který souhlas udělil, a rozsahu jeho fakturačních oddělení. Požádáte-li o documents:write a uživatel má doklady jen ke čtení, dostanete čtení. Když mu firma oprávnění později odebere, přestane ho mít i váš token — bez jakékoliv akce z vaší strany. Consent obrazovka tohle uživateli říká dopředu: scope, na který sám nemá, ukáže jako nedostupný.

Authorization code flow

Průběh má tři kroky.

1. Pošlete uživatele na consent obrazovku

https://invoicehub-web.zeabur.app/oauth/souhlas
  ?client_id=ihc_...
  &redirect_uri=https://partner.example.com/oauth/callback
  &response_type=code
  &scope=documents:read%20contacts:read
  &state=nahodny-retezec
  • scope – mezerami oddělený seznam. Vynecháte-li ho, zeptáme se na celý rozsah, který má aplikace zaregistrovaný.
  • state – náhodný řetězec, který si zapamatujete v session. Vrátíme vám ho beze změny; neshoduje-li se, požadavek zahoďte. Je to vaše jediná ochrana proti CSRF na návratové adrese.

Není-li uživatel přihlášený, aplikace ho nejdřív přihlásí a pak ho na souhlas vrátí.

2. Uživatel rozhodne, my ho vrátíme k vám

https://partner.example.com/oauth/callback?code=ihac_...&state=nahodny-retezec

Když přístup nepovolí:

https://partner.example.com/oauth/callback?error=access_denied&state=nahodny-retezec

Kód platí 10 minut a dá se uplatnit právě jednou. Druhý pokus o jeho výměnu bereme jako příznak, že kód někdo odposlechl: odvoláme celý souhlas včetně tokenů, které z prvního uplatnění vznikly.

3. Vyměňte kód za tokeny

POST /api/v1/oauth/token
Content-Type: application/json

{
  "grant_type": "authorization_code",
  "client_id": "ihc_...",
  "client_secret": "ihs_...",
  "code": "ihac_...",
  "redirect_uri": "https://partner.example.com/oauth/callback"
}

Odpověď:

{
  "access_token": "iho_...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "ihr_...",
  "scope": "documents:read contacts:read"
}

redirect_uri musí být doslova ta samá, se kterou jste uživatele posílali na souhlas. Tajemství můžete místo těla poslat i přes HTTP Basic (Authorization: Basic base64(client_id:client_secret)).

Pole scope v odpovědi čtěte. Nemusí být stejné, jako to, o co jste žádali — uživatel mohl schválit méně, nebo na část scopes sám nemá právo. Podle něj si zapněte jen ty funkce, které opravdu půjdou.

PKCE pro veřejné klienty

Aplikace bez client_secret (jednostránková aplikace, mobil, desktop) musí použít PKCE podle RFC 7636 — bez něj ji na consent obrazovku nepustíme.

  1. Vyrobte si náhodný code_verifier (43–128 znaků) a spočítejte code_challenge = base64url(sha256(code_verifier)).
  2. K odkazu na souhlas přidejte code_challenge a code_challenge_method=S256.
  3. Při výměně kódu pošlete v těle code_verifier. Neodpovídá-li, dostanete invalid_grant.

Metoda plain je podporovaná jen kvůli klientům bez SHA-256; používejte S256.

Volání API tokenem

Access token se posílá úplně stejně jako API klíč — obyčejnou hlavičkou Authorization. Hlavičku X-Organization-Id neposílejte: firma je pevně dána souhlasem, ne požadavkem.

GET /api/v1/documents
Authorization: Bearer iho_...

Token platí jednu hodinu. Když vyprší, je odvolaný, je odvolaný souhlas nebo je aplikace vypnutá, vrátí API 401 se stejnou hláškou Invalid or expired token — rozlišit se to schválně nedá.

Obnova tokenu

POST /api/v1/oauth/token
Content-Type: application/json

{
  "grant_type": "refresh_token",
  "client_id": "ihc_...",
  "client_secret": "ihs_...",
  "refresh_token": "ihr_..."
}

Odpověď má stejný tvar jako výměna kódu. Refresh token se ale rotuje: starý pár okamžitě zneplatníme a vy si musíte uložit ten nový z odpovědi. Použití už rotovaného refresh tokenu je pro nás signál krádeže a odvolá celý souhlas — uživatel pak musí přístup povolit znovu.

Rozsah nového tokenu ořízneme aktuálním souhlasem: zúžil-li ho uživatel mezitím, dostanete v odpovědi menší scope.

Odvolání přístupu

Odvolat se dá ze dvou stran.

Vy — když uživatel u vás integraci vypne, zahoďte token i u nás (RFC 7009):

POST /api/v1/oauth/revoke
Content-Type: application/json

{
  "client_id": "ihc_...",
  "client_secret": "ihs_...",
  "token": "iho_..."
}

Přijímáme access i refresh token. Odpověď je vždycky 200, i pro token, který jsme nenašli — z vašeho pohledu je výsledek stejný.

Uživatel — v Nastavení → API a integrace → OAuth aplikace → Povolené přístupy tlačítkem Odvolat přístup. Zneplatní se tím okamžitě všechny vydané tokeny, ne až vyprší poslední z nich.

Kvóty požadavků

Každé credentials — API klíč i OAuth aplikace — mají měsíční limit požadavků podle tarifu firmy. Limit je na jedny credentials, ne na celou firmu: dvě integrace se o něj nedělí.

TarifPožadavků / měsíc / integrace
Free1 000
Basic25 000
Pro250 000
AIbez omezení

Okno je kalendářní měsíc v UTC: počítadlo se vynuluje prvního dne v měsíci v 00:00 UTC. Aktuální stav vidí zákazník v aplikaci na obrazovce Nastavení → API a integrace, pruhem u každého klíče a každé aplikace zvlášť.

Do kvóty se počítají jen úspěšně autentizované požadavky. Volání s neplatným tokenem zákazníkovi z limitu neubere.

Hlavičky X-RateLimit-*

Na každé odpovědi autentizované klíčem nebo OAuth tokenem — ne až když je zle — chodí:

  • X-RateLimit-Limit – měsíční limit, nebo unlimited.
  • X-RateLimit-Remaining – kolik zbývá po započtení této odpovědi.
  • X-RateLimit-Used – kolik se jich už spotřebovalo.
  • X-RateLimit-Reset – unixový čas (v sekundách), kdy se počítadlo vynuluje.
HTTP/1.1 200 OK
X-RateLimit-Limit: 250000
X-RateLimit-Remaining: 249993
X-RateLimit-Used: 7
X-RateLimit-Reset: 1788220800

Po vyčerpání limitu vrací API až do konce období:

HTTP/1.1 429 Too Many Requests
Retry-After: 183600

{
  "error": "Vyčerpali jste měsíční kvótu požadavků API pro váš tarif.",
  "code": "RATE_LIMIT_EXCEEDED",
  "limit": 250000,
  "used": 250001,
  "resetAt": 1788220800,
  "upgrade": true
}

Nezkoušejte to znovu hned. Retry-After u měsíční kvóty ukazuje na konec období, ne na pár sekund — opakování ve smyčce nepomůže. Řešením je vyšší tarif, nebo méně volání (načítejte po stránkách a kešujte číselníky).

Chybové kódy

Endpointy protokolu (/oauth/token, /oauth/revoke, /oauth/authorize) vracejí tvar podle RFC 6749 — errorerror_description, ne obvyklý tvar chyby InvoiceHub.

KódCo se stalo
invalid_requestchybí povinný parametr, nesedí redirect_uri, nebo veřejný klient nepoužil PKCE
invalid_clientneznámé client_id, špatné tajemství, nebo vypnutá aplikace
invalid_grantkód nebo refresh token neplatí, vypršel, už byl použit, nebo neodpovídá code_verifier
invalid_scopežádný z požadovaných scopes není pro aplikaci povolený
unsupported_grant_typejiný grant než authorization_code a refresh_token
access_denieduživatel přístup nepovolil, nebo na požadované scopes nemá právo

Checklist integrace

  • Návratová adresa na https, zaregistrovaná doslova tak, jak ji používáte.
  • state generovaný náhodně a ověřený po návratu.
  • Veřejný klient používá PKCE se S256.
  • client_secret jen na serveru, nikdy v kódu, který vidí prohlížeč.
  • Po každé obnově uložit nový refresh token — starý už neplatí.
  • Řídit se polem scope z odpovědi, ne tím, o co jste žádali.
  • Sledovat X-RateLimit-Remaining a při 429 počkat do resetAt.
  • Při odpojení uživatele u vás zavolat /oauth/revoke.