Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Defining Notification Schemas

A notification schema describes the shape of an event stream: what identifier fields are accepted, how they are validated, how the storage topic is constructed, whether a payload is required, and who can read or write.

Schemas are defined under the notification_schema key in your configuration file. Each top-level key becomes an event type that clients reference when calling /api/v1/notification, /api/v1/watch, or /api/v1/replay.

notification_schema:
  my_event: # ← event type name
    topic: ...
    identifier: ...
    payload: ...
    auth: ... # optional
    storage_policy: ... # optional, JetStream only

Topic Configuration

A schema should have a topic block that tells Aviso how to build the NATS subject for storage and routing.

topic:
  base: "weather"
  key_order: ["region", "date"]
FieldDescription
baseRoot prefix for the subject. Must be unique across all schemas (case-insensitive).
key_orderOrdered list of identifier field names appended to the base, separated by ..

Given base: "weather" and key_order: ["region", "date"], a request with region=north and date=20250706 produces the subject:

weather.north.20250706

Values containing reserved characters (., *, >, %) are automatically percent-encoded so they do not interfere with NATS subject routing. See Topic Encoding for details.

Only fields listed in key_order contribute to the subject. Other identifier fields are validated but not part of the topic.


Identifier Fields

The identifier map defines the fields that clients can send. Each field specifies a handler type that controls validation and canonicalization.

identifier:
  region:
    type: EnumHandler
    values: ["north", "south", "east", "west"]
    required: true
    description: "Geographic region."
  date:
    type: DateHandler
    required: true

Every field supports these common properties:

PropertyTypeDescription
typestringHandler type (see below). Required.
requiredboolAffects watch and replay only: if true, those requests must include this field; if false, missing keys become wildcards. Has no effect on notify, which always requires every declared field. Required.
descriptionstringHuman-readable text exposed by GET /api/v1/schema. Optional.

PointCloudHandler is operation-specific. Publishers provide the declared point_cloud field. Watch and replay requests provide a closed polygon instead. That polygon satisfies required: true; subscribers must not send point_cloud.

Handler Types

StringHandler

Accepts any non-empty string. No transformation.

class:
  type: StringHandler
  max_length: 2 # optional: reject strings longer than this
  required: true

DateHandler

Parses dates in multiple formats and canonicalizes to a configured output format.

Accepted inputs: YYYY-MM-DD, YYYYMMDD, YYYY-DDD (day-of-year).

date:
  type: DateHandler
  canonical_format: "%Y%m%d" # output format (default: "%Y%m%d")
  required: false
canonical_formatExample output
"%Y%m%d"20250706
"%Y-%m-%d"2025-07-06

Invalid dates (e.g. February 30) are rejected.

TimeHandler

Parses times and canonicalizes to four-digit HHMM format.

Accepted inputs: 14:30, 1430, 14, 9:05.

time:
  type: TimeHandler
  required: false

Input 14:30 → stored as 1430. Input 9 → stored as 0900.

EnumHandler

Accepts one value from a predefined list. Matching is case-insensitive; stored in lowercase.

domain:
  type: EnumHandler
  values: ["a", "b", "c"]
  required: false

Input "A" → stored as "a". Input "x" → rejected.

IntHandler

Accepts integer strings. Strips leading zeros for canonical storage.

step:
  type: IntHandler
  range: [0, 100000] # optional: inclusive [min, max] bounds
  required: false

Input "007" → stored as "7". Input "-1" with range: [0, 100] → rejected.

FloatHandler

Accepts floating-point strings. Rejects NaN and Inf.

severity:
  type: FloatHandler
  range: [0.0, 10.0] # optional: inclusive [min, max] bounds
  required: false

Input "3.14" → stored as "3.14". Input "NaN" → rejected.

ExpverHandler

Experiment version handler. Numeric values are zero-padded to four digits; non-numeric values are lowercased.

expver:
  type: ExpverHandler
  default: "0001" # optional: used when the field is empty
  required: false

Input "1" → stored as "0001". Input "test" → stored as "test".

PolygonHandler

Accepts a closed polygon as a JSON array of [lat,lon] pairs. Legacy coordinate strings remain accepted at the HTTP boundary. The first and last pair must be identical.

Canonical form: [[lat,lon],...,[lat,lon]]. Emitted CloudEvents always use this array form.

polygon:
  type: PolygonHandler
  required: true

Constraints: at least three coordinate pairs plus the closing repeat. Latitude must be in [-90, 90]; longitude must be in [-180, 180].

PointCloudHandler

Accepts a non-empty JSON array of [lat,lon] pairs from providers. Point clouds do not have a string syntax and do not need a closing point. Duplicates are valid. Their order is preserved.

point_cloud:
  type: PointCloudHandler
  required: true
  max_points: 10000
  description: >-
    Publishers provide point_cloud as [[latitude, longitude], ...]. Watch and
    replay requests provide a closed polygon instead. The polygon satisfies
    this required field; subscribers must not send point_cloud.

