API Documentation
Submit predictions and retrieve results programmatically over HTTP.
Getting started
The base path for every endpoint is:
/online_predictors/api/
Two things that will otherwise cost you an afternoon:
- Trailing slashes are required. A POST to a path without its trailing slash cannot be redirected without losing the request body.
- No CSRF token is needed for the submission endpoints.
A plain
requests.post(...)works.
Your token
Every request carries a token: an opaque string of your choosing,
up to 100 characters. There is no registration step and no approval — pick one
and start submitting. The web interface generates one for you and keeps it in
your browser's local storage.
The token travels differently depending on the endpoint:
| Endpoints | Where the token goes |
|---|---|
api/submit/… |
a token field in the request body |
api/past-results/…, results/{id}/download/ |
an X-Bio2Byte-Token request header |
api/past-results/shiftcrypt/alignment/ |
a ?token= query parameter |
api/results/{id}/, api/results/{id}/history/ |
no token — anyone holding the request id can read it |
Rate limit
A token may queue 100 jobs per hour, counted over a rolling 60 minutes and shared across all three submission endpoints — the limit is on jobs, not on calls to any one endpoint.
Rejected submissions (any 4xx) do not count against it, so a
script with a bug will not lock itself out.
Every successful submission reports where you stand:
X-RateLimit-Limit: 100 X-RateLimit-Remaining: 99 X-RateLimit-Reset: 0
Over the limit, the response is 429 Too Many Requests with a
Retry-After in seconds:
HTTP/1.1 429 Too Many Requests
Retry-After: 1847
{
"error": "Submission limit reached: 100 jobs per hour for this token. Retry in 1847 seconds.",
"limit": 100,
"remaining": 0,
"reset_after": 1847
}
A second, looser ceiling applies per network address, so that rotating tokens does not sidestep the limit. If you have a legitimate need for a higher allowance, get in touch and we can exempt your token.
Single sequence prediction
Submit
POST /online_predictors/api/submit/single-sequence/
Content-Type: application/json
{
"token": "your_token_here",
"predictor_type": "dynamine",
"sequence": ">INS_HUMAN\nMLSDEDFKAVFGMTRSAFANLPLWKQQNLKKEKGLF\n"
}
With curl
curl -X POST https://bio2byte.be/online_predictors/api/submit/single-sequence/ \
-H 'Content-Type: application/json' \
-d '{"token": "your_token_here",
"predictor_type": "dynamine",
"sequence": ">INS_HUMAN\nMLSDEDFKAVFGMTRSAFANLPLWKQQNLKKEKGLF\n"}'
Uploading a FASTA file instead
curl -X POST https://bio2byte.be/online_predictors/api/submit/single-sequence/ \
-F token=your_token_here \
-F predictor_type=dynamine \
-F job_name='Insulin comparison' \
-F fasta_file=@sequences.fasta
| Field | |
|---|---|
token | required |
predictor_type | required; one name, or several separated by commas |
sequence | FASTA text — required unless you upload a file |
fasta_file | a file upload, as multipart/form-data instead of JSON |
job_name | optional label, up to 120 characters, unique per token |
Available predictor_type values:
dynaminedisomineefoldmineagmatapsper
efoldmine and PSPer
submits psper. Anything else is accepted at submission time and
then fails in the worker.
Run the whole suite in one job by listing several:
"predictor_type": "dynamine,disomine,efoldmine,agmata"
Response
{
"request_id": "3f2504e0-4f89-11d3-9a0c-0305e82c3301",
"task_id": "c31d822c-55af-4a3e-b22f-8d6a83876199",
"status": "enqueued",
"message": "Request submitted successfully"
}
All three submission endpoints answer with exactly these four fields, and word the same problem the same way — a missing token, an over-long job name or a duplicate one reads identically whichever predictor you use.
MSA prediction
Submit
POST /online_predictors/api/submit/msa/
Content-Type: application/json
{
"token": "your_token_here",
"msa_data": ">Protein1\nMLSDEDFKAVFGMTRSAFANLPLWKQQ\n>Protein2\nMLSDEDFKAVFGMTRSAFANLPLWKQQ\n"
}
With curl
curl -X POST https://bio2byte.be/online_predictors/api/submit/msa/ \
-H 'Content-Type: application/json' \
-d '{"token": "your_token_here",
"msa_data": ">Protein1\nMLSDEDFKAVFGMTRSAFANLPLWKQQ\n>Protein2\nMLSDEDFKAVFGMTRSAFANLPLWKQQ\n"}'
Uploading an alignment file instead
curl -X POST https://bio2byte.be/online_predictors/api/submit/msa/ \
-F token=your_token_here \
-F msa_file=@alignment.afa
The alignment needs at least two sequences, all padded to the same
length with -; anything else is rejected with a
400 describing the problem.
Response
{
"request_id": "3f2504e0-4f89-11d3-9a0c-0305e82c3301",
"task_id": "c31d822c-55af-4a3e-b22f-8d6a83876199",
"status": "enqueued",
"message": "Request submitted successfully"
}
ShiftCrypt prediction
Submit
POST /online_predictors/api/submit/shiftcrypt/
Content-Type: application/json
{
"token": "your_token_here",
"chemical_shifts": "<NEF or NMR-STAR file contents>",
"model_class": "1",
"original_numbering": true
}
With curl
Embedding shift data in JSON means escaping every newline in it, so from a shell the file upload below is almost always the easier route.
curl -X POST https://bio2byte.be/online_predictors/api/submit/shiftcrypt/ \
-H 'Content-Type: application/json' \
-d '{"token": "your_token_here",
"chemical_shifts": "save_chemical_shift_list_1\n...",
"model_class": "2",
"original_numbering": false}'
Uploading a NEF or NMR-STAR file instead
curl -X POST https://bio2byte.be/online_predictors/api/submit/shiftcrypt/ \
-F token=your_token_here \
-F model_class=2 \
-F original_numbering=true \
-F chemical_file=@bmr25703_1.nef
| Field | |
|---|---|
token | required |
chemical_shifts | NEF or NMR-STAR text — required unless you upload a file |
chemical_file | a file upload, as multipart/form-data instead of JSON |
model_class | "1", "2" or "3"; defaults to "1" |
original_numbering | boolean, defaults to true |
job_name | optional label, up to 120 characters, unique per token |
The format is detected from the contents; you do not declare it.
Response
{
"request_id": "3f2504e0-4f89-11d3-9a0c-0305e82c3301",
"task_id": "c31d822c-55af-4a3e-b22f-8d6a83876199",
"status": "enqueued",
"message": "Request submitted successfully"
}
Retrieving results
GET /online_predictors/api/results/{request_id}/
With curl
Use -i so you can see the status code —
it is what distinguishes a finished job from a running one.
curl -i https://bio2byte.be/online_predictors/api/results/3f2504e0-4f89-11d3-9a0c-0305e82c3301/
The status code tells you the outcome, so you can poll on it alone:
| Code | Meaning | Body |
|---|---|---|
200 |
Finished | the prediction payload itself |
202 |
Still enqueued or running | a status envelope; Retry-After: 5 |
422 |
The prediction failed | a status envelope, with error set |
404 |
No request with that id | {"error": "Request not found"} |
200 the body is the prediction itself, not wrapped in
an envelope — a JSON object for single-sequence and MSA predictions, and a
JSON array for ShiftCrypt.
While it is running (202)
{
"request_id": "3f2504e0-4f89-11d3-9a0c-0305e82c3301",
"status": "RUNNING",
"request_type": "single_sequence",
"progress": 40,
"attempt": 1,
"created_at": "2026-08-07T12:34:56.789+02:00",
"updated_at": "2026-08-07T12:35:10.123+02:00",
"error": null,
"status_message": "Running predictions",
"history_url": "/online_predictors/api/results/3f2504e0-4f89-11d3-9a0c-0305e82c3301/history/",
"timing": {
"queued_at": "2026-08-07T12:34:56.789+02:00",
"started_at": "2026-08-07T12:35:02.100+02:00",
"finished_at": null,
"queue_wait": "00:00:05", "queue_wait_seconds": 5,
"elapsed_time": "00:04:10", "elapsed_seconds": 250,
"left_time": "00:10:50", "left_seconds": 650,
"wall_time": "00:04:15", "wall_seconds": 255,
"time_limit": "00:15:00", "time_limit_seconds": 900,
"last_heartbeat_at": "2026-08-07T12:39:12.400+02:00",
"heartbeat_age_seconds": 4
}
}
The timing block
Four durations, deliberately kept apart — every one of them is also given in seconds so you do not have to parse a clock string:
queue_wait |
submitted until a worker picked it up. A long wait means the platform is busy, not that your job is slow. |
elapsed_time |
picked up until finished, or until now. This is what the time limit is measured against. |
left_time |
how much of the limit remains; null
unless the job is running. |
wall_time |
submitted until finished — queue wait plus elapsed. |
status_message |
what the job is doing right now, in words ("Running predictions", "Saving results"), or how it finished. Reported for every prediction type. |
heartbeat_age_seconds |
how long ago the worker last reported in. A job whose worker dies stops reporting and is failed shortly after, rather than appearing to run forever. |
The same block appears on every entry of the
past-results endpoints, which is where to look for a
finished job's figures.
If it failed (422)
{
"request_id": "3f2504e0-4f89-11d3-9a0c-0305e82c3301",
"status": "FAILED",
"error": "Invalid predictor types: earlyfolding",
...
}
The processing log
GET /online_predictors/api/results/{request_id}/history/
Returns what the job did, in order — state transitions interleaved with the phase each task reports as it works. Useful for seeing which stage a long job is in rather than only that it is running.
{
"timeline": [
{"kind": "state", "timestamp": "...", "state": "RUNNING", "message": null},
{"kind": "message", "timestamp": "...", "progress": 30, "message": "Running MSA predictions"},
{"kind": "message", "timestamp": "...", "progress": 90, "message": "Saving results"},
{"kind": "state", "timestamp": "...", "state": "FINISHED", "message": null}
],
"history": [ ... state transitions only ... ],
"messages": [ ... progress notes only ... ]
}
history and
messages are the two halves on their own;
timeline is the merged, chronological view. The same
log is behind “Processing log” on the
My predictions
page.
Downloading in other formats
Once finished, results/{request_id}/download/ serves the same
prediction as a file. It takes ?format=json|csv|metadata|input|distributions
and requires your token in the X-Bio2Byte-Token header, because
unlike the endpoint above it checks that the result is yours.
With curl
curl -OJ 'https://bio2byte.be/online_predictors/results/3f2504e0-4f89-11d3-9a0c-0305e82c3301/download/?format=csv' \
-H 'X-Bio2Byte-Token: your_token_here'
Listing everything you have submitted
The past-results endpoints return every request for a token —
one per prediction type.
With curl
curl https://bio2byte.be/online_predictors/api/past-results/single-sequence/ \
-H 'X-Bio2Byte-Token: your_token_here'
Also available for msa and
shiftcrypt in place of single-sequence.
Complete example
Python
import time
import requests
BASE = "http://bio2byte.be/online_predictors/api"
TOKEN = "your_token_here"
response = requests.post(
BASE + "/submit/single-sequence/",
json={
"token": TOKEN,
"predictor_type": "dynamine",
"sequence": ">INS_HUMAN\nMLSDEDFKAVFGMTRSAFANLPLWKQQNLKKEKGLF\n",
},
)
if response.status_code == 429:
raise SystemExit("rate limited, retry in %s s" % response.headers["Retry-After"])
response.raise_for_status()
request_id = response.json()["request_id"]
print("submitted", request_id,
"-", response.headers["X-RateLimit-Remaining"], "submissions left this hour")
while True:
response = requests.get("%s/results/%s/" % (BASE, request_id))
if response.status_code == 200:
print("results:", response.json())
break
if response.status_code == 422:
raise SystemExit("prediction failed: %s" % response.json()["error"])
if response.status_code != 202:
response.raise_for_status()
# Still working. Honour Retry-After rather than guessing.
time.sleep(int(response.headers.get("Retry-After", 5)))
Bash and curl
Needs jq to read the request id out of
the response. -w '%{http_code}' prints the status code, which is
what the polling loop switches on.
#!/usr/bin/env bash
set -euo pipefail
BASE='https://bio2byte.be/online_predictors/api'
TOKEN='your_token_here'
REQUEST_ID=$(curl -sS -X POST "$BASE/submit/single-sequence/" \
-H 'Content-Type: application/json' \
-d '{"token": "'"$TOKEN"'",
"predictor_type": "dynamine",
"sequence": ">INS_HUMAN\nMLSDEDFKAVFGMTRSAFANLPLWKQQNLKKEKGLF\n"}' \
| jq -r '.request_id')
echo "submitted $REQUEST_ID"
while true; do
CODE=$(curl -sS -o /tmp/b2b_result.json -w '%{http_code}' \
"$BASE/results/$REQUEST_ID/")
case "$CODE" in
200) echo 'finished:'; cat /tmp/b2b_result.json; break ;;
202) echo 'still running...'; sleep 5 ;;
422) echo "failed: $(jq -r '.error' /tmp/b2b_result.json)" >&2; exit 1 ;;
*) echo "unexpected HTTP $CODE" >&2; cat /tmp/b2b_result.json >&2; exit 1 ;;
esac
done
One-liner, no polling
Submit and print the response, to check your token and payload work:
curl -i -X POST https://bio2byte.be/online_predictors/api/submit/single-sequence/ \
-H 'Content-Type: application/json' \
-d '{"token": "your_token_here", "predictor_type": "dynamine", "sequence": "MLSDEDFKAVFGMTRSAFANLPLWKQQNLKKEKGLF"}'