2. REST API

Agora exposes a REST API at /api/v2/. All responses are JSON. Authentication uses a personal API key (see User Profile and Settings).

2.1. Authentication

Pass your API key in the Authorization header on every request:

Authorization: Token <your-api-key>
import requests

BASE = "https://agora.example.com/api/v2"
HEADERS = {"Authorization": "Token <your-api-key>"}

r = requests.get(f"{BASE}/project/", headers=HEADERS)
r.raise_for_status()

All examples below assume BASE and HEADERS are defined as above.

2.2. Pagination

List endpoints return paginated results:

{
  "count": 120,
  "next": "https://agora.example.com/api/v2/project/?page=2",
  "previous": null,
  "results": [ ... ]
}

Use the page query parameter to navigate pages, or no_page=1 to disable pagination and receive all results in a single response.

2.3. Projects

Method

Endpoint

Description

GET

/project/

List all projects the current user is a member of

GET

/project/<id>/

Get a single project

POST

/project/

Create a project (body: {"name": "...", "description": "..."} )

GET

/project/myagora/

Get the current user’s personal MyAgora project

# List projects
projects = requests.get(f"{BASE}/project/", headers=HEADERS).json()["results"]

# Get MyAgora project
myagora = requests.get(f"{BASE}/project/myagora/", headers=HEADERS).json()
# 404 Not Found if MyAgora is disabled for the current user

Note

An administrator can disable the MyAgora project for individual users (Settings → Users → Enable MyAgora). When disabled, /project/myagora/ returns 404 Not Found for that user, and their MyAgora project is excluded from the /project/ list.

2.4. Folders

Method

Endpoint

Description

GET

/folder/

List folders (filter by project: ?project=<id>)

GET

/folder/<id>/

Get a single folder

GET

/folder/<id>/items/

List the contents of a folder (subfolders, studies, datasets)

POST

/folder/

Create a folder (body: {"name": "...", "project": <id>} or {"name": "...", "parent": <folder_id>} )

GET

/project/<id>/root_folder/

Get the root folder of a project

# Get root folder of a project
root = requests.get(f"{BASE}/project/42/root_folder/", headers=HEADERS).json()
root_id = root["id"]

# List folder contents
items = requests.get(f"{BASE}/folder/{root_id}/items/", headers=HEADERS).json()

2.5. Studies (Exams)

In the API, studies are called exams.

Method

Endpoint

Description

GET

/exam/

List exams (filter by project: ?project=<id>, or folder: ?folder=<id>)

GET

/exam/<id>/

Get a single exam

GET

/exam/<id>/series/

List the series belonging to an exam

POST

/exam/<id>/copy/

Copy an exam to a folder (body: {"folder": <folder_id>} )

POST

/exam/<id>/move/

Move an exam to a folder (body: {"folder": <folder_id>} )

2.6. Series

Method

Endpoint

Description

GET

/series/

List series (filter by exam: ?exam=<id>)

GET

/series/<id>/

Get a single series

GET

/series/<id>/datasets/

List datasets belonging to a series

2.7. Datasets

Method

Endpoint

Description

GET

/dataset/

List datasets (filter by series: ?series=<id> or exam: ?exam=<id>)

GET

/dataset/<id>/

Get a single dataset

GET

/dataset/<id>/datafiles/

List the files belonging to a dataset

GET

/dataset/<id>/parameters/

List acquisition parameters attached to a dataset

# Walk from project to files
exam = requests.get(f"{BASE}/exam/7/", headers=HEADERS).json()
series_list = requests.get(f"{BASE}/exam/7/series/", headers=HEADERS).json()
datasets = requests.get(f"{BASE}/series/3/datasets/", headers=HEADERS).json()
files = requests.get(f"{BASE}/dataset/12/datafiles/", headers=HEADERS).json()

2.8. Uploading Data

Uploads use a three-step session protocol:

  1. Create an import session

  2. Upload files (supports chunked/resumable upload)

  3. Complete the session to trigger import

Method

Endpoint

Description

POST

/importsession/

Create an import session (body: {"folder": <folder_id>} )

GET

/importsession/<id>/

Get import session status

POST

/importsession/<id>/upload/

Upload a file (multipart file field; use Content-Range for chunked uploads)

POST

/importsession/<id>/complete/

Mark all uploads done and start the import

# Create session
session = requests.post(f"{BASE}/importsession/",
                        json={"folder": root_id},
                        headers=HEADERS).json()
sid = session["id"]

# Upload a file
with open("scan.zip", "rb") as f:
    requests.post(f"{BASE}/importsession/{sid}/upload/",
                  files={"file": f},
                  headers=HEADERS)

