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
- OpenAPI 3.1 document — every operation, parameter, response field, status value, and error code, with units.
- llms.txt — the entry point for AI agents.
- This reference as Markdown, including every code and its meaning.
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
- In Settings, under Connectors, choose Add custom connector.
- Paste the connector URL, give it a name, and leave authentication empty; none is needed.
- Start a new conversation; the connector and its tools are available there.
ChatGPT
- In Settings, under Security and login (or Apps & Connectors), turn on Developer mode.
- Add a new app or connector with the connector URL and No Authentication.
- 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
| 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.
- Crossmatch radius: 1.0 arcsec. For each object and each catalog, the crossmatch keeps only the nearest catalog source within the crossmatch radius, so an object has at most one coincident source per catalog; other sources inside the radius are not reported. A coincident source is not a host association.
- Catalogs:
gaia_dr3(Gaia DR3;catalog_source_idis itssource_id),des_y6_gold(DES Y6 Gold;catalog_source_idis itsCOADD_OBJECT_ID),delve_dr3_gold(DELVE DR3 Gold;catalog_source_idis itsCOADD_OBJECT_ID),skymapper_dr4(SkyMapper DR4;catalog_source_idis itsobject_id). - Minimum LSST reliability per broker: antares 0.6 (enforced by broker); lasair 0.6 (enforced by broker); pittgoogle 0.6 (enforced by service).
- Per request: at most 1000 IDs, 200 positions and TNS names, a 60.0 arcsec radius, 100 objects per position and 2000 in all, within a 5.0 s budget.
- Rubin object positions (ra, dec, and searched positions) are ICRS right ascension and declination in degrees; radii and separations are in arcsec. A coincident source position is the catalog's own, in degrees, read from its coordinate columns (gaia_dr3: ra, dec; des_y6_gold: RA, DEC; delve_dr3_gold: RA, DEC; skymapper_dr4: raj2000, dej2000), in the frame and epoch the catalog publishes: the crossmatch compares positions as stored, with no proper motion applied and no epoch propagation. gaia_dr3 positions are at the reference epoch in the ref_epoch payload column (J2016.0 for Gaia DR3). skymapper_dr4 coordinate columns carry a J2000 suffix (raj2000, dej2000) and are published under those names; see the SkyMapper DR4 documentation for their epoch.
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
| 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:
ids— thediaObjectIdonly, as an integer and as the stringdiaObjectId_str.position— adds the object'sra/dec.matches— adds a summary per match (catalog, source id, separation).full— adds the complete published payload for each match.
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
}