sinio — Python
Official client for the sinio.ai ingestion API. No runtime dependencies (stdlib
urllib), Python 3.10+, fully typed (mypy --strict clean).
pip install sinio
Quick start
from sinio import SinioClient, Event, EventRoles
sinio = SinioClient(
api_key=os.environ["SINIO_API_KEY"],
base_url="https://api.sinio.ai",
)
result = sinio.send_events([
Event(
source_event_id="evt_88213",
type="badge_in",
occurred_at="2026-08-28T07:58:12+02:00",
roles=EventRoles(device="BORNE-A12", subject="P-4582", location="SITE-A.HALL"),
payload={"badge": "04A2B9C1", "reader_firmware": "2.4.1"},
),
])
# The four roles (device, subject, location, actor) are read from `roles` only;
# any other key is refused. `payload` is free and optional ({} by default).
print(result.accepted, result.duplicates, result.rejections)
Plain dicts work too, and occurred_at accepts a timezone-aware datetime:
sinio.send_events([
{"source_event_id": "evt_1", "type": "badge_in",
"occurred_at": datetime.now(timezone.utc), "roles": {"subject": "04A2B9C1"}},
])
What the client does for you
- Chunking. Pass 50 000 events; the SDK splits them into calls of 500 (the API limit) and merges the counters and rejections into one result.
- Retries. 429, 5xx and dropped connections are retried with exponential
backoff and full jitter, honouring
Retry-After. This is safe because a resend with the samesource_event_idand the same content is counted induplicates, never stored twice. - Local validation. What the API would refuse raises
SinioValidationErrorbefore any call, naming the field and its index: akind,relationormodeoutside its vocabulary, a missing or empty required field, an unknown role, apayloadthat is not an object, a naivedatetimeor a timestamp without an offset, avalid_tonot aftervalid_from. - Typed errors.
SinioAuthError(401/403, do not replay),SinioRequestError(400/404/413, fix the request),SinioRateLimitError(429, carriesretry_after_seconds),SinioServerError(5xx, replayable),SinioConnectionError,SinioValidationError.
Partial acceptance
A 200 means the batch was received, not derived — derivation is asynchronous.
Within a batch, valid events are accepted even when others are rejected, so always
read rejections:
for rejection in result.rejections:
log.warning("%s %s %s", rejection.source_event_id, rejection.code, rejection.message)
duplicates and revisions are successes, not failures.
Every closed vocabulary is exported, as a Literal type and the tuple of its values:
EntityKind / ENTITY_KINDS, LocationLevel / LOCATION_LEVELS, IngestionMode /
INGESTION_MODES, AssignmentRelation / ASSIGNMENT_RELATIONS, EventRole / EVENT_ROLES,
RejectionCode / REJECTION_CODES. A rejection's code stays a str, since the API may add
codes; known_code narrows it, or is None for a code newer than the SDK:
from sinio import ENTITY_KINDS
assert "device" in ENTITY_KINDS
if rejection.known_code == "unknown_location":
sinio.upsert_locations(missing_locations)
Reference data
from sinio import Assignment, Entity, Location
sinio.upsert_locations([
Location(external_id="SITE-A", kind="site", name="Site A"),
Location(external_id="SITE-A.HALL", kind="zone", name="Hall", parent="SITE-A"),
])
sinio.upsert_entities([
Entity(external_id="BORNE-A12", kind="device", subkind="nfc_kiosk",
location="SITE-A.HALL", attributes={"firmware": "2.4.1"}),
])
sinio.create_assignments([
Assignment(credential="04A2B9C1", subject="P-4582", relation="assigned_to",
valid_from="2026-01-01T00:00:00+01:00"),
])
The reference is never a condition of acceptance: an event pointing at an unknown device is still accepted. Upserting is how you enrich it, not how you gate it.
Backfill
sinio.backfill_events(history)
mode="backfill" persists history without raising alerts; once it has arrived, the
project's processes replay it in a canonical order. Live catch-up after an outage should stay on
send_events — the declared mode decides alerting, not the age of the events.
Webhook key
For systems that can only be pointed at a URL and cannot set a header:
sinio = SinioClient(webhook_key=os.environ["SINIO_WEBHOOK_KEY"])
sinio.send_events(events) # POST /v1/webhooks/<key>
In this mode only event ingestion is available; the reference endpoints require an API key.
Bring your own HTTP client
sender replaces the stdlib transport — useful in a project that already depends
on requests or httpx, or to stub the network in tests:
from sinio import HttpResponse
def httpx_sender(url, payload, headers, timeout):
response = httpx.post(url, content=payload, headers=headers, timeout=timeout)
return HttpResponse(response.status_code, dict(response.headers), response.text)
sinio = SinioClient(api_key, sender=httpx_sender)
Development
python -m venv .venv && .venv/bin/pip install -e ".[dev]"
.venv/bin/python -m pytest
.venv/bin/python -m mypy
.venv/bin/python -m ruff check .
Metadata
Release files for sinio 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| sinio-0.1.0.tar.gz | 18.2 kB | With provenance | Details for sinio-0.1.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| sinio-0.1.0-py3-none-any.whl | Python 3 | none | any | Details for sinio-0.1.0-py3-none-any.whl |
Total release size: 35.3 kB
Release files / sinio-0.1.0.tar.gz
| Download URL | sinio-0.1.0.tar.gz |
|---|---|
| Size | 18.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
f692f0ed0cc5fccb7c86d11d25baec176121eab82eb539e541458994eda6af20
|
|
BLAKE2b-256 checksum How to use checksums |
7568b7abf8fd7796b016d2f2d9906e710e0d178bb367000ff6a4686d038635fd
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 28, 2026.
Transparency logRelease files / sinio-0.1.0-py3-none-any.whl
| Download URL | sinio-0.1.0-py3-none-any.whl |
|---|---|
| Size | 17.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
ecfa97456661764b986c54270ecf2a935a39c6def26bd1b9d0f74b90369b004c
|
|
BLAKE2b-256 checksum How to use checksums |
4bb83c8d8a844b75f378579d6c3ecbacf9e6be54cd0260246b9273f6591f58ad
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 28, 2026.
Transparency logSecurity for sinio 0.1.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for sinio-0.1.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 28, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.