Metadata-Version: 2.5
Name: ciao-client
Version: 0.1.1
Summary: Python client for the Belgian social security Check In and Out at Work (CIaO) presenceRegistration REST service
Project-URL: Homepage, https://github.com/elektriciteit-steen/ciao-client
Project-URL: Repository, https://github.com/elektriciteit-steen/ciao-client
Author-email: Steen Elektriciteit <dev@steen.be>
License-Expression: MIT
License-File: LICENSE
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Requires-Python: >=3.11
Requires-Dist: cryptography>=41.0.0
Requires-Dist: pyjwt>=2.6.0
Requires-Dist: requests>=2.28.0
Provides-Extra: dev
Requires-Dist: pre-commit>=3.6.0; extra == 'dev'
Requires-Dist: pytest-cov>=4.0.0; extra == 'dev'
Requires-Dist: pytest>=7.0.0; extra == 'dev'
Requires-Dist: responses>=0.23.0; extra == 'dev'
Requires-Dist: ruff>=0.1.0; extra == 'dev'
Description-Content-Type: text/markdown

# ciao-client

Python client for the Belgian social security **Check In and Out at Work
(CIaO)** `presenceRegistration` REST service: presence submission
(`registerInBulk`), consultation (read/search) and the referential-data
catalogs, authenticated with OAuth 2.0 client credentials over a
certificate-signed JWT assertion.

Built for the Steen Elektriciteit werf-compliance stack
(`l10n_be_workplace*` in odoo-steen-addons, ADR-0039), usable from any
Python 3.12+ environment.

## Usage

```python
from ciao_client import CiaoClient

client = CiaoClient(
    client_id="<chaman-client-id>",
    private_key=open("chaman-key.pem", "rb").read(),
    environment="simulation",   # or "production"
)

client.test_connection()        # token + one refData read

page = client.search({
    "registrationDate": {
        "startDate": "2027-04-01T00:00:00+02:00",
        "endDate": "2027-04-02T00:00:00+02:00",
    },
    "contractualRelationshipReference": "1Y1003SQ5VSSZ",
})
for presence in page.items:
    print(presence.id, presence.type, presence.validity, presence.masked_ssin)
```

## Environments and authentication

| Environment | Service endpoint |
|---|---|
| simulation | `https://services-sim.socialsecurity.be/REST/presenceRegistration/v1` |
| production | `https://services.socialsecurity.be/REST/presenceRegistration/v1` |

There is exactly **one OAuth server** (production); it issues the tokens for
both environments. Tokens live 10 minutes and are cached; a fresh one is
fetched when less than a minute of validity remains, per the RSZ's guidance.
Onboarding (employer identification, certificate, Chaman REST channel with
the "Check in And Out at Work" permission) is described at
<https://www.rest-documentation.socialsecurity.be/>.

Scopes: consultation accepts `...check-in-and-out-work-rest:professional`
and `:enterprise`; `registerInBulk` requires `:enterprise`.

## What this library deliberately does not do

- **Policy.** CIaO requires the *worker* to drive a registration, in real
  time; `registerInBulk` refuses submissions more than 10 minutes after the
  act. This library shapes and transports payloads; whether a submission is
  allowed is the caller's rule (see ADR-0039 in odoo-steen-addons).
- **Secrets management.** The Chaman client id and private key are
  constructor arguments; nothing is read from files, env vars or keyrings,
  and nothing is persisted. The caller owns the secrets.
- **Logging payloads.** Requests and responses carry INSZ numbers, so no
  exception message or repr ever contains a body or an SSIN
  (`PresenceRegistration.ssin` is repr-excluded; use `masked_ssin`).

## What it validates, and what it keeps unused

A `PresenceSubmission` is checked at construction against the format rules of
the OpenAPI: an 11-digit SSIN, `type` in or out, a 13-character
`contractualRelationshipReference` without I or O, an enterprise number of 10
digits starting with 0 or 1, and exactly one identifier on `Employer` and
exactly one of coordinates or address on `PlaceOfWork`. A malformed payload
fails here with a readable `ValueError` instead of as a 400 from the RSZ, and
the SSIN message is a fixed sentence so the number never lands in a log. What
the service sends *back* is never re-judged: `from_api` mirrors it as is.

The write path (`register_in_bulk`, `PresenceSubmission`, `Employer`,
`PlaceOfWork`) has no caller today and is kept on purpose. The Odoo side is
consult-only: the worker registers through the RSZ's own channels and Odoo
reads the result (ADR-0039 in odoo-steen-addons, revision of 2026-09-11).
CNT/NAR advies 2.465 asks for solutions for workers on the wrong side of the
digital divide, and the draft KB is reported to define an "alternative
registration method". If that turns out to be employer-assisted registration,
this is the code that answers it. Do not prune it as dead, and do not read
its presence as a decision to use it.

## Development

```bash
pip install -e ".[dev]"
pytest
ruff check src tests
```

Test fixtures under `tests/fixtures/` are hand-built from the RSZ's own
published examples (REST handleiding v1.3 pp. 18-29) and the OpenAPI v1.4.1
spec. Once simulation access exists, recorded sim responses (SSINs scrubbed)
should join or replace them.

## Sources

- REST handleiding: <https://www.socialsecurity.be/site_nl/employer/applics/check-in-and-out-at-work/documents/pdf/handleiding_ciao-rest_nl_1.7.pdf>
- OpenAPI v1.4.1: linked from <https://www.socialsecurity.be/site_nl/employer/applics/check-in-and-out-at-work/general/how-to-register.htm>
- Token contract: <https://www.rest-documentation.socialsecurity.be/documentation/developer-guide/obtaining-an-access-token-from-social-security-oauth-server.html>
