Pick it in the viewer
Increase Cells along a, b or c to display repeated cells. Select the actual atom image you want to connect.
Load any structure — a molecule, a cluster, a polymer, a peptide, a zeolite, a MOF or a COF — choose what to change, and refine it. Every operation works on a selection of atoms and their bonds, so the class of material is not something StructSub needs to know. Use the visual workspace or run the same operations from a script.
In the workspace, click Load structure or drop a structure into the viewer. Common formats include CIF, XYZ, extended XYZ, PDB and POSCAR. Use a format carrying a cell and periodicity, such as CIF or extended XYZ, for a periodic framework.
Use StructSub in your browser; no installation is needed. A public local installer and Python package are not available yet.
You can load and view structures without an account, even when your trial editing allowance has run out. The ? links beside tools open the relevant guide section in a new tab.
| To do this | Use this action |
|---|---|
| Select one atom | Click it in Select mode. |
| Add another atom | Hold Shift and click an unselected atom. |
| Deselect an atom | Click the selected atom again. Other selected atoms stay selected. |
| Select an area | Choose Box or Lasso and drag over it. |
| Inspect an atom | Hover to see its element, index, coordinates, cell image and bond count. |
| Select chemically connected parts | Use the selection tools in Overview, including Grow, rings, molecule and building-unit selection. |
Clicks allow small hand movements and near misses within five screen pixels. A larger drag rotates the view or sweeps a selection. Atom indices start at 0: the first atom is 0, the second is 1.
C12 [1,0,0] means atom 12 in the next cell along a. Picking that image preserves its cell identity for bond editing. Other atom edits act on the underlying atom in the structure.Increase Cells along a, b or c to display repeated cells. Select the actual atom image you want to connect.
Enable Set relative periodic image and enter three integers. This also works for negative images that are not displayed.
| Offset [a, b, c] | The second atom is in… |
|---|---|
[0, 0, 0] | The same cell as the first atom. |
[1, 0, 0] | The next cell along a. |
[-1, 0, 0] | The previous cell along a. |
[1, -1, 0] | One cell forward along a and one back along b. |
The offset is relative to the first selected atom. Select one atom and a nonzero offset to bond it to its own image. Offsets must use periodic directions. You can connect the same atom pair through different images; an identical connection cannot be added twice.
New bonds start as single bonds. The default force-field preparation assigns chemical orders before optimisation. Adding a bond keeps the atom positions unchanged. Use Undo to remove a mistaken addition.
Aim for the middle of the stick. A click is resolved to an atom first and to a bond only when no atom was under the cursor, so clicking close to an atom selects the atom, which is usually the intention. In a dense framework where a bond keeps losing, tick Bonds before atoms when clicking and a bond will win outright. A space-filling view draws no sticks, so ticking the box also switches to ball and stick.
Only the image you clicked is deleted. Two atoms can be bonded through more than one periodic image. Removing all of them because one stick was clicked would take a framework apart, so the others stay.
Changing an order does not move anything. It states what the bond is. The force field types from that, and a GULP deck records it; the geometry follows at the next optimisation. Both actions are ordinary history steps, so Undo brings a deleted bond back with the order it had.
Use Build & clean up to add atoms, delete selected atoms, add missing hydrogens or remove unbound guests. To add an atom onto an existing one, select one atom before choosing the new element.
For a fragment edit, select the target sites, choose a compatible fragment from the library or the drawing tools, and use Substitute or Functionalise. Attachment points determine whether a fragment fits the selected site.
Replace element changes what an atom is without moving it. The position and every bond stay exactly as they were, so the result is the structure you had with a different element at that site. That is substitutional doping. Substitute aluminium for silicon on a zeolite T-site, cobalt for zinc on a metal node, or nitrogen for carbon in a graphitic sheet.
Whatever a fragment came from — the palette, a SMILES string, an uploaded file, or a drawing — Edit current fragment puts it back on the Ketcher canvas. Attachment points come back as R atoms and can be moved, added or removed like any other. A drawing reopens exactly as you arranged it; everything else is laid out in 2D first, because its coordinates are three-dimensional and would otherwise overlap into a tangle.
The button does not appear if the fragment cannot be written as a 2D structure — a metal cluster, for instance — or if this server has no sketcher installed.
uff-periodic explicitly.The animated process indicator shows the operation and elapsed time. It stays active while background work runs. Reduced-motion settings are respected.
Choose the analysis you need in Analyse. Each one runs on its own, and each answers a different half of the same question — is this the material you meant, and did the edit do what you asked?
| Analysis | What it tells you |
|---|---|
| PXRD | The powder pattern, which is how a real sample is identified. A substitution moves the low-angle reflections a long way, so this is the most direct check that an edit did what you intended. Upload your measurement to compare, and check the wavelength and angular range. |
| Topology | The underlying net, by RCSR symbol. It is invariant under substitution, so a topology that changes after an edit means the edit broke something. |
| Stacking | For a layered material, how the sheets sit over one another. Two COFs with identical topology can stack differently and behave differently. |
| Porosity | Surface area and pore diameters — the numbers a porous framework is judged on. Needs the optional pyzeo backend; without it the field reports itself unavailable rather than failing. |
| Open metal sites | Coordinatively unsaturated metals a guest can reach. The one an edit can create or destroy without touching the formula: pull a pillar out of a paddlewheel and every metal in it opens up. |
| Ligand fingerprint | Which ligands meet which metal clusters, and how. Where topology answers only when the net has a name, this answers for every metal framework — and it moves when a linker goes missing or a carboxylate slips from bridging to monodentate, both of which leave the net alone. |
auto classifies the material first and picks a method to suit it, which is the right default; all_node keeps a rod's atoms as separate nodes and gives the true net, while sbus collapses a rod to a self-edge. The result carries the method that produced it, and a symbol quoted without one is ambiguous.Running everything at once is available too, and reports what was unavailable rather than failing. Ask for one analysis when you only need one: the topology match is far slower than the rest together.
Click Export to download the current structure in your chosen format. Downloads require a signed-in account. Export is next to Load structure at the top of the sidebar.
The GULP formats write a UFF4MOF deck that carries the bond orders, which is the whole reason to use it: a coordinate file has nowhere to put connectivity, and GULP types the force field from it. Two are offered because they answer different questions.
| Format | What GULP does with it |
|---|---|
| Optimise atoms and cell | opti conp — constant pressure, so the lattice relaxes with the coordinates. This is the default for a periodic structure, and usually what you want after a substitution: a new linker changes the cell the framework wants. |
| Optimise atoms, fixed cell | opti conv — constant volume. The coordinates settle inside the cell exactly as it is. Right when the lattice is a measurement you are holding to, and wrong if you are asking what the edited framework’s cell should be. |
A molecule has no lattice, so it is always written fixed-cell. If the GULP options are not listed, this server does not have the gulp_setup package that does the atom typing.
Editing sessions are temporary and can be lost when the server restarts or a session expires. Download results you want to keep.
The plans below show the current editing allowances and prices for this service.
Open a structure, select atoms, edit it, optimise it and analyse it. A trial allowance lets you try substitution, functionalisation and atom replacement. The one thing a visitor cannot do is download — the Export button asks for an account instead. Keep the workspace open while you sign in. Editing sessions are temporary.
Reading the plan table from this server…
If you are not signed in, the next trial-limited edit opens Sign up to continue. You can also sign in to an existing account. Signed-in users can review upgrade options when their monthly allowance runs out. Only modifications are chargeable — substituting a linker or a building unit, functionalising, and replacing an atom. Opening a file, selecting, optimising, adding or deleting atoms, removing guests, deconstructing and analysing are all free, though they are rate-limited.
Payment is handled entirely by Stripe: the workspace sends you there and never sees your card. Once you are subscribed, Manage billing & invoices in the account panel opens Stripe's own portal, where you can change your card, download invoices and cancel. A cancelled plan runs to the end of the period you have paid for, and the panel says which date that is.
A plan belongs to a workspace, not to a person, so everybody working in it shares the allowance. Only an owner or an admin can change what a workspace pays; a member sees the plan but is told who to ask.
The current workspace uses temporary editing sessions, not a saved-project library. Export work you want to keep. See Data retention for details.
An API lets a script ask StructSub to do the same work you do in the interface. An API key is a credential some services use to identify that script. StructSub currently uses the access methods below.
Upload a structure without an Authorization header. Reuse the same HTTP client so it keeps your trial cookie. Signing in is required for downloads and for edits after the trial allowance runs out.
The browser handles authentication for you. Authenticated API requests use a short-lived sign-in token in the Authorization header.
A token proves that a request belongs to a signed-in account. If your hosted integration already obtains a valid sign-in token, send it like this:
Authorization: Bearer YOUR_SIGN_IN_TOKEN
Replace the placeholder with a token issued through your deployment's sign-in flow. Tokens expire; integrations must refresh them through that flow. This guide does not generate or retrieve a token. Use the API reference to inspect each request and response.
Keep real tokens out of shared scripts and repositories. The downloadable example reads an optional token from STRUCTSUB_TOKEN and a server address from STRUCTSUB_URL. The local default server should stay bound to localhost; it does not provide authentication for a public deployment.
API reference lists endpoints and request fields. Its Try it out controls send real requests to this server; editing endpoints change the session you specify.
This example creates its own tiny periodic hydrogen structure, adds a bond across the a boundary, runs UFF optimisation and downloads the result. It needs no input file and uses the local server by default.
python -m pip install httpx.python api_quickstart.py in an output folder.The script writes relaxed.cif and relaxed-api.json in the current folder, replacing those filenames if they exist. The JSON retains exact API bond information for inspection. Check the printed convergence report.
"""Upload, add a periodic bond, optimise and download using the StructSub API.
Use an existing StructSub server. Install the HTTP client with `python -m pip install httpx`.
Run this script in a folder where you want to save the two output files.
Local use needs no API key. STRUCTSUB_TOKEN is an optional hosted sign-in token,
not a personal API key; hosted sign-in tokens expire.
"""
import json
import os
import time
from pathlib import Path
import httpx
BASE_URL = os.environ.get("STRUCTSUB_URL", "http://127.0.0.1:8000").rstrip("/")
TOKEN = os.environ.get("STRUCTSUB_TOKEN")
# Two H atoms whose closest connection crosses the a boundary.
# The 1.4 Å separation is deliberately stretched, so we add the bond ourselves.
SAMPLE = '''2
Lattice="12 0 0 0 12 0 0 0 12" Properties=species:S:1:pos:R:3 pbc="T T T"
H 0.2 0 0
H 10.8 0 0
'''
def finish(client, response, timeout=300):
"""Return an inline result, or wait for a queued job with a finite timeout."""
response.raise_for_status()
result = response.json()
if not result.get("queued"):
return result
deadline = time.monotonic() + timeout
while time.monotonic() < deadline:
status = client.get(f"/jobs/{result['job']}")
status.raise_for_status()
job = status.json()
if job["state"] == "done":
return job["result"]
if job["state"] in {"failed", "cancelled"}:
raise RuntimeError(job.get("error") or f"Job {job['state']}")
time.sleep(0.5)
raise TimeoutError("Stopped waiting; the server job may still be running.")
def main():
headers = {"Authorization": f"Bearer {TOKEN}"} if TOKEN else {}
# One client retains trial cookies if using a hosted deployment.
with httpx.Client(base_url=BASE_URL, headers=headers, timeout=120) as client:
uploaded = finish(client, client.post(
"/sessions", files={"file": ("stretched-hydrogen.extxyz", SAMPLE.encode())},
))
session = uploaded["session"]
print(f"Session: {session}")
if uploaded.get("warnings"):
print("Connectivity warnings:", uploaded["warnings"])
finish(client, client.post(f"/sessions/{session}/bonds", json={
"i": 0, "j": 1, "offset": [-1, 0, 0],
}))
relaxed = finish(client, client.post(f"/sessions/{session}/relax", json={
"engine": "uff-periodic", "scope": "all", "max_steps": 200,
"optimise_cell": False, "background": True,
}))
print(relaxed["report"]["summary"])
print("Converged:", relaxed["report"]["converged"])
exported = client.get(f"/sessions/{session}/download", params={"format": "cif"})
exported.raise_for_status()
Path("relaxed.cif").write_bytes(exported.content)
# Keep the explicit bond indices and offsets too. This is an API
# snapshot for inspection; it is not an importable session file.
Path("relaxed-api.json").write_text(json.dumps(relaxed, indent=2))
print("Saved relaxed.cif and relaxed-api.json")
if __name__ == "__main__":
main()
curl http://127.0.0.1:8000/health
A working server returns JSON with "status": "ok". For a different port or hosted deployment, use its actual address.
A session holds a structure and its edit history. Upload a file, copy the returned session value, and use it in later request paths. In the examples below, replace SESSION_ID with that value.
curl -F "file=@framework.cif" http://127.0.0.1:8000/sessions
The upload is multipart form data with the field name file. The response includes session, structure and warnings. Most later requests send JSON instead.
curl -X POST http://127.0.0.1:8000/sessions/SESSION_ID/select \
-H 'Content-Type: application/json' \
-d '{"selector":"atoms","indices":[0,1]}'
This returns selection information. It does not change the structure or automatically set the atom indices for a later edit; include the indices in that edit's request.
curl -X POST http://127.0.0.1:8000/sessions/SESSION_ID/bonds \
-H 'Content-Type: application/json' \
-d '{"i":0,"j":1,"offset":[-1,0,0]}'
This connects atom 0 to atom 1 one cell back along a. Use indices from your structure. The connection must not already exist, and a must be periodic. Omitting offset means [0,0,0].
curl -X POST http://127.0.0.1:8000/sessions/SESSION_ID/relax \
-H 'Content-Type: application/json' \
-d '{"engine":"uff-periodic","scope":"all","optimise_cell":false,"max_steps":500,"background":true}'
With background: true, poll the returned job before downloading the result. For a standalone optimisation use scope: "all"; fragment and shell scopes require a seed supplied by an editing operation.
curl -o edited.cif http://127.0.0.1:8000/sessions/SESSION_ID/download?format=cif
curl -X POST http://127.0.0.1:8000/sessions/SESSION_ID/undo
Undo steps back one operation; /redo steps forward again. Deleting atoms changes subsequent indices, so use the structure returned by the latest edit.
| Field | Meaning |
|---|---|
positions | Flat Cartesian coordinates: x₀, y₀, z₀, x₁, y₁, z₁, … in Å. |
cell | Nine numbers: lattice vectors a, b and c, one vector after another. |
pbc | Three booleans saying which cell directions are periodic. |
bonds | Pairs of atom indices: i₀, j₀, i₁, j₁, … |
bond_offsets | Three relative image integers per bond, in the same order as the bond pairs. |
bond_orders | One chemical bond order per bond. |
A bond vector is position[j] + offset @ cell - position[i]. The API stores canonical endpoint order, so reversed endpoints may come back with a negated offset. This describes the same physical bond.
A short operation returns its result immediately. A background operation first returns a response like this:
{"queued": true, "job": "JOB_ID", "poll": "/jobs/JOB_ID", "state": "pending"}
Check GET /jobs/JOB_ID periodically. The Python example above includes polling with a timeout.
| State | Next step |
|---|---|
pending | The job is waiting. Keep polling. |
running | The calculation is running. Keep polling. |
done | Read result, then the optimisation report if present. |
failed | Read error and stop polling. |
cancelled | Stop polling. There is no completed result. |
DELETE /jobs/JOB_ID can cancel work that has not started. It does not interrupt an already-running numerical calculation. A client timeout also does not stop work on the server.
Check that you are using the same server address as the workspace. Open /health at that address. If you run a developer checkout, keep its server process running. If port 8000 is occupied, use structsub serve --port 0 --open and use the address it prints.
Choose Ball and stick or press Show bonds in the bond editor. Space filling intentionally hides sticks. If the selected pair reads “No bond”, verify the atom images and add the missing connection.
Select two atom images.
It may have expired or the server may have restarted. Upload the file again and use the newly returned session ID. An old ID cannot recover an expired in-memory session.
“Done” means execution finished. Inspect report.converged, max_force and warnings. Check missing or incorrect bonds and large geometric strain before increasing the step limit.
| Status | What to check |
|---|---|
| 400 / 422 | Read detail: an unsupported format, invalid field or chemically invalid operation may be the cause. |
| 401 | On a hosted deployment, sign-in is required or the supplied token is invalid or expired. Read the response code. |
| 402 | The hosted workspace has used its available allowance. Read code and detail. |
| 403 | Your account does not have permission for this action. |
| 404 | The requested session, job or resource was not found. |
| 409 | The operation conflicts with current state, such as undoing with no prior edit. |
| 429 | A hosted request limit was reached. Respect the response's retry guidance. |