API reference

The read-model REST API answers what the service knows about Rubin objects and which catalog sources sit at their positions: by diaObjectId, by sky position, by TNS name, or for objects with alerts in a recent time window. Responses are JSON.

Machine-readable docs

Endpoints

Operations (details in the OpenAPI document)
Request Operation Summary
GET /openapi.json get_openapi This OpenAPI document.
GET /api/objects/{diaObjectId} get_object Look up one Rubin object by diaObjectId.
POST /api/lookup lookup_objects Look up a list of tagged inputs in one request.
GET /api/cone cone_search Search one position for every Rubin object within a radius.
GET /api/tns/{name} resolve_tns Look up a TNS name as a position search around it.
GET /api/status service_status Service availability, version, and TNS-resolution availability.
GET /api/describe describe_service Catalogs, vocabulary, and per-request limits.
GET /api/recent-crossmatches recent_crossmatches Crossmatches for objects with alerts in a time window.

Service configuration

Read from the running service (version 0.15.0); every successful response repeats it in its provenance block.

A coincident source is a catalog source within the crossmatch radius of the object position; it is not a host association. The host galaxy of an offset supernova is generally not a coincident source, so the absence of a coincident source is not evidence of a hostless transient.

"Not in this service" (status not_in_service, or no_rubin_object for a position) is not evidence that the object failed a reliability cut or does not exist in Rubin: reliability filtering happens before an alert is stored (at the broker, or at ingest for the cut this service enforces; see reliability_cuts), and the service holds only alerts its brokers delivered.

Recent crossmatches

GET /api/recent-crossmatches

Returns recent crossmatches for objects that had an alert in a time window, grouped by object, at a caller-selected detail level. Matches-only: an object with no catalog match is not listed.

Results are keyset (cursor) paged, newest first. Follow the top-level next_cursor to page through the whole window; it is null when the window is exhausted. The first page sets as_of and its cursors carry it, so one walk lists only alerts ingested at or before that time. It bounds ingest time rather than taking a snapshot: an alert whose ingest commits while you page can still appear on a later page if its ingest time is at or before as_of. The pin fixes which alerts are in the set, not their crossmatch state: an alert that gains its first match while you page can appear on a later page. A walk never repeats an object.

This endpoint does not filter or count: a filter parameter or response returns a 400 unsupported_parameter error; other unknown parameters are ignored. It runs within the 5.0 s request budget, so a large page at detail=full can return a 400 query_too_expensive; request a smaller page_size if it does.

Query parameters

All parameters are optional
Parameter Values Description
start ISO-8601 timestamp (UTC) Window start. Defaults to end minus 12 hours. The window may span at most 168 hours.
end ISO-8601 timestamp (UTC) Window end. Defaults to now.
time_field ingest_time (default), event_time Which alert timestamp the window filters on: arrival time or observation time.
detail ids, position, matches (default), full Cumulative detail level per object (see below).
page_size positive integer Objects per page (default 1000), clamped down to the server maximum of 10000.
cursor opaque token A next_cursor from a prior page. It pins start, end, time_field, and detail; supplying any of those with a conflicting value is a 400.

Detail levels

Detail levels are cumulative, each adding to the one before it:

Response envelope

Every successful response wraps the objects with the service provenance and the resolved query metadata:

{
  "provenance": {
    "service_version": "...",
    "contract_version": "...",
    "crossmatch_radius_arcsec": ...,
    "catalogs": [ ... ],
    "reliability_cuts": [ ... ]
  },
  "window": {"start": "...", "end": "..."},
  "time_field": "ingest_time",
  "detail": "matches",
  "page_size": 1000,
  "count": 2,
  "next_cursor": null,
  "objects": [ ... ],
  "as_of": "..."
}

count is the number of objects on this page (not a whole-set total). objects contains one entry per matched diaObjectId (objects with no match are omitted). provenance and as_of were added without changing any earlier field; ignore keys your client does not know.

Example request

curl "https://example.org/api/recent-crossmatches?detail=matches&page_size=2"

Success (200)

{
  "provenance": {
    "service_version": "0.15.0",
    "contract_version": "1.0.0",
    "crossmatch_radius_arcsec": 1.0,
    "catalogs": [{"name": "gaia_dr3", "release": "Gaia DR3"}, {"name": "des_y6_gold", "release": "DES Y6 Gold"}, {"name": "delve_dr3_gold", "release": "DELVE DR3 Gold"}, {"name": "skymapper_dr4", "release": "SkyMapper DR4"}],
    "reliability_cuts": [ ... ]
  },
  "window": {"start": "2026-08-10T00:00:00+00:00", "end": "2026-08-10T12:00:00+00:00"},
  "time_field": "ingest_time",
  "detail": "matches",
  "page_size": 2,
  "count": 1,
  "next_cursor": "eyJ0MCI6IC4uLn0=",
  "objects": [
    {
      "diaObjectId": 1234567890123456789,
      "diaObjectId_str": "1234567890123456789",
      "ra": 150.113,
      "dec": 2.205,
      "matches": [
        {
          "catalog_name": "gaia_dr3",
          "catalog_source_id": "987654321098765432",
          "separation_arcsec": 0.42
        }
      ]
    }
  ],
  "as_of": "2026-08-10T12:00:03.123456+00:00"
}

Error — invalid parameter (400)

An unparseable timestamp, unknown detail/time_field, non-positive page_size, malformed or conflicting cursor, or an out-of-range window returns a 400 with a structured JSON error body. Branch on code; error keeps the original text and equals message:

{
  "error": "detail must be one of ('ids', 'position', 'matches', 'full'), got 'everything'",
  "code": "invalid_parameter",
  "message": "detail must be one of ('ids', 'position', 'matches', 'full'), got 'everything'",
  "param": "detail",
  "retryable": false
}

The same body shape carries unsupported_parameter (a filter or response parameter) and query_too_expensive (over the request budget), both 400 and not retryable.

Error — wrong method (405)

Only GET is supported. Any other method returns a 405:

{
  "error": "method not allowed",
  "code": "method_not_allowed",
  "message": "method not allowed",
  "retryable": false
}

Error — service unavailable (503)

When the service database cannot be reached, the response is a 503 with a Retry-After header; retry after that many seconds:

{
  "error": "The service database is temporarily unavailable; retry later.",
  "code": "service_unavailable",
  "message": "The service database is temporarily unavailable; retry later.",
  "retryable": true
}