Highport orbital control
Editing, deleting and moving on
Four ways to stop, and they undo four different things. Orbital Control will do any of them; picking the wrong one is the most common way to lose something here.
Everything on this page is something you do to a site that already exists. Changing it, taking it down, giving the address up, taking a copy with you. They are four separate acts with four separate sets of consequences, and only one of them is irreversible.
| What you want | What to do | Reversible |
|---|---|---|
| Change what the page shows | Edit the site and publish again | Publish again |
| Take one file off the site | Remove it in the editor and publish | Publish again |
| Take the page down, keep the address | Delete the site content | Yes. Republish, with nothing to re-verify |
| Give the address up | Release the registration | No |
| Take a copy of everything Highport holds | Download everything on /advanced |
Nothing is changed by it |
| Make Highport re-read a record it got wrong | Publish the record again | — |
Editing a published site
There is no separate update operation. A site is one record in your account, filed under its hostname, and changing the site means writing that record again at the same address. The Content tab at https://hub.highport.space/sites/{domain} is the same screen you used to publish it the first time, and everything that happens after you press Publish is the same.
Opening the editor loads the record your account currently holds and fills the form in from it. It loads once, and it fills in only the side your record uses. A tile-bound record leaves the file table empty, and a record with its own files leaves the tile unchosen. Switching between the two afterwards keeps whatever you have put on each side, so you can look at one and go back to the other.
What you can change
| Part of the site | Control | Carried over from the published record |
|---|---|---|
| Whether the page is a tile or your own files | The three source options, plus the tile picker | Yes |
| A tile's parameters | The parameters form | Yes, re-derived from that tile's declaration |
| Which file answers which path | The file table | Yes |
| Content type and encoding per file | The two advanced columns | Yes |
| Redirects | The redirects block | Yes |
| Your not-found page and its status code | The file input and the 404 / 200 radio | Yes |
/.well-known/atproto-did and RASL |
Two checkboxes | Yes |
Five fields the editor does not carry
The record permits five things the editor has no control for. It never reads them on load and never writes them on publish, so a record carrying any of them loses it the first time you press Publish in the editor. The site record is where each one is defined.
| Field | What it does | What happens |
|---|---|---|
access.requireAuth |
The path prefixes that ask a visitor to sign in | Dropped. The site stops being gated at the next activation |
source.name |
A name on an uploaded bundle | Dropped |
resources[*].status |
A per-path status code of 200, 404 or 410 | Dropped |
notFound.contentType |
A content type on the not-found page | Dropped |
notFound.contentEncoding |
A pre-compressed not-found page | Dropped |
Only the first of these is likely to cost you something you notice. If your site asks visitors to sign in, make the edit with the same tool that wrote the record. The editor has no control that puts the gating back, so once it is dropped it can only be written again from outside. Asking visitors to sign in is where that field is covered.
If the editor says it could not load your page
Could not load this page means the read of your current record failed. The form still works, and that is the hazard: it is empty, and publishing from it replaces the live record with whatever you compose. Reload the page and wait for the record to appear before you change anything.
There is also no draft and no warning when you navigate away. A half-composed edit is lost when you leave the page.
Files that fall out of the manifest
Removing a file from the manifest removes it from the site and nothing else.
Highport keeps its copy of the bytes. Your account no longer references the upload, so your account's own storage may collect it on its own schedule. That is your provider's business and not Highport's, and Highport's copy is the durable one either way.
Files that did not change are not re-uploaded and not re-fetched. Two sites naming the same bytes share one stored copy. Republishing a site where one file changed is close to free, and an untouched file keeps the same ETag, so a returning visitor's browser gets a 304 and downloads nothing.
Is the site down while it republishes?
No, and not for an instant. Before the swap the domain serves the old page; after the swap it serves the new one. The two happen in one database transaction, so there is no moment at which a request can see half a site.
Reaching every server that answers for your domain takes a little longer: usually a second or two, and up to about two minutes on a domain under continuous traffic. The busy domain being the slow one is the counter-intuitive part. A domain nobody has visited recently has no cached answer to serve from, so it changes over at once, and a domain under constant requests always has one. On top of that, a reader who already loaded the old page keeps it in their own browser for up to 60 seconds. How your site reaches people has the mechanism and the numbers behind it.
One thing about the progress panel at the head of the Content tab. On an edit it goes back to reading Live almost at once, showing the revision that is still serving, with the new one listed underneath as a preparation. That is correct, not stuck. The page keeps polling until the new revision lands, which is what You can close this page at the foot of the tab means: come back whenever, and it picks the progress up wherever it has got to.
Taking a site down
Taking a site down takes the page off the domain and leaves everything else in place.
In the Hub it is the first row of Handing it back, at the foot of the Settings tab of /sites/{domain}, and it is only offered when something is published. The row is titled Take the site down, and its help text is the promise in full:
Visitors stop seeing your page. The domain stays registered to you, the DNS records keep working and the certificate survives, so publishing again brings it straight back with nothing to re-verify.

