This document describes the REST API for STDWeb, enabling programmatic access to astronomical image processing functionality.
/api/
The API uses token-based authentication. To obtain a token:
curl -X POST /api/auth/token/ \
-d "username=<username>&password=<password>"Response:
{"token": "your-auth-token-here"}Include the token in all subsequent requests:
curl -H "Authorization: Token <your-token>" /api/tasks/Session authentication is also supported for browser-based access.
GET /api/tasks/
Returns list of tasks for the current user (or all tasks for staff).
Response:
[
{
"id": 123,
"original_name": "image.fits",
"title": "My observation",
"state": "photometry_done",
"user": "username",
"created": "2024-01-15T10:30:00Z",
"modified": "2024-01-15T10:35:00Z"
}
]POST /api/tasks/
Create a new task with optional file upload.
Request (multipart/form-data):
file- FITS file to uploadoriginal_name- Filename (optional, derived from file if not provided)title- Optional title/commentconfig- JSON configuration objectpreset- Preset ID to apply (optional)
Example:
curl -X POST /api/tasks/ \
-H "Authorization: Token <token>" \
-F "file=@image.fits" \
-F 'config={"filter":"R","cat_name":"ps1"}'GET /api/tasks/{id}/
Response:
{
"id": 123,
"original_name": "image.fits",
"title": "My observation",
"state": "photometry_done",
"user": "username",
"created": "2024-01-15T10:30:00Z",
"modified": "2024-01-15T10:35:00Z",
"completed": "2024-01-15T10:35:00Z",
"config": {
"filter": "R",
"cat_name": "ps1",
"fwhm": 3.5,
"zp": 25.2,
...
},
"celery_id": null,
"path": "/path/to/tasks/123"
}PATCH /api/tasks/{id}/
or
POST /api/tasks/{id}/
Update task configuration.
Request:
{
"title": "Updated title",
"config": {
"filter": "V",
"cat_name": "gaiaedr3"
}
}DELETE /api/tasks/{id}/
Deletes the task and all associated files.
POST /api/tasks/{id}/duplicate/
Creates a copy of the task with all files.
POST /api/tasks/{id}/process/
Queue processing steps for the task.
Request:
{
"steps": ["inspect", "photometry", "subtraction"],
"config": {
"filter": "R"
}
}Available steps:
cleanup- Remove intermediate filesinspect- Image inspection, WCS refinement, cosmic ray maskingphotometry- Source extraction and photometric calibrationsimple_transients- Simple transient detection via catalog cross-matchingsubtraction- Template subtraction and candidate detection
Response:
{
"id": 123,
"state": "running",
"celery_id": "abc123-def456",
"steps": ["inspect", "photometry", "subtraction"]
}POST /api/tasks/{id}/cancel/
Cancel a running task and terminate all associated processes.
Response:
{
"id": 123,
"state": "cancelled",
"revoked_count": 3
}POST /api/tasks/{id}/fix/
Fix common FITS header issues.
POST /api/tasks/{id}/crop/
Request:
{
"x1": 100,
"y1": 100,
"x2": 900,
"y2": 900
}Negative values are interpreted as offsets from the edge.
POST /api/tasks/{id}/destripe/
Request:
{
"direction": "horizontal"
}Options: horizontal, vertical, both
GET /api/tasks/{id}/files/
Response:
[
{
"name": "image.fits",
"path": "image.fits",
"size": 16777216,
"modified": "2024-01-15T10:30:00Z",
"is_dir": false
},
{
"name": "objects.vot",
"path": "objects.vot",
"size": 45678,
"modified": "2024-01-15T10:35:00Z",
"is_dir": false
}
]GET /api/tasks/{id}/files/{path}
Returns the file as an attachment.
POST /api/tasks/{id}/files/{path}
Upload a file to the task directory.
Request (multipart/form-data):
file- The file to upload
Example:
curl -X POST /api/tasks/123/files/custom_mask.fits \
-H "Authorization: Token <token>" \
-F "file=@mask.fits"DELETE /api/tasks/{id}/files/{path}
Delete a file from the task directory.
GET /api/tasks/{id}/preview/{path}
Generate JPEG/PNG preview of a FITS file.
Query Parameters:
width- Image width in pixels (default: 800)format- Output format:jpegorpng(default: jpeg)quality- JPEG quality 1-100 (default: 80)ext- FITS extension (default: -1, last)cmap- Colormap (default: Blues_r)stretch- Stretch function:linear,log,sqrt,asinh(default: linear)qmin- Lower percentile for scaling (default: 0.5)qmax- Upper percentile for scaling (default: 99.5)r0- Smoothing radius in pixels (default: 0)
Example:
curl "/api/tasks/123/preview/image.fits?width=1024&stretch=log" \
-H "Authorization: Token <token>" > preview.jpgGET /api/tasks/{id}/cutout/{path}
Generate multi-panel visualization of STDPipe cutout files.
Query Parameters:
width- Image width in pixels (default: 800)format- Output format:jpegorpng(default: jpeg)quality- JPEG quality 1-100 (default: 80)
GET /api/queue/
List all active/pending/scheduled Celery tasks.
Response:
[
{
"id": "abc123-def456",
"name": "task_photometry",
"full_name": "stdweb.celery_tasks.task_photometry",
"state": "active",
"time_start": 1705312200.0,
"task_id": 123,
"task_name": "image.fits",
"chain_position": 2,
"chain_total": 4
}
]GET /api/queue/{celery_id}/
Response:
{
"id": "abc123-def456",
"state": "STARTED",
"ready": false,
"successful": null,
"failed": null,
"task_id": 123,
"task_name": "image.fits",
"chain_position": 2,
"chain_total": 4
}POST /api/queue/{celery_id}/terminate/
Response:
{
"id": "abc123-def456",
"task_id": 123,
"revoked_count": 3,
"state": "cancelled"
}Access files in the shared data directory.
GET /api/files/
GET /api/files/{path}
If path is a directory, returns file listing. If path is a file, downloads it.
GET /api/files/{path}/preview/
Generate preview of a FITS file in the data directory. Same query parameters as task preview.
GET /api/presets/
Response:
[
{
"id": 1,
"name": "Default Photometry",
"config": {
"filter": "R",
"cat_name": "ps1",
"sn": 5
}
}
]GET /api/presets/{id}/
No authentication required.
GET /api/reference/
Response:
{
"filters": ["U", "B", "V", "R", "I", "u", "g", "r", "i", "z", "y", "G", "BP", "RP"],
"catalogs": ["gaiadr3syn", "ps1", "skymapper", "sdss", "atlas", "gaiaedr3"],
"templates": ["custom", "ps1", "ls", "skymapper", "des", "decaps", "ztf"]
}GET /api/reference/filters/
Response:
{
"U": {"name": "Johnson-Cousins U", "aliases": []},
"B": {"name": "Johnson-Cousins B", "aliases": []},
"V": {"name": "Johnson-Cousins V", "aliases": []},
...
}GET /api/reference/catalogs/
GET /api/reference/templates/
The task config object accepts these parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
target |
string | - | Target name or coordinates |
time |
string | - | Observation time (ISO format) |
gain |
float | - | Detector gain (e/ADU) |
saturation |
float | - | Saturation level (ADU) |
mask_cosmics |
bool | true | Enable cosmic ray masking |
| Parameter | Type | Default | Description |
|---|---|---|---|
filter |
string | "R" | Photometric filter |
cat_name |
string | - | Reference catalog name |
cat_limit |
float | - | Catalog limiting magnitude |
sn |
float | 5 | Detection S/N threshold |
fwhm_override |
float | - | Force FWHM value (pixels) |
spatial_order |
int | 2 | Polynomial order for zero point |
use_color |
bool | true | Use color calibration term |
refine_wcs |
bool | true | Refine WCS with catalog |
blind_match_wcs |
bool | false | Perform blind WCS matching |
| Parameter | Type | Default | Description |
|---|---|---|---|
simple_skybot |
bool | false | Check SkyBoT asteroid catalog |
simple_blends |
bool | true | Reject blended objects |
simple_center |
string | - | Center position for search |
simple_sr0 |
float | - | Search radius (degrees) |
| Parameter | Type | Default | Description |
|---|---|---|---|
template |
string | - | Template source name |
subtraction_method |
string | "hotpants" | Method: "hotpants" or "zogy" |
subtraction_mode |
string | "detection" | Mode: "target" or "detection" |
hotpants_extra |
object | {} | Extra HOTPANTS parameters |
All errors return JSON with an error or detail field:
{
"detail": "Authentication credentials were not provided."
}HTTP Status Codes:
400- Bad request (invalid parameters)401- Authentication required403- Permission denied404- Resource not found500- Server error
# 1. Get authentication token
TOKEN=$(curl -s -X POST http://localhost:8000/api/auth/token/ \
-d "username=myuser&password=mypass" | jq -r .token)
# 2. Upload a FITS file and create task
TASK=$(curl -s -X POST http://localhost:8000/api/tasks/ \
-H "Authorization: Token $TOKEN" \
-F "file=@observation.fits" \
-F 'config={"filter":"R","cat_name":"ps1"}')
TASK_ID=$(echo $TASK | jq -r .id)
echo "Created task $TASK_ID"
# 3. Run processing
curl -X POST "http://localhost:8000/api/tasks/$TASK_ID/process/" \
-H "Authorization: Token $TOKEN" \
-H "Content-Type: application/json" \
-d '{"steps":["inspect","photometry"]}'
# 4. Poll for completion
while true; do
STATE=$(curl -s "http://localhost:8000/api/tasks/$TASK_ID/" \
-H "Authorization: Token $TOKEN" | jq -r .state)
echo "State: $STATE"
if [[ "$STATE" != *"running"* ]]; then break; fi
sleep 5
done
# 5. List result files
curl -s "http://localhost:8000/api/tasks/$TASK_ID/files/" \
-H "Authorization: Token $TOKEN" | jq .
# 6. Download detected objects catalog
curl "http://localhost:8000/api/tasks/$TASK_ID/files/objects.vot" \
-H "Authorization: Token $TOKEN" -o objects.vot
# 7. Get image preview
curl "http://localhost:8000/api/tasks/$TASK_ID/preview/image.fits?width=1024" \
-H "Authorization: Token $TOKEN" -o preview.jpgimport requests
import time
BASE_URL = "http://localhost:8000/api"
# 1. Authenticate and get token
response = requests.post(f"{BASE_URL}/auth/token/", data={
"username": "myuser",
"password": "mypass"
})
token = response.json()["token"]
# Create session with auth header
session = requests.Session()
session.headers.update({"Authorization": f"Token {token}"})
# 2. Upload FITS file and create task
with open("observation.fits", "rb") as f:
response = session.post(f"{BASE_URL}/tasks/", files={
"file": ("observation.fits", f, "application/fits")
}, data={
"config": '{"filter": "R", "cat_name": "ps1"}'
})
task = response.json()
task_id = task["id"]
print(f"Created task {task_id}")
# 3. Run processing
response = session.post(f"{BASE_URL}/tasks/{task_id}/process/", json={
"steps": ["inspect", "photometry"]
})
print(f"Processing started: {response.json()}")
# 4. Poll for completion
while True:
response = session.get(f"{BASE_URL}/tasks/{task_id}/")
state = response.json()["state"]
print(f"State: {state}")
if "running" not in state:
break
time.sleep(5)
# 5. List result files
response = session.get(f"{BASE_URL}/tasks/{task_id}/files/")
files = response.json()
print("Files:", [f["name"] for f in files])
# 6. Download objects catalog
response = session.get(f"{BASE_URL}/tasks/{task_id}/files/objects.vot")
with open("objects.vot", "wb") as f:
f.write(response.content)
# 7. Download image preview
response = session.get(f"{BASE_URL}/tasks/{task_id}/preview/image.fits", params={
"width": 1024,
"stretch": "log"
})
with open("preview.jpg", "wb") as f:
f.write(response.content)
# 8. Get task config and results
response = session.get(f"{BASE_URL}/tasks/{task_id}/")
task_data = response.json()
print(f"Zero point: {task_data['config'].get('zp')}")
print(f"FWHM: {task_data['config'].get('fwhm')}")For convenience, you can use this wrapper class:
import requests
import time
class STDWebClient:
"""Simple client for STDWeb REST API."""
def __init__(self, base_url, username=None, password=None, token=None):
self.base_url = base_url.rstrip("/")
self.session = requests.Session()
if token:
self.session.headers["Authorization"] = f"Token {token}"
elif username and password:
self.authenticate(username, password)
def authenticate(self, username, password):
"""Get auth token and set up session."""
response = requests.post(f"{self.base_url}/auth/token/", data={
"username": username,
"password": password
})
response.raise_for_status()
token = response.json()["token"]
self.session.headers["Authorization"] = f"Token {token}"
return token
def list_tasks(self):
"""List all tasks."""
response = self.session.get(f"{self.base_url}/tasks/")
response.raise_for_status()
return response.json()
def get_task(self, task_id):
"""Get task details."""
response = self.session.get(f"{self.base_url}/tasks/{task_id}/")
response.raise_for_status()
return response.json()
def create_task(self, filepath, config=None, title=None):
"""Create task with FITS file upload."""
with open(filepath, "rb") as f:
data = {}
if config:
data["config"] = config if isinstance(config, str) else str(config)
if title:
data["title"] = title
response = self.session.post(f"{self.base_url}/tasks/", files={
"file": (filepath.split("/")[-1], f, "application/fits")
}, data=data)
response.raise_for_status()
return response.json()
def process(self, task_id, steps, config=None):
"""Run processing steps."""
payload = {"steps": steps}
if config:
payload["config"] = config
response = self.session.post(
f"{self.base_url}/tasks/{task_id}/process/",
json=payload
)
response.raise_for_status()
return response.json()
def wait_for_completion(self, task_id, poll_interval=5, timeout=600):
"""Wait for task processing to complete."""
start = time.time()
while time.time() - start < timeout:
task = self.get_task(task_id)
state = task["state"]
if "running" not in state:
return task
time.sleep(poll_interval)
raise TimeoutError(f"Task {task_id} did not complete within {timeout}s")
def list_files(self, task_id):
"""List files in task directory."""
response = self.session.get(f"{self.base_url}/tasks/{task_id}/files/")
response.raise_for_status()
return response.json()
def download_file(self, task_id, filename, local_path=None):
"""Download file from task."""
response = self.session.get(
f"{self.base_url}/tasks/{task_id}/files/{filename}"
)
response.raise_for_status()
local_path = local_path or filename
with open(local_path, "wb") as f:
f.write(response.content)
return local_path
def get_preview(self, task_id, filename="image.fits", **params):
"""Get image preview as bytes."""
response = self.session.get(
f"{self.base_url}/tasks/{task_id}/preview/{filename}",
params=params
)
response.raise_for_status()
return response.content
# Usage example
if __name__ == "__main__":
client = STDWebClient(
"http://localhost:8000/api",
username="myuser",
password="mypass"
)
# Create and process task
task = client.create_task("observation.fits", config={"filter": "R"})
client.process(task["id"], ["inspect", "photometry"])
# Wait and get results
result = client.wait_for_completion(task["id"])
print(f"Completed with state: {result['state']}")
print(f"Zero point: {result['config'].get('zp')}")
# Download results
client.download_file(task["id"], "objects.vot")# Via management command
python manage.py drf_create_token <username>
# Or via Django shell
python manage.py shell
>>> from rest_framework.authtoken.models import Token
>>> from django.contrib.auth.models import User
>>> user = User.objects.get(username='myuser')
>>> token, created = Token.objects.get_or_create(user=user)
>>> print(token.key)