# Trigger import
requests.post(f"{BASE}/importsession/{sid}/complete/", headers=HEADERS)

2.9. Downloading Data

Downloads are initiated asynchronously. Agora packages the requested objects and provides a stream URL once ready.

Method

Endpoint

Description

POST

/download/

Initiate a download (body: list of {"object_type": "...", "object_id": <id>} entries)

GET

/download/<id>/

Poll status (state: QUEUED / STARTED / FINISHED / ERROR)

GET

/download/<id>/stream/

Download the ZIP once state is FINISHED

import time

dl = requests.post(f"{BASE}/download/",
                   json=[{"object_type": "exam", "object_id": 7}],
                   headers=HEADERS).json()
dl_id = dl["id"]

# Poll until ready
while True:
    status = requests.get(f"{BASE}/download/{dl_id}/", headers=HEADERS).json()
    if status["state"] == "FINISHED":
        break
    time.sleep(2)

# Stream the ZIP
with requests.get(f"{BASE}/download/{dl_id}/stream/",
                  headers=HEADERS, stream=True) as r:
    with open("download.zip", "wb") as f:
        for chunk in r.iter_content(chunk_size=8192):
            f.write(chunk)

2.10. Tasks

Method

Endpoint

Description

GET

/task/

List task definitions available to the current user

GET

/task/<id>/

Get a task definition (includes expected inputs)

POST

/runtask/

Run a task (see below)

GET

/timelineitem/

List timeline items (task runs, imports, downloads)

GET

/timelineitem/<id>/

Get a single timeline item (includes state, output, log)

POST

/timelineitem/<id>/cancel/

Cancel a running task

To run a task, POST to /runtask/ with the task ID and a list of input objects:

payload = {
    "task": <task_id>,
    "inputs": [
        {"object_type": "dataset", "object_id": 12}
    ]
}
result = requests.post(f"{BASE}/runtask/", json=payload, headers=HEADERS).json()
timeline_id = result["timeline_item_id"]

Poll the timeline item to track progress:

while True:
    item = requests.get(f"{BASE}/timelineitem/{timeline_id}/", headers=HEADERS).json()
    state = item["state"]   # QUEUED / STARTED / FINISHED / ERROR / CANCELED
    if state in ("FINISHED", "ERROR", "CANCELED"):
        break
    time.sleep(3)

print(item.get("output"))   # task stdout/stderr

2.11. Task Store

The Task Store lets a user browse, import, and publish Task and Trigger definitions shared across GyroTools customers via the central GyroTools license server. See Task Store for the full architecture and Task Store for the user-facing guide.

Method

Endpoint

Description

GET

/task-store/entries/

Search store entries. Query params: search, ordering, limit, offset, type (task or trigger).

POST

/task-store/entries/

Publish a new entry. Requires a linked Task Store account (see below).

GET

/task-store/entries/<id>/

Get one entry’s metadata.

PATCH

/task-store/entries/<id>/

Update an entry you own.

DELETE

/task-store/entries/<id>/

Delete an entry you own.

GET

/task-store/entries/<id>/download/

Download the entry’s raw bundle (the same shape as a local Task/Trigger export).

POST

/task-store/entries/<id>/import/

Download and import the entry’s bundle into a project. Body: {"project": <project_id>}. Requires can_manage_tasks (for a task bundle) or can_manage_triggers (for a trigger bundle) on the target project.

GET

/task-store/account/

Get the current user’s Task Store account link status.

POST

/task-store/account/

Link a GyroTools portal account. Body: {"email": ..., "password": ...}.

DELETE

/task-store/account/

Unlink the current user’s Task Store account.

Publish a task by selecting one of the project’s own task definitions and posting its exported bundle along with store metadata:

payload = {
    "type": "task",
    "name": "Rotate PAR/REC",
    "description": "Rotates a PAR/REC dataset by 90 degrees.",
    "long_description": "Uses numpy inside a Docker container...",
    "tags": ["python", "par-rec"],
    "tasks": [task_bundle],   # same shape as a local task export
}
entry = requests.post(f"{BASE}/task-store/entries/", json=payload, headers=HEADERS).json()

POST /task-store/entries/ and PATCH /task-store/entries/<id>/ return 400 with a detail message if the current user has no linked Task Store account yet — link one first via POST /task-store/account/.

2.14. Current User

Method

Endpoint

Description

GET

/user/me/

Get the current user’s profile and settings

PATCH

/user/me/

Update profile fields (e.g. {"first_name": "...", "last_name": "..."} )

me = requests.get(f"{BASE}/user/me/", headers=HEADERS).json()
print(me["username"], me["email"])