# SudregEx
Elixir client for the **Croatian Court Register** (*Sudski registar*) public
open-data REST API served at <https://sudreg-data.gov.hr/>.
The portal exposes machine-readable data about Croatian legal entities (limited
companies, sole traders, institutions, cooperatives, …) — court of jurisdiction,
MBS, OIB, status, name, registered seat, share capital, legal form, activities,
proceedings, financial reports and more. `SudregEx` wraps all 39 v3 "javni"
(public) endpoints, handles OAuth2 auth and token caching, and adds pagination,
snapshot and formatting helpers.
## Installation
Add `:sudreg_ex` to your dependencies in `mix.exs`:
```elixir
def deps do
[
{:sudreg_ex, "~> 0.1"}
]
end
```
## Getting access
1. Register (free) at <https://sudreg-data.gov.hr/> and verify your e-mail to
obtain a `client_id` and `client_secret`.
> The generated credentials **end with two dots** (`..`) — that is part of the
> credential, not a display artifact. Pass them verbatim.
2. The API uses the **OAuth2 client-credentials flow**; `SudregEx` exchanges the
credentials for a bearer token (valid 6 hours) and caches it for you.
> **Getting `401 Unauthorized` on every data call?** The token endpoint can issue
> a token *before* your account is fully activated, but the data endpoints stay
> `401` until you **confirm your registration e-mail** — open the verification
> message and click **"Potvrdi"** (Confirm). After that the same credentials and
> token work. (So a working `SudregEx.Auth.fetch_token/1` plus 401s everywhere
> else almost always means an unconfirmed account, not bad credentials.)
A test environment mirrors production at <https://sudreg-data-test.gov.hr/>.
## Configuration
Provide credentials via application config (merged into every
`SudregEx.Client.new/1`):
```elixir
config :sudreg_ex, :client,
client_id: System.get_env("SUDREG_CLIENT_ID"),
client_secret: System.get_env("SUDREG_CLIENT_SECRET")
```
A token-cache process starts by default; disable it with
`config :sudreg_ex, start_token_cache: false` and call `SudregEx.Auth.fetch_token/1`
yourself. To point at the test environment, override the URLs on the client:
```elixir
SudregEx.Client.new(
base_url: "https://sudreg-data-test.gov.hr/api/javni",
token_url: "https://sudreg-data-test.gov.hr/api/oauth/token"
)
```
## Usage
```elixir
client = SudregEx.Client.new(client_id: "your-id..", client_secret: "your-secret..")
# one record type for all subjects (paginated)
{:ok, %SudregEx.Response{data: rows, total_count: total}} =
SudregEx.Api.subjekti(client, only_active: true, limit: 50)
# everything about one subject (the API caps this at 6/min — see Throttling)
{:ok, %SudregEx.Response{data: subject}} =
SudregEx.Api.detalji_subjekta(client, tip_identifikatora: "oib", identifikator: "12345678901")
# code tables (šifrarnici)
{:ok, %SudregEx.Response{data: countries}} = SudregEx.Api.drzave(client)
```
Each endpoint is a function on `SudregEx.Api` taking `(client, opts)`. `opts`
mixes the endpoint's query params (whitelisted per endpoint — an unsupported
param raises `ArgumentError`) with the pipeline opts `:token`, `:token_cache`,
`:format`. Booleans become `"1"`/`"0"`; `nil` params are dropped.
### Pagination
`SudregEx.Api.stream/3` lazily walks a paginated endpoint. Pin a snapshot so the
walk isn't split across the daily refresh:
```elixir
{:ok, snapshot_id} = SudregEx.Api.latest_snapshot_id(client)
SudregEx.Api.stream(client, :tvrtke, snapshot_id: snapshot_id)
|> Stream.map(& &1["ime"])
|> Enum.each(&IO.puts/1)
```
### Throttling `detalji_subjekta`
`detalji_subjekta` is rate-limited to 6 requests/minute for public users. The
optional `SudregEx.RateLimiter` enforces it client-side:
```elixir
{:ok, _} = SudregEx.RateLimiter.start_link(name: :sudreg_limiter)
:ok = SudregEx.RateLimiter.acquire(:sudreg_limiter)
SudregEx.Api.detalji_subjekta(client, tip_identifikatora: "mbs", identifikator: "080000014")
```
### Display helpers
Identifiers come back as numbers without leading zeros; timestamps carry no
timezone:
```elixir
SudregEx.Format.format_mbs(80_000_014) #=> "080000014"
SudregEx.Format.format_oib(12_345_678_901) #=> "12345678901"
SudregEx.Format.parse_timestamp("2026-06-15T00:00:00")
#=> {:ok, ~N[2026-06-15 00:00:00]}
```
### Results and errors
Success is `{:ok, %SudregEx.Response{}}` — `data` (the decoded rows) plus metadata
harvested from response headers (`snapshot_id`, `total_count`, `rows_returned`,
`log_id`, `seconds_elapsed`, `timestamp`). Failure is
`{:error, %SudregEx.Error{}}` carrying `http_status` and, for API JSON errors,
`error_code` / `error_message` / `log_id` (quote `log_id` when contacting support).
## Key API concepts
* **Identifiers** — subjects are keyed by **MBS** (9-digit court register number)
and **OIB** (11-digit tax number), returned as numbers *without* leading zeros
(use `SudregEx.Format`).
* **Snapshots** — data refreshes once per working day; each refresh is a numbered
snapshot. Pass `snapshot_id:` to keep multi-call reads consistent.
* **Two usage modes** — `detalji_subjekta` returns *everything* about one subject
in one call (rate-limited, not for bulk); the per-table endpoints return one
kind of record for *all* subjects (for building a local replica via `stream/3`).
* **Historisation** — rows carry `status` (1 active, 0 historical, 5 deleted,
8/9 inconsistent) and `prbu_od`/`prbu_do` (entry ordinals). Pass
`only_active: true` for just the currently-valid rows.
## Reference
The OpenAPI 3.0 spec for the public service ships with the package at
[`priv/doc/open_api_javni_v3.json`](priv/doc/open_api_javni_v3.json) (39
endpoints). The official developer guide (Croatian PDF) is committed in the
repository at `priv/doc/upute-za-razvojne-inzenjere-v3.0.0.pdf` and available on
the [portal](https://sudreg-data.gov.hr/).
## License
MIT — see [LICENSE](https://gitlab.com/banianitc/sudreg_ex/-/blob/master/LICENSE).