max_points defaults to 10,000 and cannot exceed 10,000. Canonical point-cloud JSON is also limited to 60 KiB. This is a conservative Aviso interoperability limit informed by NATS-backed header transport and near-limit round-trip tests. It leaves room for Aviso’s other metadata, but is not a protocol-wide NATS header limit. The point-count check runs first.

The handler must use the reserved point_cloud identifier key. Only one is allowed per schema, and the schema must define a topic block. Do not put point_cloud in topic.key_order; startup rejects it. Aviso stores the cloud in spatial metadata instead of the subject.

Point-cloud subscribers use polygon on /watch and /replay. A valid polygon satisfies a required point_cloud field for those operations. A schema with a PointCloudHandler cannot declare polygon, since that key is reserved for the query-time filter.

See Spatial Filtering for usage examples.

Reserved Query-Time Fields

point (built-in)

The point field is a reserved identifier that clients can send on /watch or /replay to filter notifications whose polygon contains the point. Canonical form is [lat,lon]; compatible lat,lon strings are also accepted.

point is not a schema-configurable handler. It is available on schemas that include a PolygonHandler. The /notification endpoint rejects it.

See Spatial Filtering for usage examples.


Payload Configuration

Controls whether requests must include a payload field.

payload:
  required: true
requiredBehavior
trueRequests without a payload are rejected (400).
falsePayload is optional; missing payloads are stored as JSON null.

The payload can be any valid JSON value (object, array, string, number, boolean, null). It is stored as-is with no reshaping.

See Payload Contract for full semantics.


Per-Stream Authentication

When global authentication is enabled, individual schemas can require credentials and restrict access by role.

auth:
  required: true
  read_roles:
    localrealm: ["analyst", "consumer"]
  write_roles:
    localrealm: ["producer"]
FieldDefault when omittedEffect
required(none)Must be set explicitly to true or false.
read_rolesAny authenticated user can readMaps realm → role list for watch/replay access.
write_rolesOnly admins can writeMaps realm → role list for notify access.

Use ["*"] as the role list to grant access to all users from a realm.

Admins (users matching global admin_roles) always have both read and write access.

Omitting the entire auth block makes the stream publicly accessible, even when global auth is enabled.

See Authentication for the full access-control matrix and role-matching rules.


Storage Policy (JetStream Only)

When using the JetStream backend, you can configure per-stream retention limits.

storage_policy:
  retention_time: "7d"
  max_messages: 500000
  max_size: "2Gi"
  allow_duplicates: false
  compression: true
FieldTypeDescription
retention_timedurationDiscard messages older than this. Accepts 30m, 1h, 7d, 1w.
max_messagesintegerMaximum message count; oldest are discarded when exceeded.
max_sizesizeMaximum stream size. Accepts 100Mi, 1Gi, etc.
allow_duplicatesboolAllow duplicate message IDs. Default: backend-specific.
compressionboolEnable message-level compression. Default: backend-specific.

All fields are optional. Omitting storage_policy entirely uses backend defaults.

The in-memory backend does not support storage policies.


Complete Example

This example defines a weather alert stream with date/region routing, enum validation, optional payload, and role-restricted access.

notification_schema:
  weather_alert:
    payload:
      required: false

    topic:
      base: "alert"
      key_order: ["region", "severity_level", "date"]

    identifier:
      region:
        description: "Geographic region."
        type: EnumHandler
        values: ["europe", "asia", "africa", "americas", "oceania"]
        required: true
      severity_level:
        description: "Alert severity (1 to 5)."
        type: IntHandler
        range: [1, 5]
        required: true
      date:
        description: "Alert date."
        type: DateHandler
        canonical_format: "%Y%m%d"
        required: true
      issued_by:
        description: "Issuing authority identifier."
        type: StringHandler
        max_length: 64
        required: false

    auth:
      required: true
      read_roles:
        operations: ["*"]
      write_roles:
        operations: ["forecaster", "admin"]

    storage_policy:
      retention_time: "30d"
      max_messages: 100000

With this schema:

  • Publishing a notification with region=europe, severity_level=3, date=2025-07-06 produces the subject alert.europe.3.20250706.
  • The issued_by field is validated if present but does not appear in the subject (not in key_order).
  • Any authenticated user in the operations realm can watch/replay.
  • Only users with the forecaster or admin role can publish.
  • JetStream retains up to 100,000 messages or 30 days, whichever limit is hit first.

Tips

  • Start simple. Define only topic, one or two identifier fields, and payload. Add auth and storage policy later.
  • Use key_order deliberately. Fields in key_order become part of the NATS subject and affect routing granularity. More fields = more specific topics = more efficient filtering, but also more distinct subjects.
  • Mark routing fields required. If a field is in key_order, consider making it required: true so every notification produces a complete subject.
  • Keep base short and unique. It is the root of every subject in this stream. Avoid collisions with other schemas.
  • Test with GET /api/v1/schema/{event_type}. This endpoint returns the public view of your schema, showing all identifier fields and their validation rules.