Validate before you write

Publishing is two calls in a fixed order. Do them the other way round and nothing breaks — it is simply slower and the errors arrive later.

First: validate

Send the record you are about to write to lol.dids.manage.validate, with the record key you intend to use. For a site that is the hostname. For a tile it is the existing key when you are publishing a new version, or nothing at all for a new tile — in which case the service mints a TID and returns it, and that is the key you must use.

Validation checks everything a record can be wrong about: the manifest's paths and reserved-path collisions, the tile you are binding and whether that version is available, the parameters against the tile's declaration, the redirect targets against the effective manifest. Errors come back named and scoped to a path in the record, so a form can highlight the field that caused them.

What comes back on success is more useful than a yes: the AT-URI the record will have, the CID it will have, and the canonical record to submit.

Then: write exactly that record

Write the returned record, unchanged, with putRecord at exactly the returned key. Not a record you built that ought to be equivalent — the returned one. Any edit changes the hash, which means the work the service did in step one no longer applies to what you wrote.

That is never an error. The commit takes the ordinary path and is indexed like any other. It is simply not what you were trying to achieve.

What "prepared" means

For an identity that is allowed to publish this record, validation does more than check it. It starts fetching the blobs the record names from your server and materializing a snapshot on the strength of the validated record, tracking it as pending publication. By the time you write the record, the waiting has usually already happened.

Two consequences worth knowing:

Publish is gated on prepared: true. For a site that means the domain is verified. Until then validation still runs and still reports problems — it is useful while DNS propagates — but the record would be rejected on arrival, so the app disables publishing and says why. For a tile it means you pass the author policy.

The upload-to-write gap closes. Blobs are fetched at validate time rather than when the commit arrives, so nothing depends on how long your server keeps an unreferenced upload.

Editing does not prepare

While you are still changing a record, validate with dryRun: true. You get the same checks and the same error paths with no preparation and no fetching. Prepare once, on an explicit validate or the first publish.

Repeated validation of an unchanged record is idempotent: the same URI, the same CID, a refreshed lifetime, no second job. A changed record supersedes the previous preparation for that key. At most one preparation is live per record at a time, so an impatient client cannot pile them up.

Preparations expire

A preparation has a limited lifetime. Validate, get distracted for a day, and come back: your preparation is gone and validating again re-creates it. Nothing is lost — the record was never written, and the blobs are still in your repository.

There are no drafts anywhere in the system. Nothing you have not published is stored on a dids.lol server as content. If you leave a form, you compose again.

A tile must be published before a site can bind it

If you have just published a tile and immediately try to bind it, validation answers TileNotReady: the commit is not yet visible on the firehose. Wait for the tile to report ready — the app waits for you — and validate the site again.

Records in a space

A record in a space is validated the same way, and answers the same URI, CID and canonical record — but prepared: false, because a permissioned blob is only readable once a committed record references it. Publishing is gated on valid: true there instead, and the fetching happens after the commit rather than before it. In practice it is seconds either way.