Skip to guide
StructSubGuide
THE STRUCTSUB HANDBOOK

Using StructSub

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.

LoadSelectEditOptimiseExport
01 / GET STARTED

Your first structure

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.

  1. Load your file. The viewer draws the atoms, initial bonds and unit cell. Read any connectivity warnings shown after upload.
  2. Explore. Drag to rotate, scroll to zoom, and use Reset to fit the structure back into view.
  3. Choose a representation. Ball and stick shows atoms and bonds. Space filling shows atomic sizes. Licorice and wireframe emphasise connectivity.

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.

02 / EXPLORE

Select and inspect

To do thisUse this action
Select one atomClick it in Select mode.
Add another atomHold Shift and click an unselected atom.
Deselect an atomClick the selected atom again. Other selected atoms stay selected.
Select an areaChoose Box or Lasso and drag over it.
Inspect an atomHover to see its element, index, coordinates, cell image and bond count.
Select chemically connected partsUse 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.

Periodic images share an atom index. The label 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.
03 / CONNECT

Add a bond, including across a cell

  1. Choose Add a bond in Structure tools, or open Edit → Build & clean up → Bonds.
  2. Click the first atom and Shift-click the second. The readout shows their distance, relative image and existing bond order.
  3. Press Add bond. The view switches to ball and stick so you can see the connection.

Two ways to choose a periodic image

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.

Enter the image offset

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.

Removing a bond, and changing what one is

  1. Click a bond in the viewer. Its two atoms become the selection, and the readout names them, their image offset, their distance and the bond’s order.
  2. Press Delete bond, or choose a new Order.

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.

04 / REFINE

Edit and optimise

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.

Doping: replacing an element

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.

  1. Select the atom or atoms to change. Find equivalent turns one pick into every site the symmetry says is the same, so you can dope one crystallographic site and leave the others alone.
  2. Open Replace element and choose the new element.
  3. Relax afterwards if the new element wants a different bond length. Nothing here moves an atom on its own.
The bond graph is kept, not re-perceived. A dopant with a different covalent radius would gain or lose neighbours if the connectivity were recalculated from distances, which is a different material from the one you asked for. Change the geometry by relaxing, not by replacing.

Reopening a fragment in the sketcher

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.

Accepting an edit redraws the 3D coordinates. That costs nothing for a fragment that was only ever a sketch, but a file can carry optimised geometry, and a round trip through a 2D editor discards it — for biphenyl the span moves 0.435 Å, which is 0.87 Å of MOF-5 lattice parameter, larger than the difference between two published sources of the same molecule. The editor says so in its footer when that applies. Keep the original file if the geometry matters.

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.

Optimisation after bond editing

  1. Finish adding the bonds you need.
  2. Open Relaxation. Use auto for the default backend choice, or uff-periodic explicitly.
  3. Keep cell optimisation off to refine positions at a fixed lattice. Enable it for a periodic structure when you also want to refine the lattice.
  4. Run the calculation, then read its report. Check convergence and warnings before using the result.
The bond graph is carried into force-field optimisation. This includes your added periodic offsets. Machine-learned potentials use geometry rather than explicit bond constraints. A completed request can still report an unconverged optimisation.

The animated process indicator shows the operation and elapsed time. It stays active while background work runs. Reduced-motion settings are respected.

05 / KEEP YOUR RESULTS

Analyse, export and preserve bonding

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?

AnalysisWhat it tells you
PXRDThe 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.
TopologyThe underlying net, by RCSR symbol. It is invariant under substitution, so a topology that changes after an edit means the edit broke something.
StackingFor a layered material, how the sheets sit over one another. Two COFs with identical topology can stack differently and behave differently.
PorositySurface 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 sitesCoordinatively 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 fingerprintWhich 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.
Report the topology method along with the symbol. How a framework is cut into nodes changes the answer, and where it has rod-shaped building units the methods genuinely disagree — each is right about a different net. 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.

A structure file and a saved session are different. CIF and XYZ exports do not preserve the complete manually edited bond graph in this implementation. Reopening them can re-perceive bonds. Keep the API response if you need to inspect the exact bond indices, orders and periodic offsets.

GULP

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.

FormatWhat GULP does with it
Optimise atoms and cellopti 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 cellopti 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.

06 / ACCOUNTS & PLANS

Accounts, allowances and paying

The plans below show the current editing allowances and prices for this service.

What you can do without signing in

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…

Where the account controls are

  1. Sign in or create an account by clicking the account icon at the top right of the workspace.
  2. When signed in, the control is labelled Account. Open it to see your plan and remaining allowance.
  3. The account panel also contains Account settings, upgrade options and billing controls when available.
  4. Sign out is in that panel, at the bottom.

When the allowance runs out

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.

A failed operation does not cost you anything. The allowance is reserved before the work runs and given back if the work fails, so a chemically impossible substitution is not charged for.

Paying, and stopping

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.

Your structures and how long they are kept

The current workspace uses temporary editing sessions, not a saved-project library. Export work you want to keep. See Data retention for details.

07 / API ACCESS & KEYS

Do I need an API key?

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.

ANONYMOUS API

Load and explore

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.

HOSTED WORKSPACE

Sign in to your account

The browser handles authentication for you. Authenticated API requests use a short-lived sign-in token in the Authorization header.

There is no “Create API key” feature yet. This version does not issue personal, long-lived API keys. A structure session ID is not an API key. Clerk publishable keys and server-side configuration secrets are not user API credentials either.

What does “Bearer token” mean?

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.

08 / YOUR FIRST SCRIPT

A complete Python example

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.

  1. Use an existing StructSub server. The default address in this example is for a developer source checkout, not an installable public package.
  2. In another terminal, install the HTTP client with python -m pip install httpx.
  3. Download the script below and run python api_quickstart.py in an output folder.
Download Python example

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.

Read the complete script
"""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()

Prefer curl? Check the connection first.

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.

09 / THE API MODEL

Upload once. Work within a session.

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.

1. Upload a structure

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.

2. Find sites without editing

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.

3. Add a specific periodic bond

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].

4. Optimise with the current bonds

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.

5. Download or undo

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.

Understand the returned structure arrays
FieldMeaning
positionsFlat Cartesian coordinates: x₀, y₀, z₀, x₁, y₁, z₁, … in Å.
cellNine numbers: lattice vectors a, b and c, one vector after another.
pbcThree booleans saying which cell directions are periodic.
bondsPairs of atom indices: i₀, j₀, i₁, j₁, …
bond_offsetsThree relative image integers per bond, in the same order as the bond pairs.
bond_ordersOne 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.

10 / WAIT FOR RESULTS

Some requests return a job

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.

StateNext step
pendingThe job is waiting. Keep polling.
runningThe calculation is running. Keep polling.
doneRead result, then the optimisation report if present.
failedRead error and stop polling.
cancelledStop 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.

11 / GET UNSTUCK

Common questions

I cannot connect to the API.

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.

I cannot see the bonds.

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.

Add bond is disabled or returns 422.

Select two atom images.

My session returns 404.

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.

Optimisation finished but did not converge.

“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.

What do API errors mean?
StatusWhat to check
400 / 422Read detail: an unsupported format, invalid field or chemically invalid operation may be the cause.
401On a hosted deployment, sign-in is required or the supplied token is invalid or expired. Read the response code.
402The hosted workspace has used its available allowance. Read code and detail.
403Your account does not have permission for this action.
404The requested session, job or resource was not found.
409The operation conflicts with current state, such as undoing with no prior edit.
429A hosted request limit was reached. Respect the response's retry guidance.