Errors

sinio.ai separates two kinds of failure, and the distinction drives everything on this page: a status code is about the call, a rejection code is about one event inside an otherwise successful call.

Status codes

CodeMeaningWhat to do
200Batch processed — read the countersRead rejections
400Malformed body, an unknown field, or more than 500 itemsFix the request
401Key missing, invalid or revokedDo not retry
403Key lacks the required scopeDo not retry
404Unknown resourceFix the request
413Request body over 10 MBSplit the batch
429Rate limitedBack off and retry
5xxTemporarily unavailableReplay identically

The ingestion and reference-data routes — everything a key authenticates — are not rate-limited today, so they do not return 429; the code is listed so that your client handles it if limits are introduced.

A 200 is the interesting one: it means the batch was received, not that every event in it was accepted, and not that anything has been derived yet.

On /v1/events and the webhook URL, an unknown field — in the body, in an event or in its roles — is a 400 for the whole call (events.0.property foo should not exist for a key inside the first event), never dropped in silence. Only the content of payload is free.

Rejection codes

Rejections come back inside a 200, one entry per event, each with a code and a message. The rest of the batch is unaffected.

Ingesting events

  • Name
    invalid_occurred_at
    Type
    code
    Description

    The timestamp is not ISO 8601 with a UTC offset, written YYYY-MM-DDThh:mm:ss (seconds required, fraction optional) followed by Z or ±hh:mm. 2026-08-28T07:58:12 and 2026-08-28T07:58+02:00 are rejected; 2026-08-28T07:58:12+02:00 and 2026-08-28T05:58:12Z are accepted. A timestamp without an offset is not a moment, and guessing one would silently corrupt every duration derived from it. Message: occurred_at must be ISO 8601 with an offset, got: "2026-08-28T07:58:12".

  • Name
    payload_too_large
    Type
    code
    Description

    That event's payload exceeds 256 KB. The others in the batch still land.

  • Name
    missing_required_field
    Type
    code
    Description

    An event type's interpretation marks the subject or the actor as required and the event did not carry it. Roles are optional by default: only an explicit required: true produces this.

  • Name
    trial_ended_quota
    Type
    code
    Description

    The organization's trial has ended without a subscription. Until it subscribes, sinio.ai keeps up to 5,000 events a month and analyses none; beyond that, events are rejected with this code until the next month or the subscription. A subscribed organization is never rejected for volume: its overage is counted. Retrying does not help.

Reference data

  • Name
    unknown_location
    Type
    code
    Description

    On /v1/entities: the location does not exist yet. Declare it first with POST /v1/locations.

  • Name
    unknown_parent_location
    Type
    code
    Description

    On /v1/locations: the parent does not exist yet. Send parents before children.

  • Name
    invalid_validity_period
    Type
    code
    Description

    On /v1/assignments: valid_from or valid_to is not an ISO 8601 date-time, or valid_to is not after valid_from.

  • Name
    overlapping_assignment
    Type
    code
    Description

    On /v1/assignments: this credential already has an assignment covering part of that window. Refused by a database exclusion constraint, so it holds under any race condition.

Two things that look like errors and are not

  • Name
    pending_classification
    Type
    counter
    Description

    The event's type is not known to the project yet. It is stored, with its payload intact, and derived once the type is given a meaning. This is how onboarding is meant to work — rejecting it would drop valid traffic and make a healthy integration look broken.

  • Name
    unmapped_event_type
    Type
    finding
    Description

    The type exists but no episode definition uses it yet. Accepted, stored, not derived, and reported as a data_quality finding for someone to look at.

Neither is returned in rejections, and neither should be treated as a failure by your integration. pending_classification is a counter in the response; unmapped_event_type shows up in the console, not in the ingestion reply.

Retry policy

What to retry

429, 5xx, dropped connection  → retry with exponential backoff + jitter
                                honour Retry-After if a response carries one
400, 401, 403, 404, 413       → do not retry, fix and resend
200 with rejections           → do not retry the batch, fix those events

Retrying is always safe: within a project and a source, an event resent with the same source_event_id and content identical to any revision already stored is counted in duplicates and stored once — see Batching & idempotency. The official SDKs implement this policy already, and expose it as typed errors:

  • Name
    SinioAuthError
    Type
    401 / 403
    Description

    Do not replay. Fix the key.

  • Name
    SinioRequestError
    Type
    400 / 404 / 413
    Description

    Fix the request.

  • Name
    SinioRateLimitError
    Type
    429
    Description

    Carries retryAfterSeconds (retry_after_seconds in Python), from a Retry-After header when there is one.

  • Name
    SinioServerError
    Type
    5xx
    Description

    Replayable.

  • Name
    SinioConnectionError
    Type
    network
    Description

    Replayable.

  • Name
    SinioValidationError
    Type
    local
    Description

    Caught before the call — a timestamp with no offset, for instance.

PHP uses AuthException, RequestException, RateLimitException, ServerException, ConnectionException and ValidationException, all extending SinioException; the API's rejection code sits on $exception->errorCode, since $code belongs to \Exception.

Was this page helpful?