Skip to content

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.
  • curl and jq for 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.

curl -s "${auth[@]}" "$SKYNET/me" | jq '{id, slug, username}'

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.

export OBS_ID=<id-from-response>

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