The confirmation is a press. It used to ask you to type the hostname, the same as releasing does, and that was the wrong shape for it: this one is undone by publishing again, and asking for the same ceremony as the action that cannot be undone teaches you to type your hostname without reading. Only releasing the domain asks now.
There is no Highport method for deleting a site. The record is yours, it lives in your account, and deleting it is a write to your own account that Highport observes. Any AT Protocol client can do it:
curl -sS -X POST "https://pds.example/xrpc/com.atproto.repo.deleteRecord" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"repo": "did:plc:zqcffbaxaz5ltrz6i2nnfzmf",
"collection": "space.highport.sites.site",
"rkey": "example.com"
}'
The account and hostname above are examples. The record key is the hostname, so the delete names its target directly.
If the site is published from a space
The Delete button does not take down a site published from a space. It always deletes from your own account, and a site published from a space does not keep its record there. So the record the call names is not the record your domain is serving, and the site goes on serving.
Delete the record at the space host instead, then ask Highport to pull the space so it sees the change. Spaces has both calls. Releasing the registration also stops the page, in one step and from the Hub, but it gives the name up, the irreversible half of this page, and it leaves the record sitting in the space unused.
When the page stops answering
The delete reaches Highport over the network's event stream, usually within seconds, and the domain is switched off the moment it arrives. How the station notices is what happens in between.
The switch is not the whole story. A domain under continuous traffic can go on answering with the deleted page for up to about two minutes while the caches in front of it expire. A domain nobody is requesting stops immediately. It is the same window a publish goes through, in the other direction.
After that, every path on the domain answers 404 with the page that says Nothing is published here yet. It is one page for four different situations, being never registered, released, suspended, and registered-but-nothing-published, and it does not distinguish them, deliberately.
If the delete never reaches Highport, nothing else notices. Highport will not read a record's absence as a deletion, because an account that cannot be reached looks exactly the same from here, and reading it that way would take live sites down during an outage. A site still serving well after a delete comes down by publishing something else at the address, or by releasing the domain.
What survives a delete
Everything except the page.
| Thing | Survives |
|---|---|
| The domain registration | Yes. It moves from active back to verified |
The _bard.<domain> ownership TXT record |
Yes. Highport cannot edit anyone's DNS |
| Your traffic record | Yes |
| Verification state | Yes, untouched |
| The TLS certificate | Yes, and the domain stays authorized at the permission endpoint, so a renewal still goes through |
| The record itself, in your account | No. You deleted it |
| Highport's copy of every file | Yes |
| The built copy of the site Highport was serving | Yes, marked as replaced |
| Traffic and billing history | Yes, untouched |
Two things the Hub shows afterwards read like faults and are not. /sites/{domain} reports the state as Replaced by a newer version, which is wording written for a different situation, and nothing replaced it. And the page keeps offering Visit site and Delete the site content, because what it reads to decide that is still there.
The domain stays in your dashboard and in space.highport.manage.listDomains, with no active revision. It leaves the public directory: space.highport.sites.resolveDomain and space.highport.sites.getSite answer 404 SiteNotFound, and it is absent from space.highport.sites.listSites.
Republishing after a delete
Publish a record at the same hostname and the site comes back. Nothing is re-verified, because the delete touched no verification state. The ownership record was never invalidated and the certificate was never deauthorized.
A byte-identical republish works. The record's content hash is unchanged but the write is a new one, and Highport orders publishes by the write, not by the content.
The success message the Hub shows after a delete says the same thing in fewer words:
Deleted. Your page stops serving in a moment. The domain, its DNS records and its certificate are untouched, so you can publish something new at the same address whenever you like.
Deleting does not delete your files
Nothing on the delete path deletes a byte. The only thing here that ever deletes one is the collector that reclaims unreferenced bytes, and because an account collects blobs no record of its own references any more, the copy the collector is looking at can be the last one anywhere. So it is deliberately conservative. It marks first, waits out a grace period of 30 days by default, re-checks every reference, and only then deletes.
Two things follow, and they are worth knowing before you plan around them. The collector is off by default on a deployment: it observes and reports without deleting until an operator turns it on. And a deleted site's stored snapshot goes on referencing every file it named, so those bytes are not reclaimed by that pass at all. Treat deletion as "the page stops being served", not as "the bytes are gone".
If a file must not exist anywhere, the deployment's operators are who to ask. The abuse contact in the footer is the route.
Releasing a domain
Releasing gives the name up. It is the one action on this page that cannot be undone.
In the Hub it is the second row of Handing it back, titled Release the registration, and it is always offered, because a domain you claimed and never published to has nothing else worth doing to it. It is the row with the hazard striping down its edge — the two are not the same kind of action and the page says so before you read a word of it. Its help text, when something is published:
The domain stops serving and the name becomes available to anybody. Your page itself is untouched and stays with your account, but Highport will not serve it at this address again without a new registration and a fresh verification.
The confirmation is typing the hostname exactly; the submit button stays disabled until what you typed matches the domain, character for character. The dialog is titled Release this domain?, its body says the name becomes available to anybody and that it cannot be undone from here, and it says in as many words that your page is not deleted and stays with your account. On success the Hub returns you to the dashboard, instead of leaving you on a page of registration panels and DNS instructions for a domain that is no longer yours. The dashboard says example.com is released. The name is free for anybody to claim.
By hand, it is one call:
POST /xrpc/space.highport.manage.releaseDomain HTTP/1.1
Authorization: Bearer <service-auth token, lxm = space.highport.manage.releaseDomain>
Content-Type: application/json
{"domain": "example.com"}
{ "released": true }
Every effect, in order
- The hostname is canonicalized. A malformed one is
InvalidDomain. - Your registration is checked as it stands at that moment. If you are not the holder, or the domain is not currently claimed, including one already released, the answer is
DomainNotFound. That one error covers both cases, so the method cannot be used to find out who holds a name. - If anything was serving, it stops. In the same operation as the release, with no grace period, because somebody releasing a domain in a hurry usually wants the content off it now. The caches in front of it take the same couple of minutes a delete takes.
- The registration is marked
released, and the record of your verification and every certificate field are cleared. Clearing the certificate failure counters matters: they are what Highport reads when it decides whether to authorize a certificate, and leaving them would make it refuse the next holder over a cooldown you earned. - A
releasedentry goes into the domain's event history, with adeactivatedentry before it if something was serving. - The change is announced to the servers that answer for the domain, and each one drops what it is holding for it in memory.
The domain then stops being authorized for certificates. Highport's permission endpoint answers 403 for it, though it answers from a cache before it reads the registration, so for up to 30 seconds after the release a request for a certificate can still be allowed. The certificate already issued is neither revoked nor deleted by this call. It stays in the edge's certificate storage until it expires. Nothing is served behind it, because the domain no longer has a site to serve.
The domain leaves your dashboard and the public directory. The crawlable copy of /explore/sites is re-rendered at most once every five minutes, so a released domain can persist in that one server-rendered listing for a few minutes after it is gone from everywhere else.
What survives a release
- Your DNS records. Highport has no write access to anybody's zone. The
_bard.<domain>TXT record and your traffic record are still there, now pointing at a service that will not serve you. Removing them is your job, and nothing prompts you. - The record in your account. Release writes nothing to your account. The site record stays exactly where it was, which is why the help text says the page itself is untouched.
- The registration's history. The row and its events are kept.
releasedis not a claimed status, so the name is free regardless. - Highport's copy of your files, on the same terms as a delete.
Registering the name again, by you or by anybody else, starts from scratch: a new registration, and a fresh verification of the ownership record.
An unverified claim releases itself. If you register a domain and never finish verifying it, it is released automatically after 168 hours and the name is free again. Bringing your domain alongside has that clock.
The distinction
Release when you're finished with the domain. Delete when you're finished with the site.
| Delete the site content | Release the registration | |
|---|---|---|
| What is written | Your record, in your account | The registration, in Highport's index |
| The domain registration | Survives | Freed for anybody to claim |
| DNS | Untouched | Untouched, and now pointless |
| Certificate | Untouched, still authorized | Not revoked, no longer authorized |
| Coming back | Publish again, no re-verification | Register again, verify again |
| Offered when | Only when something is published | Always |
| Reversible | Yes | No |
Downloading everything
The first panel on /advanced has one button, Download everything, and it is a plain link to GET /api/export. Nothing is changed by pressing it.
The archive is a ZIP, and it is stored rather than compressed. Almost everything large in it is an image or a font that would not shrink, and spending the CPU to not shrink them is a cost paid on every export for nothing. The traffic files under analytics/ are the exception and are compressed, because columns of timestamps and counts are the one thing in there that shrinks. It is built in full before the download starts, so a large site means a wait and then a fast transfer. There is no progress percentage, because the response has no declared length.
The file is named for the moment you asked, to the millisecond: highport-export-2026-09-05T14-32-07.123Z.zip.
What is in it
registrations.json
analytics/
manifest.json
{domain}/traffic-hourly.csv
{domain}/status-mix.csv
public/
{your account}/space.highport.sites.site/{domain}/{content hash}.json
{your account}/space.highport.sites.tile/{record key}/{content hash}.json
blobs/{content hash}
spaces/
{space type}/{space key}/
... the same three, for a space you are the authority for
registrations.json is a pretty-printed array of every domain your account has registered, with domain, status, and space where the domain belongs to a space. It includes every status, so a domain you released last year is in your export and is not in your dashboard.
analytics/ is the one part of the archive you could not fetch again from your own account. Everything else came from your PDS and is still there; the traffic numbers were measured here, and Highport deletes them on a schedule. There are two CSV files per registered domain, covering the last 90 days, with a manifest.json beside them naming the exact window the files were read over.
traffic-hourly.csv has one row per hour that saw traffic — hour_utc,requests,bytes_body,bytes_billed — and an hour nobody visited has no row rather than a row of zeroes. status-mix.csv is one row per HTTP status over the whole window — status,requests,bytes_billed,bot_requests — where a 402 row is what your allowance refused. Billed bytes include the per-response header estimate, so they do not match a byte count of the files themselves. A domain nobody visited gets both files with their header lines and nothing else: the file being there is how you tell "no traffic" from "not measured".
bot_requests is the share of a status that came from a crawler, as classified from each request's user agent — the edge says what is kept and for how long. Requests recorded before September 2026 were never classified and count as zero here.
Two things are deliberately missing from it. Nothing in the archive names a visitor. Highport will tell you who authenticated to one of your domains, one domain at a time, on that site's Traffic view — an archive spanning every domain you hold is not that, and it would outlive the retention those rows are kept under. And there are no top paths and no per-version split, because those are answerable for 30 days rather than 90; putting them in a file labelled 90 days would be putting a shorter answer under a longer heading.
Each record is one JSON file, exactly as Highport indexed it, with no wrapper: the identifying facts are in the path. That is not always what your account holds today, and a difference between the two is one of the things the archive is for. There is one file per site, holding the last revision indexed, and one file per version of each tile, which is why a tile you have republished ten times has ten files side by side.
blobs/ holds the files themselves, each named for its content hash with no extension. Nothing in the ZIP's own structure says which of them is your home page. That map is inside the record, at source.resources, where each key is a request path and each value names the file under blobs/. Copy each one to its key and the files are back where they belong. The redirects, the not-found page and any sign-in gating are fields of the record rather than files, so they come back by reading it.
One case will not reconstruct, and it is worth knowing before you rely on the archive. A site bound to a tile somebody else published brings no files with it. The record names the tile by address and content hash, and that tile's bytes belong to its author's account, not yours. Using a tile on your site explains that binding. What lands in the ZIP is five lines of JSON. If you need those files, fetch the tile record from its author's account, or take the bytes off the served site.
A file Highport no longer holds is skipped silently. The record still names its path, so a naive reconstruction produces a site with one broken asset and no error.
What it does not contain
Four things are read to build the archive: your registrations, your site records, your tile records, and 90 days of hourly traffic to your domains. Everything else has no representation in it.
| Not in the archive | Why |
|---|---|
| The built copies of your sites | Derived from the record and the files, both of which are in there |
| Registration history, verification and certificate state | Not part of the record set |
| Indexing job history | Same |
| Records you validated but never published | Same |
| Space credentials, keys, grants and receipts | Same |
| Who visited your sites | Answered one domain at a time, on that site's Traffic view, and never across the domains you hold |
| Traffic older than 90 days, top paths, per-version splits | The first is outside the export's window; the other two are only answerable for 30 days |
| Your own browsing of other people's sites | A separate read: space.highport.manage.getMyActivity |
| Consumption history and what you were billed | A separate read: space.highport.manage.getConsumption |
| Billing periods and per-domain caps | Not part of the record set |
| Your session, OAuth tokens and keys | Credentials. Never exportable |
Highport is not the authority for most of this. The records are your account's, and the files came from there too. An export is not a backup of anything unique — except the traffic numbers, which nothing but Highport ever held. It is the answer to "what does this service actually have about me", which is a different and more useful question.
Calling it by hand
There is no XRPC method and no token path. The export is authenticated by the Hub session cookie and nothing else, and the subject is always the signed-in account. There is no parameter for whose data to export, and no operator method that exports somebody else's.
curl -sS \
--cookie 'bard_session=<value copied from a signed-in browser>' \
-o export.zip \
https://hub.highport.space/api/export
No CSRF token is needed, because a GET writes nothing.
| Condition | Status | Error |
|---|---|---|
| No session cookie, or an expired one | 401 |
AuthRequired |
| Too many requests from one session | 429 |
RateLimitExceeded, with Retry-After |
| Anything failing during the build | 500 |
Internal |
Because the archive is finished before the response starts, a failure during the build is a clean error status. There is no half-written ZIP wearing a 200.
Asking for a re-index
"Re-index" names several different things here, and the one people reach for is the one that does the least. Read this section before you wait on one.
| What it is | Who can ask | What it actually does |
|---|---|---|
space.highport.manage.reindex on a site address |
You, for your own record | Nothing. It answers with a job id, and the job is dropped as a repeat of the revision Highport already has |
space.highport.manage.reindex on a tile address |
You, for your own tile | Re-runs every tile stage. This one works |
Re-read from my PDS on /advanced |
You | Only offered for domains Highport has no registration for, so it answers RecordNotFound |
| The nightly reconciliation pass | Nobody. It runs itself | Compares your account's record against Highport's copy and re-runs only when they differ |
| A rebuild of your account after it moves to a new host | Nobody. The network sends it | Re-runs every site that account has indexed |
space.highport.admin.rebuildSnapshot |
An operator | Nothing, for the same reason as the first row |
The button on /advanced says my PDS. That is the account holding your records, the one the rest of this page calls your account.
The first row is the one to be clear about. A site re-index reports success and rebuilds nothing. It answers 200 with a job id, the job settles as done with no error, space.highport.manage.getIndexStatus reports exactly what it reported before, and an operator's view of the job queue shows it completing normally. Nothing about the outcome distinguishes it from a rebuild that ran, so do not wait on one, and do not read a clean job as evidence that something was repaired.
It is rate limited at 30 calls per 60 seconds per account, and the refusal says so: reindex is limited to 30 per 60 seconds per identity.
What actually re-runs a site
Publishing the record again. Any change, or none at all: write the same JSON back and the new write is enough. Through the Hub that is the ordinary flow, so open the editor, press Get it ready, then Publish.
If what changed is the record in your account rather than Highport's copy of it, the nightly pass finds it on its own, within 24 hours by default, without you asking.
For a site published from a space, the Hub's re-index route always names your own account's copy of the record, so it answers RecordNotFound. Spaces has the sync call that applies instead.
When you would actually need one
Two situations exist, both from bugs that shipped and were fixed, and both affecting sites indexed before the fix.
A tile-bound site recorded as if it served its own files. Nothing served wrong. The page, its bytes and /_bard/site.json were always correct. What is wrong is one line in Highport's index, and the symptom is two screens away: the tile's page in the directory says no site is using it while your site is serving it, and space.highport.sites.listSites?tile= does not match your domain. The nightly pass cannot repair it, because the record is not what is wrong. Republish the site. Any edit, or none.
A tile whose icon was never fetched. The icon is a field beside the tile's files rather than one of them, and it was not being collected, so space.highport.sites.getTileIcon answers 404 TileNotFound while the directory advertises an icon URL. The symptom is a broken image on the tile's card. This is the one case a re-index fixes, because it is a tile:
curl -sS -X POST \
-H 'Authorization: Bearer <service-auth token, lxm = space.highport.manage.reindex>' \
-H 'Content-Type: application/json' \
-d '{"uri":"at://did:plc:z72i7hdynmk6r22z27h6tvur/space.highport.sites.tile/3lqk4bqzfhs2k"}' \
https://highport.space/xrpc/space.highport.manage.reindex
The account and record key above are examples. The caller must be the tile's own account, there is no control for it in the Hub, and republishing the tile does the same job.
How to tell it worked
space.highport.manage.getIndexStatus for the domain, and one field in it:
{
"state": "active",
"rev": "3lrevaaaaaa2z",
"cid": "bafkreia4w54blzdgvovgbdc7baqhnp5aohygjqt36g6ak5inwiidzlkno4",
"entryCount": 12,
"fetched": 12,
"pending": 0,
"failed": [],
"errors": [],
"activatedAt": "2026-08-23T12:00:00.000Z"
}
The revision, hash and timestamp above are examples; the field set is exact. activatedAt moves when a new build of the site goes live, and only then. If it holds its old value, nothing was rebuilt. For the tile-binding case specifically, the check is the directory: your domain now appears on the tile's page, and space.highport.sites.listSites?tile= matches it.
Site owners · 11 of 12