Quickstart — curl¶
The "list my telescopes, create an observation, watch it run" sketch. Raw HTTP, no SDK.
An observation is created in one POST from a complete spec — there's no
draft-then-publish handshake. For the full treatment (catalog/non-sidereal
targets, exposure sizing, asset download) see
Creating observations.
Prereqs¶
- An access token. See Auth for how to get one.
- The base URL for your environment. See Environments.
curlandjqfor pretty-printing.
For this walkthrough, set:
export SKYNET=https://api.skynetgo.org/v1
export TOKEN="<paste-your-access-token>"
auth=(-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json")
1. Who am I?¶
The /me endpoint returns the authenticated user record. Note your slug —
it's the entity in the create path.
If this fails with 401, your token is expired or doesn't match the environment. Refresh it (see Auth → Refreshing).
2. Telescopes you can submit to¶
curl -s "${auth[@]}" "$SKYNET/me/observing-access/telescopes" | jq '.[] | {id, name}'
export TELESCOPE_ID=<id-from-the-list>
3. A funding grant and the imager's filters¶
Pick an observing grant that funds the telescope, and read the telescope's optical imager + the filter ids you can request.
curl -s "${auth[@]}" "$SKYNET/observing-grants?telescopeId=$TELESCOPE_ID" | jq '.items[].id'
export GRANT_ID=<grant-id>
curl -s "${auth[@]}" "$SKYNET/users/<slug>/telescopes/$TELESCOPE_ID/detail" \
| jq '.instruments[] | select(.instrumentType=="opticalImager")
| {id, filters: [.filterWheels[].options[].filters[].id]}'
export IMAGER_ID=<instrument-id>
4. Create the observation¶
One POST carries the whole observation: the target, the imaging
configuration (tracking + tiling), the exposure requests, and the funding.
You do not send an owner id — it's derived from the {slug} in the path.
curl -s "${auth[@]}" -X POST "$SKYNET/users/<slug>/observations" -d '{
"name": "Quickstart M51",
"target": {
"name": "M51",
"position": {
"positionType": "fixed",
"coordinates": { "coordinateType": "equatorial", "raDeg": 202.4696, "decDeg": 47.1952 }
}
},
"opticalImagingConfiguration": {
"trackingMode": "sidereal",
"ditherStrategy": "none",
"temporalOffsetSec": 0.0,
"maxTiles": 1,
"tileOverlap": 0.0
},
"requests": [
{ "requestType": "opticalImaging", "order": 0,
"filterSpecifierIds": ["<filter-id>"], "exposureTimeSec": 60.0, "sampleCount": 1 }
],
"observingGrantIds": [<grant-id>],
"instrumentIds": [<imager-id>]
}' | jq '{id, name, status}'
The response includes the new observation's id (and uid). It's created
active and visible to the scheduler immediately.
If the call returns 4xx, the detail message names the failed check — most
commonly a missing grant, a snake_case field name (the wire format is
camelCase), or more than one exposure-sizing field set. See
Conventions and the
gotchas.
5. Watch it run¶
# Poll the observation
curl -s "${auth[@]}" "$SKYNET/observations/$OBS_ID" \
| jq '{status, progressFraction, lastActivityOn}'
# List the tasks the scheduler created from it
curl -s "${auth[@]}" "$SKYNET/observations/$OBS_ID/tasks" \
| jq '.items[] | {id, taskType, status, telescopeId}'
For a live view, subscribe to the relevant WebSocket stream — see WebSocket protocol.
Cleanup¶
If you were just testing, cancel the observation (drops any pending tasks):
curl -s "${auth[@]}" -X PATCH "$SKYNET/observations/$OBS_ID" \
-d '{ "status": "canceled" }' | jq '{id, status}'
Next steps¶
- Creating observations — catalog and non-sidereal (asteroid/satellite) targets, exposure sizing, and downloading the resulting frames.
- Conventions — pagination, errors, and discriminators.
- The TypeScript SDK or Python SDK for typed bindings instead of hand-rolled HTTP.
- WebSocket streams for realtime task progress.