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

Connect from a chat assistant

Ask Claude.ai or ChatGPT about Rubin transients by TNS name, diaObjectId, or sky position and get this service's answer in the chat, without writing code. The assistant calls three read-only tools on this service's MCP endpoint, which answer from the same lookups as the API. A lookup takes up to 100 identifiers and a position search at most a 60 arcsec radius; an answer summarizes at most 20 objects, and a larger result comes back as the first part plus a ready-to-run API request for the full set.

Connector URL: https://crossmatch.scimma.org/mcp

Custom connectors need a paid plan: Claude.ai Pro, Max, Team or Enterprise, or ChatGPT Plus, Pro, Business, Enterprise or Education (on the web).

Claude.ai

  1. In Settings, under Connectors, choose Add custom connector.
  2. Paste the connector URL, give it a name, and leave authentication empty; none is needed.
  3. Start a new conversation; the connector and its tools are available there.

ChatGPT

  1. In Settings, under Security and login (or Apps & Connectors), turn on Developer mode.
  2. Add a new app or connector with the connector URL and No Authentication.
  3. Start a new conversation in Developer mode and select the connector so its tools are available.

In a ChatGPT workspace, an admin must first allow Developer mode and custom MCP connectors. Menu labels in both apps change from time to time; look for Connectors or Developer mode in Settings.

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.16.2); 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.16.2",
    "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
}