The HTTP API
Run one tool with a JSON request, worked end to end on a melting temperature.
| Endpoint | POST /api/tools/<name> |
| Listing | GET /api/tools/ |
| Interactive reference | /api/openapi |
What it does
Each tool in the workbench has its own endpoint, named after the tool. The endpoint accepts that tool's real parameters, with the same bounds, units and published options the panel offers, and returns the same result the panel renders.
Finding a tool
GET /api/tools/ returns every tool with its full input schema: each parameter's type, its
description, its default, its bounds and units, and for a parameter that selects between published
methods, the list of ids with what each one is.
That listing is the reference. It is generated from the tool declarations themselves, so it cannot drift from what the tools actually accept.
A worked example: a melting temperature
In the editor, a melting temperature comes from loading a sequence and opening a panel. Through the API it is one request, and it takes the full set of knobs rather than a simplified subset.
We will melt M13 forward (−20), GTAAAACGACGGCCAGT, a standard sequencing primer.
The simplest call
curl -s https://<host>/api/tools/melting_temperature \
-H 'content-type: application/json' \
-d '{"oligo": "GTAAAACGACGGCCAGT"}'That returns a temperature of 57.883 °C, the model that produced it, the GC content, and the thermodynamics from the same table.
The same oligo, with the knobs set
Now name a melting model, add 5% DMSO to the buffer, and choose which published DMSO depression to apply:
curl -s https://<host>/api/tools/melting_temperature \
-H 'content-type: application/json' \
-d '{
"oligo": "GTAAAACGACGGCCAGT",
"model": "nn_unified",
"dmso_method": "cul76",
"conditions": { "additives": { "dmso": 5 } }
}'The real response:
{
"outcome": "ok",
"data": {
"tm": 55.383198058369146,
"baseTm": 57.883198058369146,
"model": "Nearest-neighbour, unified 2004",
"gc": 52.94117647058824,
"steps": [
{
"step": "Nearest-neighbour, unified 2004",
"tm": 57.883198058369146,
"delta": null,
"derivation": "SantaLucia & Hicks 2004 (DNA_NN4); the table pydna `tm_default` uses · buffer applied (salt method 7)"
},
{
"step": "DMSO 5 %",
"tm": 55.383198058369146,
"delta": -2.5,
"derivation": "−0.5 °C per %, Cullen and Bick (1976)"
}
],
"nn": {
"dH": -132.7,
"dS": -358.0999999999999,
"dG": -21.635285000000025
},
"lockedBases": 0,
"caveat": ""
}
}What that response is telling you
This is the shape worth understanding, because every tool follows it: you get the working, not just the number.
tmis the answer after every correction the buffer asked for, andbaseTmis what it was before any additive. The difference is what the DMSO cost you, stated rather than left to be inferred.stepsis the derivation, one row per correction. Each row carries the temperature after that step, the change it made (delta), and the citation that set the coefficient. Here Cullen and Bick's −0.5 °C per % across 5% DMSO is exactly the −2.5 °C in the row. Ask for a different published model and the coefficient, the citation and the number all change together.modelis named, never returned as a bare id.nncarries ΔH, ΔS and ΔG(37 °C) from the same table as the temperature, so a row never mixes two models. It is absent for models that have no nearest-neighbour table.caveatis empty when the model can be read for an oligo of this length, and says why not when it cannot. The Wallace rule outside its published 14 to 20 nt domain is the case it exists for.
What this computes
The melting-temperature endpoint offers twelve melting models: four DNA nearest-neighbour tables
(SantaLucia & Hicks 2004, Allawi & SantaLucia 1997, Sugimoto 1996, Breslauer 1986), three RNA/RNA
tables (Xia 1998, Chen 2012 with GU wobble, Freier 1986), an RNA/DNA hybrid table (Sugimoto 1995),
pydna's dsDNA-binding-domain model, the GC formula, the Wallace rule, and NEB. It applies the salt
correction you name over Na⁺, K⁺, Tris, Mg²⁺ and dNTP concentrations, then four published DMSO
depressions (Ahsen 2001, Musielski 1981, Cullen & Bick 1976, Escara & Hutton 1980) and formamide
depression. Locked nucleic acid is understood: write the probe as it is ordered with an L
after each locked base (GTCGLAACAGC) and McTigue, Peterson & Kahn's 2004 per-neighbour increments
are added to the nearest-neighbour sum.
It returns the temperature, the pre-additive temperature, the per-step derivation with citations, GC content weighted for ambiguity codes, ΔH/ΔS/ΔG from the matching table, the locked-base count, and a caveat when the model is being read outside its published domain.
Refusals
A tool that cannot compute says so, with the reason and the values it would have accepted. Asking for a melting model that does not exist:
{
"outcome": "error",
"error": "unknown model “not_a_model” — one of nn_santalucia, nn_unified, nn_sugimoto, nn_breslauer, nn_rna_xia, nn_rna_chen, nn_rna_freier, nn_hybrid_sugimoto, bresluc_dbd, gc, wallace, neb"
}An oligo too short to melt:
{ "outcome": "error", "error": "provide an `oligo` of at least 2 bases" }An enzyme name the catalogue does not hold, which is a refusal rather than an empty result:
{
"outcome": "error",
"error": "unknown enzyme “NotAnEnzyme” — not in the 201-enzyme cut catalog this tool resolves names against. Names are case-sensitive and spelled as REBASE spells them (BamHI, EcoRI, BsaI). That catalog is the set whose CLEAVAGE GEOMETRY the cut engine carries, which is smaller than the REBASE dossier the editor shows records from — a real, correctly spelled enzyme can be in the second and not the first (AarI is). Define it as a custom enzyme in Enzyme Collections to cut with it. Nothing was computed: a missing enzyme and an enzyme with no sites are different answers."
}That distinction matters more than it looks. "No sites" and "that enzyme does not exist" are opposite conclusions, and a tool that returns the first for the second will have you believe your plasmid is clean.
The refusal names the right catalogue, which is not the same set as the enzyme records the editor can show you. Cutting needs a cleavage position; showing a record does not. REBASE determines no cleavage position for most of the enzymes it lists, so the set that can be cut with is the smaller one, and a correctly spelled, entirely real enzyme can fall outside it. Define its recognition site and cut offsets once as a custom enzyme and it cuts like any other.
The status codes, which mean different things
| Status | Meaning |
|---|---|
| 200 | The tool ran. The body carries "outcome": "ok" and the result. |
| 400 | The request itself was malformed, checked against the schema the endpoint publishes: a body that is not a JSON object, a value of the wrong type, or a parameter the tool does not take. A misspelled parameter is named, with the one it was probably meant to be. |
| 404 | No tool has that name. The body suggests the nearest names. |
| 409 | The operation needs approval before it can change anything. |
| 413 | The body is larger than one request may carry (16 MiB, which admits a whole bacterial genome as _subject). |
| 422 | The request was well formed and the engine declined it: an unknown model, an oligo of one base, a sequence the operation cannot apply to. The body carries "outcome": "error" and the reason. |
| 500 | The tool itself failed. That is a defect on our side, not in your request. |
The distinction between 400 and 422 is worth keeping: the first is your request being wrong, the second is your request being answerable and the answer being "no, and here is why".
Sending a molecule
Tools that operate on a sequence take it as _subject rather than as a loose parameter, so the body
shape is the same for every one of them:
curl -s https://<host>/api/tools/find_restriction_sites \
-H 'content-type: application/json' \
-d '{"_subject": {"sequence": "ATGGGATCCAAA"}, "enzymes": ["BamHI"]}'{
"outcome": "ok",
"data": [{ "top": 4, "bot": 8, "kind": "5'", "overhangSeq": "GATC", "enzyme": "BamHI" }]
}A _subject carries the sequence and, where it matters, its name and whether it is circular.
Circularity is not cosmetic: a site spanning the origin exists on a circular molecule and does not
on a linear one.
The earlier spellings $subject and $files are still read, so older scripts keep working. The
underscore is what tool-calling models require of an argument name.
Naming a record instead of pasting it
Pasting bases loses the annotations and, unless you remember to say so, the topology. A _subject
can instead name the record, by exactly one of four keys, and it is read the way the workbench
reads it when you start a project:
| key | what it is | example |
|---|---|---|
sequence | the bases themselves | {"sequence": "ATGGGATCC…", "circular": true} |
accession | an archive record, fetched and parsed with its features | {"accession": "U55762", "source": "ncbi"} |
sample | a file from the bundled sample library | {"sample": "data/samples/Genbank/CVU55762.gb"} |
file | a file you hold: text for a text format, base64 for any file | {"file": {"name": "pUC19.gb", "text": "LOCUS …"}} |
source is one of ncbi, ncbi-protein, ena, uniprot, igem, addgene or dbfetch
(the last also takes db and format). Two tools help you find a record to name:
search_records searches the archives by free text, and list_samples browses the sample
library, narrowed by a query and a kind.
A file's format is read from its name's extension, so .gb, .fasta, .dna (SnapGene),
.ab1 and the rest open as they do in the workbench. A file holding several records is not
silently reduced to its first: the request is refused with the records listed, and you choose one
with record_index.
Every response to a named record says what it resolved to under resolved: its name, length,
topology, feature count, and, for an accession, where and when it was fetched.
Sending a whole project: _files
_files is a list of the same references, standing in for the project open in the workbench.
Every sequence record in them becomes a document a tool can name in its sequences, parts or
document arguments (the records of a multi-record file as file · record, or all of them by the
file's own name), and list_library shows what they held. A tool that reads a file itself,
such as a Sanger trace, finds it there too:
curl -s https://<host>/api/tools/align_sequences \
-H 'content-type: application/json' \
-d '{"_files": [{"sample": "data/samples/FASTA/COI-crickets.fasta"}],
"sequences": ["COI-crickets.fasta"], "include_subject": false}'Uploading
Every tool also accepts multipart/form-data: files as _subject or (repeatable) _files
parts, everything else as JSON in an arguments field.
curl -s https://<host>/api/tools/draw_trace \
-F '_files=@read.ab1' -F 'arguments={"document": "read.ab1"}'Object parameters, and the two buffer shapes
Some parameters are objects rather than single values. conditions is one: it describes the buffer
a temperature is measured in. The listing spells out the inside of these, so every key, unit and
bound is discoverable, and a key the object does not have is refused rather than ignored.
There are two buffer shapes, because two engines are behind them, and sending one where the other is expected is an error rather than a silent default.
Nested, for melting_temperature — ions under salt, strand concentrations under oligo,
DMSO and formamide under additives:
{ "conditions": { "salt": { "Na": 1000 }, "additives": { "dmso": 5 } } }Flat, for the primer and qPCR designers (design_primers, design_gibson_primers,
design_qpcr_assay, check_scorpion_stem) — ions, strand concentrations and saltcorr at the top
level, with additives in Biopython's own spelling:
{ "conditions": { "Na": 1000, "saltcorr": 7, "additives": { "DMSO": 5 } } }Send the wrong one and you get a 400 naming the path, not a quiet answer computed in the default buffer:
{ "error": "invalid arguments: data/conditions must NOT have additional properties" }Salt is not a rounding detail. On the M13 primer above, raising Na⁺ from the default buffer to 1000 mM moves the melting temperature from 57.883 °C to 67.383 °C.