For contributors
API reference
Three surfaces, one implementation — the REST core, the /ff/ client surface, and the deprecated XML-RPC adapter.
The machine-readable description is at
/openapi.json (OpenAPI 3.1), rendered interactively at
/api/docs. This page is the orientation; that document is the
contract.
Three surfaces
| Surface | Path | Status |
|---|---|---|
| REST core | /api/v1/… |
Current. Target this for new code. |
| Client surface | /ff/… |
Supported. What the installed mofplus client speaks. |
| XML-RPC | /API/user/xmlrpc, /API/admin/xmlrpc |
Deprecated, with a published sunset date. |
All three call the same functions. The two older surfaces contain no logic of their own, which is what keeps them from drifting away from the REST core.
Both XML-RPC endpoints are public contracts — including the admin one, which has consumers outside the maintaining group. Neither is an internal convenience that can be withdrawn quietly.
Authentication
Reads on the REST core need no credential. Search, record pages, downloads and metadata are open.
Anything that writes, and everything under /ff/ and /API/, needs one of:
Authorization: Bearer mofpp_<prefix>_<secret>— a scoped token. Preferred.Authorization: Basic <user:password>— the legacy bridge. XML-RPC clients reach this automatically, because credentials embedded in the URL are sent as a Basic header.
Scopes are the three tiers: user, weaver, admin. admin does not imply
weaver — a curator who also deposits holds both explicitly, so that "what can
this credential reach" is answerable by reading one field.
Mint a token: POST /api/v1/tokens, authenticating with anything the server
accepts, including a legacy password. That is the bridge, and it is the
supported way to migrate without a flag day.
REST core
| Method | Path | Purpose |
|---|---|---|
| GET | /api/v1/nets |
Search nets: name, coordination, exclusive, spacegroup_number, plane_group_number, pqrs, include_catenated, order |
| POST | /api/v1/nets/identify |
Which net is this? Upload a .mfpx or .cgd; answers by canonical Systre key |
| POST | /api/v1/nets/search-cs |
Search by coordination sequence and vertex symbol |
| GET | /api/v1/nets/{name} |
One net |
| GET | /api/v1/building-blocks |
Search building blocks |
| GET | /api/v1/building-blocks/{name} |
One building block |
| GET | /api/v1/frameworks |
Search frameworks |
| GET | /api/v1/frameworks/{name} |
One framework and its structures |
| GET | /api/v1/force-fields |
Force fields |
| GET | /api/v1/force-fields/{name} |
One force field and its fits |
| GET | /api/v1/force-fields/{name}/fits/{id}/params/{ric} |
Parameters |
| GET | /api/v1/fragments |
Fragments, optionally filtered by atom type |
| POST | /api/v1/tokens |
Mint a token |
| GET | /api/v1/whoami |
What the presented credential can do |
ric is one of bnd, ang, dih, oop, cha, vdw.
Nets
Everything in this section holds for the REST listing, the REST record, and
the HTML pages at /data/nets and /net/{name} alike: they call the same
service functions, so they cannot answer differently.
All 3077 nets are published; two licences, not one
Read this before you write a client. The collection is 3077 net records, loaded from the RCSR Systre archive and from MOF+ with full provenance, all of them served, under a licence that is split by source:
| Records | Source | licence |
licence_source |
published |
A fetch answers |
|---|---|---|---|---|---|
| 2438 | mofplus or rcsr+mofplus |
CC-BY-4.0 |
the project decision | true |
200 |
| 634 | rcsr |
RCSR-2019 |
the RCSR agreement | true |
200 |
| 5 | null (seeded) |
CC-BY-4.0 |
null |
true |
200 |
So an unfiltered GET /api/v1/nets answers "total": 3077 and
"held_pending_licence": 0.
The MOF+ half is licensed by the project's own decision. MOF+ is this
group's own service, so those nets are the project's own data to license, and
the project licenses its data CC BY 4.0 — recorded in CONTEXT §6 and quoted in
full in each record's licence_source. It is not a grant found in the source
data, and the record says as much ("No third-party grant is claimed").
The RCSR half was held unpublished until 2026-09-09, when the repository
owner confirmed the terms: those nets are © 2019 RCSR.net, redistributed by
MOF++ under a private agreement with the rights holder, and RCSR-2019 is
deliberately not a Creative Commons identifier — the agreement permits
redistribution by MOF++ and grants a reader no onward licence, so the identifier
carries open: false. See
docs/reports/net-ingest-2026-09-09.md §3 in the repository for the resolution
and what it does and does not cover.
The held mechanism itself is retained and still tested: a deposit whose
provenance the licence decision does not recognise is licensed nothing and
answers 403, not 404 — the identifier resolves, and the record states what
is withheld. No record is in that state today, so every held_pending_licence
below is 0 on this collection; the field stays in the envelope because the next
ingest can put a record there. Held is a state of the collection, not of your
credential: no token, scope or account changes it. The position is set out on the
Licensing page.
Ordering
order selects the result order of GET /api/v1/nets.
order |
Result order |
|---|---|
rcsr (default) |
rcsr_index ascending; nets with no RCSR symbol after the whole indexed block, by name |
name |
Net name, ascending |
complexity |
complexity_rank ascending — the ingest's simplest-first processing order; nets with no rank last, by name |
Name is the tie-break in all three, so pages are stable. An unrecognised value
is not an error: it is replaced by the default, and the order actually used is
echoed in the response's order field, so a client never has to assume the
default has not moved. It has moved once already — the listing sorted by name
before rcsr_index existed.
rcsr_index is derived here, not published by RCSR: it is the position of
the symbol in our sort of the archive's symbols. Every representation carrying
the number also carries rcsr_index_provenance and a sentence saying so. Do not
present it as RCSR's numbering —
The two orderings a net carries is the whole
rule.
POST /api/v1/nets/search-cs takes no order; it always answers in the default
RCSR-index order.
The listing envelope
{"total": 3077, "limit": 50, "offset": 0, "order": "rcsr",
"excluded_for_missing_data": 0, "excluded_for_unknown_catenation": 0,
"held_pending_licence": 0, "results": [ ... ]}
total— matching records that are listed, which means published ones.excluded_for_missing_data— records dropped only because a field the query filtered on (spacegroup_number,plane_group_number,pqrs) is blank on them. Neither matched nor ruled out; not the same as "did not match". 271 records have no spacegroup and the same 271 noporq, for the reasons indocs/reports/net-analysis-2026-09-12.md.- The two symmetry-group filters are separate, and each sets aside the
other's whole population.
spacegroup_numberis an ITA space-group number (1–230) and matches 3-periodic nets;plane_group_numberis an ITA plane-group number (1–17) and matches the 200 nets here that are 2-periodic. No record carries both, and neither number is ever written into the other's field — which is the point of the separation: 11 meansP21/min one numbering andp4mmin the other, and a client readingspacegroup_numberis reading an ITA space-group number ornull, on every record, without first having to ask what dimension the net is. So of the 271 records aspacegroup_numberfilter sets aside, 200 are 2-periodic and are found byplane_group_numberinstead; the remaining 71 are the ones nothing resolved into a single net (52), the ones Systre refused or could not finish (18) and one with no graph to analyse. spacegroup_numbermatches the number on the record, and for a chiral net that number is one of a pair. A quotient graph carries no handedness, so the computation fixes the group only up to its enantiomorphic pair: 202 records carryfield_sources.spacegroup_handednesssaying which pair, and a search for the other member of that pair will not return them. They are not counted inexcluded_for_missing_dataeither — the field is not blank. Read the handedness note before treating the number as exact.excluded_for_unknown_catenation— records dropped only becauseinclude_catenated=falsewas asked for and their catenation is unrecorded: they cannot be certified un-catenated and are not returned, but they were not judged catenated either.0whenever the query did not filter on catenation. Neither source states catenation — the RCSR archive has no such field and the MOF+-cmarker is a name, not a measurement — which is why the column is three-valued and why this count exists. It is0today because this project now computes the answer on every record that has a graph, and it stays in the envelope because a record can still arrive with none.held_pending_licence— records that matched but are held. Never listed; each resolves at its own URI with 403. A client that reportstotalalone reports a smaller collection than exists.
held_pending_licence counts only what your filters could have judged, and
a held record is judgeable on very little. It is counted against name,
coordination and catenation, and not against spacegroup_number or pqrs:
judging a held record by a value we decline to serve would serve it, one bit at a
time — a count that moved with a spacegroup filter would give up the withheld
number in 230 questions. The surfaces that report the count say so, rather than
claiming a match on a field the count never applied.
Beyond that, a record held for want of a licence typically came from one source alone and has little to judge: no vertices, no coordination number, no recorded catenation. Any filter that touches those excludes it before the count is taken. So a zero means "no held record was ruled out by this query", not "nothing is being withheld"; only an unfiltered or name-filtered listing shows the real number. On this collection every row is published, so the numbers, run against the service as it stands, are:
| Query | total |
held_pending_licence |
|---|---|---|
| (none) | 3077 | 0 |
name=ac |
20 | 0 |
coordination=6 |
510 | 0 |
include_catenated=false |
3023 | 0 (and excluded_for_unknown_catenation 0) |
spacegroup_number=221 |
86 | 0 (and excluded_for_missing_data 271, of which 200 are 2-periodic) |
plane_group_number=17 |
46 | 0 (and excluded_for_missing_data 2877) |
order and the three counts are null on collections that have no such state
(building blocks, frameworks). POST /api/v1/nets/search-cs answers a smaller
envelope, whose results is a list of names — and which carries, beside them,
what the match means:
{"total": 3, "results": ["pcu", "..."], "held_pending_licence": 0,
"matched": [{"name": "pcu", "sequence_source": "stored", "basis": null}, "..."],
"matched_on": "coordination sequences and vertex symbols",
"establishes": "These nets carry the coordination sequences asked for. That is not an identification: …",
"identity_endpoint": "/api/v1/nets/identify",
"sequence_sources": {"stored": 2, "computed": 1},
"excluded_unreadable_sequences": 0, "excluded_not_vertex_symbols": 0,
"nets_without_sequences": 0, "computed_note": "…"}
Three things about that search are worth stating outright:
- It is not an identification. Two different nets can carry the same
coordination sequences, and the search compares only as many shells as were
given.
establishessays so andidentity_endpointpoints at the question that is answered yes or no. - A requested sequence gets a vertex class of its own. n requested sequences require n distinct classes, so asking for the same sequence twice asks for two kinds of vertex and is answered as such. It used to be a per-sequence existence test, which one vertex class could satisfy for every request at once. The same now holds for vertex symbols.
- The sequences searched are not only the source's. A record that arrived
with a MOF+ deposit is matched on the sequences the source supplied, as the
legacy contract has always returned; the 634 records from the RCSR archive
alone carry none — the archive has no such field — and are matched on the
sequences computed from their own graph. Before this the form silently covered
2443 of 3077 records.
sequence_sourcescounts which warrant each match rests on, andmatchedsays it per record.
Provenance and ordering on every net
Every net representation — list row, full record, and held record — carries two blocks, always with all their keys present. A record with no tracked source says so with nulls rather than by omitting them.
"provenance": {"source": "rcsr+mofplus", "source_id": "aab",
"retrieved": "2026-09-08T18:09:42.545747",
"licence_source": "MOF++ project decision, CONTEXT §6 (Licensing): ...",
"field_sources": {"cella": "mofplus", "systre_key": "rcsr",
"catenated": "computed:mofpp.periodicity-v1 (mofplus-deposit)",
"catenated_name_heuristic": "No; agrees with the computed value",
"spacegroup": "computed:systre-19.6.0 (mofplus-deposit)",
"p": "computed:systre-19.6.0 (mofplus-deposit)",
"licence": "mofpp-project-decision (CONTEXT §6)",
"retrieved_utc": "2026-09-08T18:09:42.545747+00:00",
"name_match": "corroborated-by-systre-key",
"cs_corroboration": "checked: ...",
"cs_row_check": "corroborated: 96 of 96 vertex rows agree ...",
"vs_corroboration": "corroborated: 96 of 96 vertex rows agree ...",
"...": "..."}},
"ordering": {"rcsr_index": 1,
"rcsr_index_provenance": "derived",
"rcsr_index_absence": null,
"rcsr_index_note": "Derived by this site, not published by RCSR: ...",
"complexity_rank": 2131}
| Field | Meaning |
|---|---|
provenance.source |
rcsr, mofplus, rcsr+mofplus, or null for a seeded demonstration record |
provenance.source_id |
That source's own identifier for the record |
provenance.retrieved |
ISO 8601 timestamp of the retrieval, null if never. No offset: the column is naive UTC, and the offset is served separately in field_sources.retrieved_utc |
provenance.licence_source |
The basis for the licence. The project decision on MOF+-sourced records; null on RCSR-only records, and null on the seeded ones |
provenance.field_sources |
Per-field attribution — which source each stored value came from, plus the flags below. Full records only |
ordering.rcsr_index |
Position in the derived RCSR ordering. null means only that this row carries no index — not that the net has no RCSR symbol; rcsr_index_absence says which |
ordering.rcsr_index_provenance |
derived or authoritative. Every value here is derived |
ordering.rcsr_index_absence |
null when an index is present; otherwise why it is blank: outside-archive (the name is not an RCSR symbol), unrecorded (it is a symbol, but no index is stored on this row), unknown (the local archive could not be read). Every blank in the collection today is outside-archive — 147 of them; no record is unrecorded |
ordering.rcsr_index_note |
The sentence that travels with the field — the derivation rule when it holds a number, the reason for the blank when it does not — so neither can be quoted out of context |
ordering.complexity_rank |
Ingest processing order, simplest first. Not a quality or difficulty ranking. 1–2443 are ranked on cell contents; 2444–3077 are unkeyed (the RCSR-only block, which supplies the ranking nothing) |
ordering.complexity_basis |
The level, values and key the rank was computed from. Full records only |
catenated |
true, false, or null. Three states, not two — see below |
published |
Whether this record is served in full. false means held — see below |
licence |
An SPDX-style identifier: CC-BY-4.0 on 2443 records, RCSR-2019 on the 634 RCSR-only ones. The second is deliberately not a Creative Commons code — it permits redistribution by MOF++ and grants a reader no onward licence, and carries open: false |
List rows omit field_sources, complexity_basis, vertices, geometry and
files; fetch the record for those.
catenated: null means unknown, never "no"
The column is nullable and all three states are real. Today: 54 true, 3023
false, 0 null — but do not hard-code that last figure. null is a
supported state and means no answer could be given, not "no"; treating it as
false overstates what is known.
Neither source states catenation. The RCSR archive has no catenation field
at all, and MOF+ carries a -c marker in a name rather than a measurement.
That is why the column is three-valued, and it is still true: what changed is
that this project now computes the answer from the graph, so the state a source
left unanswerable is answered here on all but five records.
The value is now a measurement, not a name heuristic. On 3071 records it is
computed from the net's graph — the number of interpenetrating copies the
quotient graph's cycle lattice unfolds into — and field_sources.catenated
reads computed:mofpp.periodicity-v<VERSION> (<basis>) accordingly. The
heuristic that used to supply it (the name contains -c) is preserved beside it
as field_sources.catenated_name_heuristic on 2433 records, reading
"<Yes|No>; agrees|disagrees with the computed value". It disagrees on eight,
each a -c record whose deposited topology holds a single copy of the parent
net.
Five records keep the heuristic's value — four stacks of layers (sql-c,
sql-c6, hcb-c, hcb-c3) and one bundle of chains (lcw) — where the
computation refuses to decide, because a net that is not made of 3-periodic
copies has no copies to count and entanglement in space is not something a graph
records. Their column is not blank: it still holds what the name heuristic
read, field_sources.catenated still reads mofplus:name-heuristic, and
catenated_note says in words that the value shown is that heuristic and that
the measurement can neither confirm nor contradict it. So
include_catenated=false returns lcw as recorded, and
excluded_for_unknown_catenation counts it nowhere: the count is a fact about
the column, and the note is where the strength of the value is stated.
catenated_note travels beside the value on records and on list rows, and
qualifies whichever state a row is in — including a new one for a computed
value and one for a computed non-answer. Show it wherever you show the value;
the value alone reads as more certain than it is.
Flags a client will find in field_sources
Beyond the per-field source names, field_sources carries these. They are on
the row, not only in an internal report, so a client can see what was checked:
| Key | On | Meaning |
|---|---|---|
retrieved_utc |
2438 | The retrieval timestamp with its +00:00 offset, which the naive retrieved column cannot hold |
name_match |
3076 | Four values since the topology became computed. corroborated-by-systre-key (2292) — the canonical key computed from the MOF+ deposit equals the RCSR archive key, so the two sources hold the same net under that name; conflicting-systre-key (1, skl-a) — they do not, and neither source was edited to agree (the record carries a settled identity curation call saying what was decided about it); uncorroborable (3) — the check could not run, with name_match_reason saying why (all three are Systre timeouts); single-source (780) — only one source holds this net, so there is nothing to corroborate |
cs_corroboration |
2419 | The class-to-sequence mapping was checked against the net's own topology. Its absence on a record with coordination sequences would mean no check was made — a different fact from "nothing disagreed" |
cs_topology_mismatch |
7 | The stored sequences disagreed with the walk for the named classes |
cs_class_mapping |
7 | The corrected mapping, class → the sequence the source stored positionally |
cs_unresolved |
0 | A class whose sequence could not be attributed without guessing; its cs is null rather than wrong |
mofplus_coverage |
634 | On RCSR-only records: MOF+ does not list this symbol at all, so no counterpart is expected |
licence |
3077 | Which rights holder: mofpp-project-decision (CONTEXT §6) on 2438, rcsr-agreement (© 2019 RCSR.net) on 634, the seed's own sentence on 5 |
spacegroup, spacegroup_number |
2806 each | computed:systre-19.6.0 (<basis>) — the column was computed from the net's graph, not read from a source |
plane_group, plane_group_number |
200 each | The same, for the 2-periodic nets. Decided by the same rule behind a gate of 2 instead of 3, so a record never carries both pairs |
p, q |
2806 each | as above. r and s have no such key: nothing computes them |
catenated |
3077 | computed:mofpp.periodicity-v<VERSION> (<basis>) on 3071; the older mofplus:name-heuristic on the rest |
catenated_name_heuristic |
2433 | What the -c name rule said, kept beside the measurement, and whether the two agree |
spacegroup_handedness |
202 | The written group is one of the eleven enantiomorphic pairs; the note names the pair and says which basis's lattice the symbol follows. Not a defect and not a conflict |
rcsr_identification |
116 | Systre recognises this net's topology as an RCSR net under a different symbol than the record's own name. Recorded, never written into systre_key |
vs_corroboration, vs_topology_mismatch |
2437, 1 | Whether the stored vertex symbols agree with the symbols recomputed from the graph, and which rows do not (dum, 6 rows). Two vocabularies live under this key. 2432 records carry the apply step's sentence; 5 seeded rows (pcu, pyr, qom, tbo, pcu-c) still carry the ingest's, because they store no vertex symbol to compare — four of them do have a usable rings analysis, and pcu-c has no graph at all. That retained sentence asserts that no ring perception is implemented in this project, which is no longer true; the record serves topology.checks.vs_corroboration_superseded beside it saying so, and a client rendering the key should render that too |
cs_row_check |
2423 | The same for the stored coordination sequences, compared on the stored length |
<column>_check |
3 rows, 8 keys | On a seeded row whose curated value was kept: whether it agrees with the computed one. pcu (spacegroup, spacegroup_number, p, q), pyr and qom (spacegroup, spacegroup_number). A value the apply step wrote itself is not a curation and gets no such key — it is identified by its computed: provenance, rewritten on every run and never frozen |
cs itself is attributed mofplus:cs-class-mapping-resolved-by-topology
instead of a flat mofplus on the seven records whose class mapping had to be
corrected. In every case the sequences are the source's own values, byte for
byte — only the question of which vertex class each one describes was settled
here. The hazard and the method are explained in
The two orderings a net carries.
systre — the canonical key
A full record carries a systre block. The key is on 2930 rows (every RCSR
symbol) and is null on the 147 with no symbol.
"systre": {"key": "3 1 2 0 0 0 1 3 0 0 0 ...",
"checksum": "d9542982e79b231ae96c7ed340001140",
"provenance": "rcsr", "checksum_provenance": "rcsr",
"absence": null, "computed_absence": null,
"note": "The canonical Systre key, copied verbatim from ..."}
- This field is archive text and nothing else is written into it. The key
here is RCSR's, copied verbatim; the
checksumis the archive's own, copied from the same block so that this copy can be audited against the upstream file — it is not computed or checked here either. - A key is computed, and it is served elsewhere. Systre 19.6.0 runs in this
project, and the canonical key it computes from the MOF+ deposit is served
under
topology.symmetry.<basis>.systre_key, withkey_matches_archiverecording the comparison against the field above. It is deliberately not merged into this field: a computed key is our statement and this column is RCSR's, and merging them would destroy the comparison. On a MOF+-only recordsystre_keytherefore staysnulleven where a computed key exists. absencesays why the key is blank when it is:outside-archiveon all 147 today (the name is not an RCSR symbol, and MOF+ supplies no canonical key at all). A blank is an unknown key, never an empty one.computed_absencesays whytopology…systre_keyis null when it is, and it is the same discipline applied to this project's own work:not-run(no Systre analysis is stored),not-ok(every stored run refused, failed or hit the 900 s cap —runsnames each basis, status and reason), orcomponents(a run ended ok on a structure Systre resolved into several connected nets, each of which has a key, so the row has none).nullwhen a key was computed. The third is a result, not a shortfall, and thenotedistinguishes all three: a refusal and a time limit are answers about the run, never the finding that this net has no canonical form.- The
noteis worded by what the run was given. A key computed from the deposited graph is an independent second statement of the canonical form and can disagree; a key computed from the archive text is a round trip through Systre, which checks that the copy is a well-formed canonical key and is not a second source. 636 records have only the second kind, and the note says so rather than claiming two independent statements agree. - List rows do not carry it. Fetch the record; a 500-row page of Systre keys is not a listing.
On a held record the key is the whole of the substance, and it is withheld. An RCSR-only row has no cell, no vertices and no coordination sequence — the archive carries none — so the canonical key and its checksum are the only real data it holds, and the terms that could not be verified are precisely that archive's. So a 403 body carries the block in a different shape, saying that much rather than pretending there is nothing behind the hold:
"systre": {"key": null, "checksum": null,
"withheld": true, "key_on_record": true,
"note": "This record carries a canonical Systre key and it is withheld ..."}
key_on_record distinguishes "held, and there is a key behind it" from "held,
and there is no key either" — which for RCSR-only records is always the former.
systre_key is also named in the withheld list.
coordination_sequences — how far the sequences can be trusted
A full record carries a block saying what is known about its coordination sequences, because a reassigned sequence and a corroborated one used to be presented identically. Sequence values are on the vertices; this block is their provenance.
"coordination_sequences": {"state": "reassigned",
"note": "The sequences below are the values the source supplied ...",
"provenance": "mofplus:cs-class-mapping-resolved-by-topology",
"corroboration": "checked: one walk ... (2 class(es) disagreed)",
"topology_mismatch": "class(es) [0, 1]: ...",
"class_mapping": "...", "unresolved": null}
state is the field to branch on. Its six values, and how many records are in
each today:
state |
Records | Meaning |
|---|---|---|
corroborated |
2412 | The source's class-to-sequence mapping was checked against a walk over the net's own periodic topology, and agreed |
reassigned |
7 | It disagreed. Each sequence was attached to the class whose walk it matches — the source's own values, in a different order. class_mapping gives the permutation |
not-recorded |
653 | The source stores no sequence: the 634 RCSR-only records, and 19 deposited nets whose upstream record has none |
unattributed |
5 | Not written by the provenance-tracked ingest (the seeded rows); sequences shown as stored, unchecked |
uncorroborated |
0 | Sequences present, but the mapping was never checked |
withheld |
0 | A class whose sequence could not be attributed without guessing. Its cs is null rather than wrong; unresolved names the classes |
In every state the sequences are the source's own values byte for byte. Nothing
here computes a sequence into a record, extends one, repairs one, or replaces
one with a walked one. Why the check is needed at all — the mapping is
invisible to the obvious cs[0] == coordination_number test whenever two
classes share a coordination number, which is 44% of the deposited records — is
in The two orderings a net carries.
topology — what was computed from the net itself
A full record carries a topology block: what this project computed from the
net's own graph, as opposed to everything else on the record, which is what a
source said. It is per basis and never merged, because a MOF+ deposit and
an RCSR archive key are two different graphs that usually describe the same net
and sometimes do not. The basis keys are mofplus-deposit and
rcsr-archive-key.
Abridged, with the values dia actually serves (… marks keys left out of the
excerpt, not keys that are absent):
"topology": {
"note": "Computed here from the quotient graph of the net itself, not transcribed from either source: ...",
"periodicity": {"mofplus-deposit": {
"status": "ok", "reason": null, "dimension": 3, "periodicity": 3,
"connected": true, "interpenetration": 1, "catenated": false,
"undetermined_reason": null, "component_count": 1,
"basis": "mofplus-deposit", "basis_note": "...",
"tool": "mofpp.periodicity", "tool_version": "1", "seconds": 0.001,
"input_checksum": "...", "computed": "2026-09-12T..."}},
"symmetry": {"mofplus-deposit": {
"status": "ok", "space_group": "Fd-3m",
"space_group_setting": "second origin choice", "space_group_number": 227,
"plane_group_number": null, "point_group_order": 48,
"node_kinds": 1, "edge_kinds": 1,
"repeat_unit_accurate": false, "repeat_unit_factor": 4,
"multiplicity": 1, "degrees_of_freedom": 1,
"rcsr_identification": "dia",
"systre_key": "3 1 2 0 0 0 1 2 0 0 1 1 2 0 1 0 1 2 1 0 0",
"key_matches_archive": true, "key_matches_archive_reason": null,
"td10": 981, "describes_one_net": true,
"scope": "Systre found one connected net in the repeat unit, ...",
"connected": true, "component_count": 1, "components": [ ... ],
"warnings": [], "...": "..."}},
"invariants": {"mofplus-deposit": {
"status": "ok", "shells": 10, "td10": 981.0, "td10_reason": null,
"genus": 3, "genus_reason": null,
"repeat_unit_factor": 4, "repeat_unit_factor_source": "...", "...": "..."}},
"rings": {"mofplus-deposit": {
"status": "ok", "max_ring": 40, "net_point_symbol": "(6^6)",
"net_point_symbol_reason": null, "capped": false,
"classes": [{"vertices": [0, 1, 2, 3, 4, 5, 6, 7],
"cycle_symbol": "6(2).6(2).6(2).6(2).6(2).6(2)",
"vertex_symbol": "6(2).6(2).6(2).6(2).6(2).6(2)",
"point_symbol": "6^6",
"coordination_sequence": [4, 12, 24, 42, 64, "..."],
"capped": false, "reason": null}], "...": "..."}},
"checks": {"name_match": "corroborated-by-systre-key", "name_match_reason": null,
"vs_corroboration": "corroborated: 8 of 8 vertex rows agree with mofpp.rings v2 (mofplus-deposit).",
"vs_corroboration_superseded": null,
"vs_topology_mismatch": null, "cs_row_check": "corroborated: ...",
"catenated_name_heuristic": "No; agrees with the computed value",
"spacegroup_handedness": null, "rcsr_identification": null,
"conflicts": {}}
}
Three of those values are worth reading twice, because they are easy to mistake
for a defect. repeat_unit_factor 4 says the deposited cell holds four minimal
repeat units — the deposit is a supercell of the net, which is ordinary, and it
is why the one ring class lists eight vertices and the corroborations count
eight rows: dia's deposit has eight vertex rows where its archive key has
two, and the classes above are the deposit's own. And on
dia-c, whose deposit is two interpenetrated copies, describes_one_net is
false, genus is null with a reason ("genus is defined for one connected
3-periodic net; this one is not …"), and the space group served is that of one
copy — which is exactly why the spacegroup column is left untouched for such
a record.
Rules a client should hold to:
- Read
notebefore the values. It distinguishes three different empty states that would otherwise look identical: this representation was built with no database session to ask; the analysis has not been run for this record; or it has, in which case the note names the kinds, the bases and the tool versions actually below it. A kind with no row is{}, which never means the net has no such property. - Every entry carries
status(ok,refused,failed,timed-out) with areasonwhere it is notok. A refusal is Systre's answer about the net — 17 deposits are non-crystallographic ladders or have colliding nodes — and a timeout is a budget that ran out. Neither is a value of zero or a null to be read as "none". input_checksumis the checksum of the graph actually analysed, so a result computed before a deposit changed can be told from one computed after. Nothing rebuilds a graph when serving, so no entry claims to be current.randsare absent by design. The note says so in every populated block: they count faces and tiles of a natural tiling, and none is derived here. The limit is measured, not pending — the Gavrog library this site runs Systre from has no route from a 3-periodic net to a tiling at all, and the 200 2-periodic nets for which it has one tile the plane, whose transitivity is a triple and deliberately does not go into anr/spair (docs/reports/tiling-spike-2026-09-12.md).- Symmetry has a scope.
describes_one_netandscopesay whether Systre found one connected net, one net repeated n times, or several different nets sharing a repeat unit. A space group served from the second or third case describes one copy or one component, not the whole, and the column-levelspacegroupis deliberately not written in those cases at all. checksis quoted, not recomputed. These are the sentences the apply step recorded on the row; every key isnullon a record the apply step has not reached, which differs from a check that ran and found nothing.topologyis named in thewithheldlist of a held record.
Computed fields on a vertex row
Vertex rows of a record with a deposit gain four computed keys beside the
source's own, deposit basis only — Vertex.idx is the deposit's vertex
numbering, and matching an archive key's vertices to these rows by position
would be a guess:
| Key | Meaning |
|---|---|
vertex_symbol_computed |
The MOF+ convention (shortest cycle per angle) — directly comparable with the stored vs |
vertex_symbol_superseded |
Present (non-null) only where a settled curation decision ruled the stored symbol wrong. The row then presents the computed symbol under vertex_symbol, the stored string is served verbatim under vertex_symbol_as_stored, and this sentence names the decision, the role that took it and the date. 6 rows of one record (dum) |
ring_vertex_symbol_computed |
The RCSR convention (shortest ring per angle, * where none within the 40-edge cap). A different quantity, hence its own key |
point_symbol_computed |
The Wells point symbol, 4^12.6^3 style |
coordination_sequence_computed |
The sequence walked from the net's own graph |
computed_absence |
Why a computed value is missing, when one is: the analysis was never run, it did not end ok, or it covers no vertex of this index |
These are served for truncated and placeholder rows as readily as for whole ones — that is the repair — while the stored string stays under its own key, unchanged and unrepaired. The two never merge, and a computed value never appears under a source's key.
POST /api/v1/nets/identify — which net is this?
The one direction the site could not be asked in. Every other search takes a name or a property and answers with records; this takes a file and answers with a name.
curl -F file=@mynet.mfpx https://mofplus.org/api/v1/nets/identify
file is a .mfpx topology or a .cgd holding a PERIODIC_GRAPH block; a
.cgd with several graphs also needs block=<ID>. A .cif or a plain
coordinate file is not read: it states no connectivity, and deriving one
from interatomic distances means choosing a cutoff, which would make the answer
depend on a decision the caller did not make.
Identity here is the canonical Systre key, an invariant of the quotient graph. The same net in another cell, with the vertices numbered differently, or with every coordinate nudged, gives the same key; two different nets do not. So this is an identification, not a similarity score — and it identifies the net, the abstract periodic graph, not the material the file came from.
Four outcomes, each with its own status, so a client can branch on the
status line as well as on outcome:
| Status | outcome |
What it means |
|---|---|---|
200 |
match |
The key is one this collection holds. holders names the records and, for each, whether it matched the RCSR archive's own key or a key this project computed, and on which basis |
404 |
no-match |
The key was computed and matches nothing here — a statement about this collection, not about the net. neighbours offers the nearest coordination sequences, as suggestions: a sequence match is not an identity |
422 |
refused |
Systre will not key this graph, with its own systre_status (refused, failed, timed-out, unavailable) and reason. Deliberately not a 404, which would say this collection does not hold it |
400 |
unreadable |
The file could not be read, or the request is outside the bounds. An answer about the file, not about any net |
429 |
— | The rate limit, with Retry-After |
A match can name more than one record, and they are not the same claim. A
holder with key_origin: "archive" is the RCSR symbol the key is published
under — the name the caller asked for. A holder with key_origin: "computed" is
a record whose own graph keys to this net, which may be a second name for it or
a deposit that is not the net its name claims; the archive holder is listed
first, and each record's own page says what is known about it.
Limits, and why. The upload is bounded at 4 MB before it is parsed, and the
graph at 4000 vertices and 20 000 edges after parsing and before any process
starts — a few hundred bytes of .cgd can name a graph on a million vertices,
and Systre's cost grows with the graph. Each call starts a Systre process under
the same 900 s per-structure clock the analysis runner uses, so the endpoint is
rate-limited per client. The file is never stored: it is read into memory,
its connectivity is taken, and the bytes are dropped when the request ends.
site_symmetry — what kind of vertex each vertex is
Every net's vertex rows carry a symmetry, and the block that says what it
rests on travels with the vertex table, under
coordination_sequences.site_symmetry:
"site_symmetry": {"recorded": 8, "vertices": 8,
"provenance": "computed:mofpp.site_symmetry-v1 (spglib-2.7.0, mofplus-deposit)",
"note": "The site symmetry on each vertex row is the point group of …",
"absence": null}
A vertex's site symmetry is the point group of the operations of the net's ideal
space group that leave that vertex where it is — its Wyckoff position in the
maximum-symmetry embedding. It is what the coordination number cannot tell
you. dia and nbo are both four-coordinated at every vertex; dia's sites
are -43m (tetrahedral) and nbo's are 4/mmm (square-planar), and no
building block fits both.
- It is a property of the net, not of any material built on it. It is computed from the net's own graph and read from no source: neither source states one.
recordedandverticesare counts, not a ratio to round: nothing is written unless every row of a record can be given a site, sorecordedis eitherverticesor0.absencecarries the reason when it is0, and the reason is about this project's evidence and never about the net — a vertex always has a site in its net's own group. 2794 of 3077 records carry one (146 243 vertex rows); the 283 without are 200 2-periodic nets (whose symmetry is a plane group, and the tables used are the 230 space groups), 52 that resolved into more than one connected net, 21 Systre refused or could not finish, 4 whose barycentric placement puts distinct vertices on one point, 1 with no graph, and 8 whose rows a computed site could not be put on.- The symbol is a bare Hermann–Mauguin point group (
-43m,4/mmm,m-3m,1), not the axis-oriented site-symmetry spelling (4/mm.m): it is computed as the stabiliser in the net's own group and named by spglib's point-group classification.
curation — what a curator has to decide
Beside topology, a full record carries a curation list. A curation call
is a disagreement inside the record between what a source said and what was
computed from that same source's deposit. Each call carries a state:
state |
Meaning |
|---|---|
open |
Stated, not settled. Deciding it needs somebody who can go back to the source, and nothing on the record has been changed |
settled |
Ruled on. The call carries decision.ruling, decision.taken_by, decision.taken_on and decision.consequence, and the ruling changes what this site presents — never what is stored |
An open call and a settled one are the same structure: a settled call is
what an open one becomes, so branch on state and not on which keys are
present. The four decision fields are null while a call is open.
The list is empty on all but 10 of the 3077 records.
8 open calls, kind: "catenation" — dia-c3s, eta-c, eta-c4, qtz-c,
qtz-c3, qtz-c4, qtz-c6, twt-c. Each is named with the MOF+ -c marker
for an interpenetrated net, and the topology those same records deposit is a
single net: the periodicity analysis counts one copy, and Systre reads each as
one connected net and identifies it as the parent.
1 settled call, kind: "vertex-symbol" — dum. Its six 3-coordinated rows
store 3.8(4).8(4) where mofpp.rings computes 3.8(2).8(2); the other six
rows of the same record, in the same convention from the same graph, are
reproduced exactly. On 2026-09-12 the repository owner ruled: the stored symbol
is wrong; the computed symbol stands. The consequence is on the vertex rows —
those six serve the computed symbol as vertex_symbol, the stored string as
vertex_symbol_as_stored and the decision as vertex_symbol_superseded, and
vertex_symbols.superseded states it once for the record. Nothing was written
to the database: the MOF+ value is untouched, and re-reading the source will
still find the wrong symbol there.
1 settled call, kind: "identity" — skl-a, the collection's only
name_match: conflicting-systre-key. The canonical key computed from the MOF+
deposit (56 vertices, 120 edges, degrees 24×3, 24×5, 8×6) is not the key the
RCSR archive carries under that name (56 vertices, 124 edges, 24×3, 16×5, 16×6);
TD10 is 1195.29 against 745.71 and genus 65 against 69. A canonical key names a
net uniquely, so these are two nets under one symbol. On 2026-09-12 the
repository owner ruled: the identity of the symbol stays with the RCSR archive
key; the deposited topology is a different net and is not served as the topology
of this symbol. Two follow-ups are part of the ruling and neither is a local
edit — ask MOF+ to re-deposit or rename that record, and report the missing
loop-derived edges to RCSR. Both graphs stay on the record, each under its own
basis in topology; curation[].record reports which basis the computed
columns are attributed to as it stands when the record is served.
"curation": [
{"kind": "catenation", "slug": "catenated-marker-without-measured-interpenetration",
"state": "open", "headline": "The name marks this record as catenated; ...",
"note": "A curation call is a disagreement inside this record that this site can state but must not settle. ...",
"claim": {"field": "catenated", "value": true, "stated": "Yes", "basis": "The MOF+ name heuristic: ...",
"recorded_as": "catenated_name_heuristic",
"recorded_sentence": "Yes; disagrees with the computed value"},
"finding": {"field": "catenated", "value": false, "stated": "No", "interpenetration": 1,
"periodicity": 3, "connected": true, "basis": "mofplus-deposit", "basis_note": "...",
"tool": "mofpp.periodicity", "tool_version": "1", "statement": "..."},
"evidence": [{"check": "interpenetration", "statement": "...", "interpenetration": 1, "…": "…"},
{"check": "systre-identification", "rcsr_identification": "qtz",
"describes_one_net": true, "statement": "...", "…": "…"}],
"record": {"column": "catenated", "value": false, "stated": "No",
"attribution": "computed:mofpp.periodicity-v1 (mofplus-deposit)", "statement": "..."},
"decision": {"state": "open", "question": "...", "options": ["...", "..."], "note": "...",
"ruling": null, "taken_by": null, "taken_on": null, "consequence": null},
"changed": "Nothing was changed in response to this call. ..."},
{"kind": "vertex-symbol", "slug": "stored-vertex-symbol-contradicted-by-the-computed-one",
"state": "settled", "headline": "The stored vertex symbol disagrees with ... and the stored one was ruled wrong.",
"note": "A curation call is a disagreement inside this record, derived when the record is served ...",
"claim": {"field": "vs", "value": "3.8(4).8(4)", "stated": "3.8(4).8(4)", "basis": "The MOF+ vertex symbol ...",
"recorded_as": "vs_topology_mismatch",
"recorded_sentence": "vertex idx 0, 1, 2, 3, 4, 5; 6 of 12 rows compared.",
"vertices": [0, 1, 2, 3, 4, 5]},
"finding": {"field": "vs", "value": "3.8(2).8(2)", "stated": "3.8(2).8(2)",
"disagree": 6, "agree": 6, "compared": 12, "basis": "mofplus-deposit", "basis_note": "...",
"tool": "mofpp.rings", "tool_version": "2", "statement": "...",
"rows": [{"vertex": 0, "stored": "3.8(4).8(4)", "computed": "3.8(2).8(2)"}, "…"]},
"evidence": [{"check": "comparison", "agree": 6, "disagree": 6, "compared": 12, "statement": "...", "…": "…"},
{"check": "agreeing-rows", "agree": 6, "statement": "The convention is not the disagreement: ...", "…": "…"},
{"check": "decision-evidence", "tool": null, "statement": "The evidence the ruling rests on: ..."}],
"record": {"column": "vertices.vs", "value": "3.8(4).8(4)", "stated": "3.8(4).8(4)",
"attribution": "mofplus", "statement": "The 6 rows ruled on present the computed symbol ..."},
"decision": {"state": "settled", "question": "...", "options": ["...", "..."], "note": "Decided. ...",
"ruling": "The stored symbol is wrong; the computed symbol stands.",
"taken_by": "the repository owner", "taken_on": "2026-09-12",
"consequence": "What follows for a reader: ..."},
"changed": "Nothing was written to any record. The decision changes what this site presents ..."}
]
- Derived at serve time, stored nowhere. A call is assembled from
field_sourcesand the analysis rows the record already carries. No column is written for it, and no record has been renamed, re-deposited, merged or withdrawn on the strength of one.changedsays so on every call. - The rule reads the record, not a list of names. A
catenationcall is raised when the-cmarker was recorded for the record and read yes, a periodicity analysis endedokand counted the copies, and that count is- A genuinely interpenetrated
-cnet (dia-c, 2 copies) raises none; nor does a record whose analysis states it cannot count copies (the four layer deposits andlcw), nor one nobody has analysed. Avertex-symbolcall is raised when the ring analysis of the deposit contradicts a stored symbol, row by row, comparing like with like. Anidentitycall is raised when a Systre run computed a canonical key and it differs from the archive key the record carries. A record that stops meeting a rule stops carrying the call at its next request.
- A genuinely interpenetrated
- A ruling is matched to the question it answered, never to a record: the
kind, the basis, the tool, the exact pair of strings in dispute and — where
the ruling names one — the
input_checksumof the graph that was analysed all have to be the ones ruled on. For theidentityruling the disputed pair is the two canonical keys themselves. A different record showing a different disagreement, or this record after a new deposit or a changed archive key, gets an open call rather than inheriting the decision. - Every evidence entry says whether this site measured it.
computed_hereis true for a comparison made from the record's own stored analyses and false for thedecision-evidenceentries, which are what a ruling rests on and were established elsewhere; those carry notooland nobasis. recordis not the claim and not the finding. It is what the columns hold today: on the eight,catenatedisfalse, written by the apply step from the measurement, with the superseded marker kept undertopology.checks.catenated_name_heuristic; ondum,vertices.vsstill holds the string MOF+ supplied.- An empty list is not a clean bill of health. It says no rule fired, which on an unanalysed record is mostly a statement about the analysis.
curationis named in thewithheldlist of a held record, for the reasontopologyis: a call quotes the measured values in words.
403 — held pending licence verification
No record is in this state today — the RCSR terms were confirmed on 2026-09-09 and all 3077 are served. The state and its tests remain, because the next deposit whose provenance the licence decision does not recognise lands in it. A held record's URI resolves. It answers 403 with a body that states what it is, rather than 404 (which would deny that a minted, stable identifier exists) or 200 (which would present a withheld record as a served one).
{"name": "example", "uri": "https://.../net/example", "type": "net",
"published": false,
"status": "held-pending-licence-verification",
"detail": "This record exists and is identified, but its licence could not be verified ...",
"licence": "unverified",
"provenance": {"source": "...", "field_sources": { ... },
"field_sources_withheld": ["spacegroup_handedness", "vs_corroboration", "..."]},
"ordering": { ... },
"systre": {"key": null, "withheld": true, "key_on_record": true, "note": "..."},
"withheld": ["spacegroup", "plane_group", "transitivity", "cell", "natoms",
"catenated", "vertices", "geometry", "files", "systre_key", "topology",
"curation"]}
Three things about that body are worth knowing.
detail says the licence "could not be verified against either source", which
is the wording for a record in the state, not a claim about the collection. This
is exactly why the rule below says branch on status and not on the prose.
provenance.field_sources is filtered on a held record, and
field_sources_withheld names what was dropped. Only the entries that say where
a value came from are served; an entry that says what a value is, or what
comparing two values showed, is withheld with the value itself. The analysis
writes such entries — spacegroup_handedness names the space group in words,
catenated_name_heuristic names the catenation, vs_corroboration counts the
vertex rows — and a hold that stopped at the columns would publish them in prose
at the status code that declines to serve them.
How much of the rest of withheld is null anyway depends on the record: a row
built from the RCSR archive alone has no cell, no coordinates and no coordination
sequence, so the entries that cost a reader something are systre_key and
topology. The systre block says so directly, and key_on_record tells you
whether there is a key behind the hold at all.
What a client should do with it:
- Branch on
status, not on the prose indetail.held-pending-licence-verificationis the machine-readable state;withheldnames the fields a published record would have carried, so you can show the shape of the hole instead of an empty record. - Treat it as "exists, content withheld", not "not found". Keep the identifier. It is stable and will resolve to the full record when the RCSR archive's terms are confirmed.
- Do not retry, and do not authenticate. This 403 is a standing state, not a rate limit and not an auth failure.
- Use
held_pending_licencefrom a listing rather than probing names one at a time — but read the catenation caveat above, which can zero that count.
GET /net/{name}.mfpx and .xyz on a held record answer 403 with
{"detail": "net 'aca': ..."} — no deposit is served without a licence. The
HTML record page renders the same state at the same status code.
The deprecated XML-RPC surface cannot express any of this: get_net, get_cs
and get_vs raise fault 1 (not found) for a held net, and
get_list_of_nets and search_cs simply omit them. That is a limitation of the
old contract and one more reason to target the REST core.
Record URIs and content negotiation
Every record answers at one address in more than one representation:
GET /net/pcu → HTML
GET /net/pcu Accept: application/json → JSON
GET /net/pcu.json → JSON
GET /net/pcu.mfpx → the topology file
GET /net/pcu.xyz → coordinates
The suffix form exists for callers that cannot set headers. Both are the same identifier.
.mfpx answers on 2876 of the 3077 records, and not all of them the same
way:
| Records | What .mfpx is |
How to tell |
|---|---|---|
| 2442 | the file a contributor deposited, served verbatim | provenance.field_sources.topofile is mofplus |
| 434 | generated on request from the net's computed maximum-symmetry embedding | response header X-MOFpp-Geometry: computed-embedding, # lines in the file saying so, and geometry.computed in the JSON |
| 200 | nothing: 422 with the reason — the net is 2-periodic, the format's # cell line states six parameters and a layer has three |
geometry.dimension is 2 |
| 1 | 404 — the seeded pcu-c row, which has no geometry on any basis |
geometry is null |
A generated file is not a measurement: every edge is the same length, the cell
is the ideal cell of the net's symmetry group and the mean edge is scaled to 1.
Do not read its cell as a lattice in ångström. What it states exactly is the
topology — which vertices, which are joined, and across which cell boundary.
docs/reference/file-formats.md sets out the three ways a client can tell a
generated file from a deposit.
The 422 is deliberate and is not a 404: the geometry of those 200 records
exists, is computed, and is served in full in the .json at the same URI. A 404
would deny it.
.xyz is available on no net: this loader deposits only the canonical
.mfpx and writes no .xyz, so the suffix answers 404 on a published net (and
403 on a held one, which is judged before the file is looked for). Do not build
a client that expects .xyz to appear for nets.
geometry in a net's JSON
The same object the viewer on the page draws, so the picture and the machine-readable answer cannot disagree. Full records only.
"geometry": {"dimension": 3,
"cellparams": [2.3094, 2.3094, 2.3094, 90.0, 90.0, 90.0],
"cell": [[2.3094, 0.0, 0.0], [0.0, 2.3094, 0.0], [0.0, 0.0, 2.3094]],
"cell_note": null,
"computed": "This geometry is computed, not deposited. …",
"boundary_edges": 12,
"vertices": [{"idx": 0, "coord": [0.125, 0.125, 0.625],
"coordination_number": 4}, "..."],
"edges": [{"idx": 0, "i": 0, "j": 2, "image": [0, 0, 0],
"crosses_boundary": false}, "..."]}
dimensionis 3 or 2 and everything else follows it.cellis that many Cartesian lattice vectors of that many components,cellparamsthe six or three parameters they were built from (a, b, c, alpha, beta, gammaora, b, gamma), and each vertex coordinate and each edge image carries that many numbers. Readdimensionfirst. A 2-periodic net is served as 2-periodic rather than padded into three with an axis nothing measured, andcell_notethen carries the sentence saying so.computedis the sentence saying this geometry was computed rather than deposited, andnullon a deposit. It is the field to branch on.imageis the cell offset of the edge's far end, which is what makes a boundary-crossing edge drawable at all rather than a long line to the wrong vertex.geometryisnullwhere the record has no geometry on any basis — one row in this collection.
The /ff/ surface
Shapes are fixed by the installed client and will not change while it is supported.
GET /ff/gen/check
GET /ff/gen/{FF,FFrefs,FFfits,atypes,FFfrags,special_atypes}
GET /ff/gen/params/{ric}/{fitid}
GET /ff/gen/{refs4frags,refs4atypes}
GET /ff/gen/{topoqeq_fit,topoqeq_par}
GET /ff/frags/frags
GET /ff/get_file/{fname}
POST /ff/gen/FFfits
POST /ff/gen/set_params/{ric}
POST /ff/gen/del_params/{ric}
POST /ff/frags/upload
Two quirks are preserved deliberately, because the client depends on them:
- POST payloads travel in the query string. The client posts with its data as URL parameters rather than a body; only the fragment upload is multipart.
- The response envelope is positional. A successful call answers
{"status": "OK", "<first path segment>": …}— so/ff/gen/checkreturns acheckkey. Where the client instead readsidanderrorsoff the whole document, those responses deliberately omit the positional key.
GET /ff/get_file/{fname} answers raw text, no envelope.
XML-RPC
Deprecated. Every response carries Deprecation: true and a Sunset date
(RFC 8594). It exists so that installed code keeps working, and it will be
deleted rather than maintained.
Adapted methods: add, get_net, get_list_of_nets, get_list_of_bbs,
get_bb, get_mof_structure_by_id, get_cs, get_vs, search_cs,
list_FFrefs, list_FFfrags, get_FFfrag, get_FFfrags, get_FFref,
list_special_atypes, get_params, get_params_from_ref, create_fit.
Methods not yet adapted answer a fault naming their replacement rather than "method not found" — most of them depend on tables whose migration is still open. If your workflow needs one, say so; the fault text is the list of what to prioritise.
Errors
REST and /ff/ use HTTP status codes with a JSON body. XML-RPC returns faults:
1 not found, 2 not supported or insufficient tier, 3 bad arguments.