Licensing, credits and issuer numbers
Read this if you are choosing a plan, expect to mark more than the free allowance, or need to know what a licence changes on a running deployment. You will know what is free, how credits are counted, what a mark carries in each state, and how licences and issuer certificates are installed and renewed. Skip if you only detect or verify (both are free and need no licence), or you want prices (laidline.com/licensing). Time: 10 minutes for the first half; the rest is reference. Needs:
pip install laidlineto run the commands.
In short
- Detection and verification are free in every state and never read a licence.
- Marking is counted in credits per calendar month (UTC); the first 1,200 credits a month are free (1,200 images, or 100 minutes of video, or 400 minutes of audio).
- Over the allowance without a licence, marking never stops: new marks are shared marks and the compliance paperwork in the evidence export is withheld.
- Registering is free and gives you a registered issuer number and a private payload key, so your marks name you; paid plans raise the monthly allowance.
- Licences and issuer certificates are signed files, verified offline under two pinned Laidline roots, and renew on their own.
This page describes what the software does. The terms are in the Laidline Customer Licence Agreement, which this page does not summarise.
What is free, what is metered, what is locked
- Free in every state: detection and verification.
/detect,laidline detect,laidline evidence verifyand the bundle'sverify.pynever read the licence, the meter or a certificate, and grades never depend on them, including after a licence lapses. - Metered: marking, in credits per calendar month (UTC). The first 1,200 credits each month are free: one allowance per company, registered or not, however many deployments it runs. The software counts each evidence log separately and the customer agreement sets the company-wide figure (How credits are counted).
- Never stopped: marking and evidence recording. Over the allowance, without a licence, after a licence lapses or with a broken licence file, every mark is watermarked, signed, logged, checkpointed and timestamped exactly as before, and the mark and proxy responses do not change.
- What changes over the allowance: the payload key new marks carry (What a mark carries) and, without a licence, the compliance paperwork in the evidence export (What happens over the allowance).
- Never locked: the export itself, and the raw evidence log, checkpoints and certificates on your disk. Nothing
deletes, encrypts or locks them, and
verify.pykeeps working on every bundle you have exported.
Plans
The plan id is what laidline register --plan takes and what a licence's grant.plan carries. Prices are on
laidline.com/licensing; none appear in the package.
| Plan | --plan |
Credits a month | Signing keys | Billing |
|---|---|---|---|---|
| Free | registered_free |
1,200 | 1 | none |
| Lite | lite |
15,000 | 1 | monthly or yearly |
| Starter | starter |
150,000 | 2 | monthly or yearly |
| Growth | growth |
1,000,000 | 5 | monthly or yearly |
| Scale | scale |
10,000,000 | 10 | monthly or yearly |
| Enterprise | sold by contract | unlimited | unlimited | by contract |
- Free: registering costs nothing. The free licence runs 12 months and renews itself.
- Paid plans: you buy and change them in the customer portal at https://account.laidline.com (sign in with a
work email at your registered domain).
laidline registerbills monthly unless you add--interval year. A monthly subscription gets a licence for each paid month, a yearly one a licence for the year; each paid invoice issues the next licence (Automatic renewal). Moving from yearly to monthly billing takes effect at the end of the paid year. - Grace: every licence covers the period you pay for plus 30 days.
- Over a paid grant: credits above the plan's monthly figure are a True-Up under the customer agreement, not a switch: your marks stay private and nothing is withheld, and the credits above the plan are charged at the plan's overage rate (True-Up).
- Scale and Enterprise may carry their own weight table; it prices every month that licence unlocks. Enterprise adds an organisation CA binding and tenant reports.
- Earlier plan ids stay valid and read as the plans that replaced them:
registeredas Starter,unlimitedandplatformas Enterprise.
laidline usage names the smallest plan whose monthly figure covers a month's credits (suggested_band), and
laidline licence status prints the same as suggested band:.
True-Up
Credits above the plan's monthly figure are charged after the Month, at a fixed rate for each credit above the plan. The rate is the plan's price per credit plus 25 percent, the same on monthly and yearly billing:
| Plan | Overage rate, per credit above the plan |
|---|---|
| Lite | $0.0124 |
| Starter | $0.0042 |
| Growth | $0.0025 |
| Scale | $0.000625 |
| Enterprise | the rate its Order states |
-
Which plan decides: the highest plan you paid for during the Month sets both the credits that count as the plan and the overage rate. Each Month stands alone; credits are not pooled across a year.
-
Which Months are billed: the Month a paid plan starts is not billed, and neither is the Month of Lapse.
-
When it is settled: on the 16th of the next month, the day after the usage-report deadline. Laidline emails the amount that day.
-
How it is charged: as a separate invoice, charged to the card on file on the 23rd, or 7 days after the email if that is later. Monthly and yearly billing alike. Until then you can dispute it at billing@laidline.com.
-
Under 50 cents: the amount is waived. It is not charged, not carried to a later Month, and you get no email.
-
The amount emailed is the amount charged, or nothing is, apart from any tax. If your account with the payment processor holds a credit or an unpaid balance on the charge date, nothing is charged automatically: the charge is held for review by hand and your balance stays as it was.
-
A charge held for a credit: if Laidline approves it, your card is charged in full, as a card payment with a receipt and no invoice, for the amount emailed plus any tax. Your credit stays untouched.
-
A failed charge never touches your plan, your licence or its renewal.
-
Counts come from your reports (and, if you turned reports off, from a signed Usage Statement). Laidline holds no logs, so a report is your own declaration, and the agreement's audit clauses stand behind it.
-
Your marks stay private throughout, and nothing is withheld.
How credits are counted
The evidence log is the meter: every mark is already an evidence entry, and credits are computed from those entries when they are read. There is no separate counter.
The signed weight table that ships in the package, version 1.0.1, prices marks from November 2026:
| Medium | Credits | What 1,200 credits buy |
|---|---|---|
| Image | 1 per mark, at any size | 1,200 images |
| Video | 0.2 per second | 100 minutes |
| Audio | 0.05 per second | 400 minutes |
| Text | 0.05 per 1,000 tokens | 24 million tokens |
| Unmarked or failed output | 0 |
-
Example: a month with 900 images and 30 minutes of audio costs 900 + 1,800 s x 0.05 = 990 credits, inside the free allowance.
-
Voice calls: a streamed call is metered like any other audio, 0.05 credits for each second of audio your bot sends through the marker, which is 3 credits a minute. Only that audio counts: the caller's voice, hold music and the gaps between turns never pass through the marker, so they cost nothing. Silence inside the bot's own audio counts, and audio below 16 kHz is not marked and counts 0. A 5-minute call with the bot speaking throughout is 15 credits; an hour of bot speech is 180. The free allowance of 1,200 credits is 400 minutes, about 80 such calls. At each plan's monthly price (plan price divided by plan credits; yearly billing is cheaper, since a year costs 10 months):
Plan Per minute (3 credits) 5-minute call (15 credits) Per hour (180 credits) Lite $0.03 $0.15 $1.79 Starter $0.01 $0.05 $0.60 Growth $0.006 $0.03 $0.36 Scale $0.0015 under $0.01 $0.09 -
Content Credentials without a watermark: an image signed but not watermarked counts like a marked image.
-
Rounding: the month's total is rounded once, half up to 0.01. A single entry's rounded figure is illustrative, and per-medium subtotals are each rounded, so they need not add up to the total exactly.
-
Start-up self-test: up to 10 self-test marks per UTC day per deployment are not metered. Beyond that a self-test mark is an ordinary, metered mark and its result warns
self-test cap reached (10 per day); this mark is metered. -
Per evidence log: the allowance applies per evidence log (per authoritative sink). Deployments with separate logs each count separately; the customer agreement, not the software, sets an entity-wide figure.
Check this month's credits
laidline licence status
Expected output on a new, unregistered deployment:
state: free_within_cap plan: none allowance: none credits this month: image 0, video 0, audio 0 (total 0) suggested band: registered_free marks this month: private 0, shared 0 self-test marks: 0 issuer: unregistered shared number <your trial number> directory: snapshot 2026-10-21 serial 10 licence: absent
What just happened: Laidline read this month's entries from your authoritative evidence sink, priced them with
the weight table in force and printed the state. allowance: shows a licence's grant (none before you register);
the free 1,200 credits apply either way. laidline licence status --json prints the same as one JSON object.
The formula and the table in force
Credits are computed from facts every entry already records (modality, the event (marked, unmarked,
failed), watermark scheme, Content Credentials presence, audio duration_s, video frames/fps, text
completion_tokens) and from the entry's meter block (pixels, env, selftest; see
Meter blocks). Per entry:
(per_mark + per_second x seconds + per_megapixel x pixels / 1e6 + per_1k_tokens x tokens / 1000)
x outcome_factor[marked|unmarked|failed] x signed_only_factor (C2PA without a watermark) x env_factor[env]
with the row <medium>/<scheme> if the table has one, else <medium>/*, else meter_missing_as. The table names
the monthly allowance as free_cap_units_per_month and its rounding as rounding.
Table in force: a month is priced by the newest table this installation knows whose effective_from is at or
before that month; a month before the earliest table's effective_from is priced by the earliest table. A later
table never re-grades an earlier month. Every status, notice and statement names the table version it applied.
A table carried in a licence. A licence may carry its own weight table, either inline (grant.weights) or by
naming a shipped table (grant.weights_version). That table prices every month the licence unlocks: its own
effective_from is not consulted, so a licence that starts in the middle of a year prices all of its months with its
own table, and the months an earlier licence unlocks keep that licence's table. Where two licences unlock the same
month, the one that ends last (a renewal) decides, so each month is priced by exactly one table. Marking, the usage
statement, the usage report, laidline licence status and the evidence export all apply this one rule. An inline
table has the same fields as a published table, including free_cap_units_per_month; Laidline copies that figure
from the published table, and inside a paid licence it decides nothing, because the licence's own grant is the
limit. A licence table that cannot be used never stops marking or the export: the shipped table prices the month,
and the problem is named (Licence states).
The start-up self-test is excluded by an in-process flag only (it counts for no credits and no switch); a request
header such as x-laidline-route: startup:selftest never excludes a mark. Self-test marks are still shown, as
self-test marks: N per month in laidline licence status, the export's coverage line and verify.py
(selftest_marks in the status JSON, the usage statement and the monthly usage report).
What happens over the allowance
The first mark that takes a month over 1,200 credits changes two things, and only for that UTC month:
| Your deployment | New marks for the rest of the month | Compliance paperwork in the export |
|---|---|---|
| Unregistered, no licence | shared marks (as before: unregistered marks are always shared) | withheld for the month |
| Registered on the free plan | shared marks under your trial number, instead of private marks | included; the month reads over allowance, not attested |
| Paid plan, licence current or in grace | private marks, unchanged (over the grant is a True-Up, charged at the plan's overage rate) | included |
| Licence lapsed past grace, invalid, or certificate revoked | shared marks | withheld for months over 1,200 credits that no licence unlocks |
When the paperwork is withheld, the export still runs and the bundle is complete and signed: summary.md opens with
an UNLICENSED USE notice (months, credits, allowance, table version); the filled-in compliance texts, the licence
clause and the packaged robustness and phantom-decode figures read "not included: licence required"; and
manifest.json carries a licence block. The raw log, the checkpoints, detection and verify.py are untouched.
The first mark of the next UTC month sees zero credits again: a registered deployment's marks are private again, and nothing marks the return. If you receive a lawful order for records whose paperwork is withheld, Laidline issues a licence for the demanded months free of charge (Lawful orders).
What a mark carries
The payload key and the issuer number of a new mark depend on the registration. The choice is made once per mark, at mark time, from local files and the evidence log: nothing is checked online, and an error in the choice falls back to the private key if the certificate binds one, else to the shared key. The two key families are described in Payload keys: The shared family and the private key.
| State | Key in the new mark | Issuer number | What a verifier sees |
|---|---|---|---|
| unregistered | the shared pool key | the trial number laidline init drew |
"shared mark", grade D: a Laidline mark is confirmed, the provider is not identified |
registered free, within the month's allowance (the grant's units_per_month; 1,200 credits on the free plan) |
the private key bound in the issuer certificate | the registered number | "registered to {name}" per the directory snapshot, with the URL to verify at; grade B on confirmation |
| registered free, allowance crossed | the shared pool key, for the rest of the UTC month | the trial number (the certificate's prior_trial_ids[0]) |
as unregistered |
| paid, licence current or in grace | the private key, never a fallback | the registered number | as registered; credits above the grant are a True-Up at the plan's overage rate, not a switch |
| licence lapsed past grace, licence invalid, or certificate revoked | the shared pool key | the trial number | as unregistered |
The private key is used only while an installed issuer certificate that binds this deployment lists its key_fp for
the medium. An expired, not yet valid, revoked or unbound certificate counts as none. A grant with no plan counts as
paid. Credits are counted from the evidence log, self-test entries excluded. A long-running process re-reads the
certificate and licences hourly; a read that fails (a file that is there but cannot be read) keeps the last good
inputs until the next read, while a removed certificate or licence takes effect.
The switch happens at mark time only. The directory entry, the certificate and the licence are not touched
mid-month. The first mark that would cross the allowance is made shared and appends one payload_key_switch
evidence event, {month, switched_to: shared, at_credits, key_id, issuer_id}, before its marked entry; later marks
that month read the event and stay shared. The first mark of the next UTC month sees zero credits and is private
again; nothing marks the return. A mark made while private keeps its registered number and private tag and resolves
to "registered to {name}" for as long as the directory lists the number, including after the licence lapses. Lapsed
and revoked read differently:
- lapsed (licence or registration ended): the verifier is told the number was registered to {name} until {date} per a snapshot, that a watermark does not say when it was made, and that the provider's evidence record (RFC 3161 timestamps) can show the date. Attribution stays.
- revoked (key compromise or terms, never non-payment): the verifier is told the registration was revoked on {date}. Attribution is stripped, and new marks are shared.
Coverage and features in an export
Lock mode. The weight table's or the grant's lock_mode says what is locked over the allowance; the only mode in
effect is withhold_paperwork (a value of refuse_export reads as withhold_paperwork; nothing refuses a plain
export for the allowance). The export never locks by accident: with no valid weight table, or if the licence code
itself fails, nothing is withheld (the manifest then carries a licence_check_failed:<type> warning).
Features enforced. Only export, paperwork and tenant_reports on a licence mean anything; any other feature
string is ignored, and marking, detection and decoding are never gated. export belongs to the licence attestation
in a bundle, paperwork to the compliance paperwork and tenant_reports to the platform filters (a tenant filter on
an export or a statement). A licence without export still unlocks its months, but the bundle reports them as
licensed, not attested. A licence without paperwork withholds the paperwork (the Measure texts, the licence
clause and the packaged figures read "not included") for the months it unlocks that are over the free allowance, and
summary.md says "Paperwork: not included: the licence for <months> does not include the paperwork feature"; it
never refuses the export. The one refusal is LicenceRequired: a tenant filter without tenant_reports (on every
decided month). It is CLI exit code 3 (laidline evidence export --tenant, laidline usage --tenant) and HTTP 402
{"reason": "licence_required", "detail": ...} on /admin/evidence/export?tenant=, and its text ends with the
lawful-order note (Lawful orders).
Coverage in an export. The detection-access text carries a line - Coverage: (when the paperwork is withheld,
summary.md carries the same line near its top): licence months <months or none>; issuer <number>: registered
(or registration absent, with (trial) for a trial number, or registration unknown when the registration could
not be read); the licence months are the months whose status is covered or true-up; then, for each month the
export decides, <month>: private P, shared S, self-test marks: N, <status>, where P and S count the month's
marked entries by watermark.key_family (shared; anything else or absent counts as private: text marks,
signed-only images (watermark_skipped) and, on a deployment without an issuer certificate, marks under a
hand-written registered-range issuer_id) and N counts its self-test marks apart (the meter leaves them out). The
manifest's licence.months carries the same private, shared and selftest_marks counts, and verify.py
recounts all three. The status is one of six words:
| Status word | Meaning |
|---|---|
covered |
A licence unlocks the month, within its grant, and carries the export feature. |
true-up |
A paid licence unlocks the month and the usage is over its grant; the credits above it are a True-Up at the plan's overage rate. |
over allowance, not attested |
A registered free licence unlocks the month and the usage is over its allowance. |
not covered |
The month is over the free allowance and no licence unlocks it (the paperwork is withheld). |
absent |
No licence unlocks the month and nothing is owed, or no licence decision was made. |
licensed, not attested (no export feature) |
A licence unlocks the month but lacks the export feature. The month is left out of the "licence months" list, as is every month whose status is not covered or true-up. |
The same counts and statuses are in the manifest as licence.months, keyed by month ({private, shared, status},
the status as an identifier: covered, true_up, over_allowance, not_covered, absent, not_attested).
verify.py recounts every month from the signed streams and fails the bundle on a difference
(licence_months_mismatch:<month>); it prints each month's line under its licence: line.
--tenant TENANT (laidline evidence export) or ?tenant=TENANT (/admin/evidence/export) narrows the counts to
that tenant's entries. It needs a licence with tenant_reports unlocking every decided month; otherwise the export
is refused (exit 3 / 402). The streams, checkpoints and fingerprint records stay whole: only the Counts tables and
the per-month counts of the coverage line are narrowed, the manifest names the tenant, and summary.md has a line
saying so.
- Which months decide: an export of a date range is decided by the months of the asset entries inside that range.
The bundle also carries the entries after the range up to the checkpoint that anchors it. Those entries decide
nothing when they fall in a month after the month of
--toand the stream carries at most 1,000 of them (the defaultevidence.checkpoint.every_n); an entry carried in the month of--toor earlier, or in an extension longer than that, decides like an entry inside the range. A range with no asset entry inside it (for example the instant of a checkpoint event) is decided by every asset entry the bundle carries. When the manifest carries alicenceblock, itsdecided_monthslists the months that decided. - Months a licence unlocks: month
mis unlocked when the licence'scovers_fromis at or beforem(or null) andmends at or beforenot_afterplusrenewal.grace_days. Months inside a licence window stay unlocked after the licence lapses. A licence's owngrant.lock_modeapplies only while the licence is inside its window plus grace. - Over the grant of a licence:
summary.mdcarries a line naming the months, credits and grant (true-up due) and the manifest'slicence.over_grantlists them. The amount is settled as in True-Up. The grant withholds nothing by itself (a licence withoutpaperworkstill withholds the paperwork of its over-allowance months).
Lawful orders
If you receive a legal summons or another lawful order for your records and the paperwork it needs is withheld,
Laidline issues you a Legal-Process Licence, free of charge and promptly once the items below arrive (clause 12
of the customer agreement). Laidline never holds your logs: they stay on your servers, and your own deployment
produces the export. The licence-required refusal (a tenant filter without tenant_reports; withheld paperwork is
not a refusal) ends with a note pointing here.
A Legal-Process Licence is an ordinary licence file covering the period demanded:
- features
exportandpaperwork, plustenant_reportswhere the order concerns one tenant; covers_fromset to the first month the order demands;- a validity window on month boundaries equal to the demanded months:
not_beforethe first instant of the first demanded month,not_afterthe first instant after the last demanded month,renewal.grace_days0, so the licence unlocks exactly the demanded months and nothing after them (a licence with a short validity from its issue date andcovers_fromat the first demanded month would unlock every month up to the issue date); - bound to your signing key like any licence.
Such a licence is already past its window when it arrives, so on status surfaces it shows licence_expired:lapsed
if it is your only licence; the export of the demanded months is unaffected. An export that carries only this
licence holds it as licence/licence.json; a bundle that carries several licences holds each as
licence/<licence_id>.json. Where a term licence also unlocks a demanded month, the month is attributed to the
licence with the latest not_after.
You install it (laidline licence install) and your deployment then exports the full signed bundle, with the
Measure texts and Laidline's licence attestation, for exactly those months. It is the same laidline.licence
document, verified the same way.
The process is described in Laidline's legal-process policy at https://laidline.com/legal/legal-process and in clause 12 of the customer agreement:
- What to send to legal@laidline.com: a copy of the order, the name of the issuing court or authority, its file reference and a contact there. A deployment that is not registered also sends the public certificate chain of its signing key with the order, so the licence can be bound to that key.
- Timing: Laidline issues promptly once those items arrive. It may check the order with the issuing court or authority, without delay, and may wait for that reply before issuing.
- Refusal: Laidline refuses a Legal-Process Licence only for a failed check.
- Limits: a Legal-Process Licence unlocks
exportandpaperworkfor the demanded months, andtenant_reportswhere the order concerns one tenant. It licenses no marking, and it never satisfies the software licence's condition for marking above the allowance (that needs a Laidline licence other than a Legal-Process Licence). It creates no fee and settles no fees or claims for the demanded months.
Licence states
| State | When |
|---|---|
free_within_cap |
no valid bound licence; this month's credits at or under the allowance (or no valid weight table) |
over_cap_unlicensed |
no valid bound licence; this month's credits over the allowance |
licensed |
a valid licence bound to this deployment covers now; credits within its grant (or unlimited) |
licensed_over_grant |
as licensed, credits above the grant (a True-Up, not a lock) |
licence_expired:grace |
the newest licence's not_after has passed by less than its grace days |
licence_expired:lapsed |
past the grace window |
licence_invalid:<reason> |
licence files are present but none is valid and bound: bad_signature, unknown_signer, revoked, not_bound, malformed, not_yet_valid, signer_expired, signer_not_yet_valid, signer_not_permitted |
Flags and warnings:
meter_unknown: the in-process tally has not finished rebuilding, or the evidence sink cannot be read. It appears on operator surfaces only and never decides a lock.weights_invalid: no shipped weight table verified, so the credits are unknown, no cap applies and nothing is withheld (laidline licence statusprintscredits: unknown (no weight table); the usage report lists the warning). Reinstall Laidline if you see it. A healthy installation never shows it.weights_invalid:licence(the licence's inline table is malformed) andweights_unknown:licence(the licence names a table version this installation does not ship): a table carried in a licence could not be used, so the shipped table priced the month instead. They appear inlaidline licence status(theflags:line and the JSONflagslist), as warnings in the usage statement, the usage report and the export manifest, andlaidline servelogs one warning per process for each.clock_in_doubtandissuer_state:<state>.
Changes of licence state are not recorded as events in the evidence chain.
Clock. Licence validity is judged against the later of the system clock and the latest TSA time verified
against the TSA roots (signing.tsa.roots_path). The default, builtin, is the public DigiCert and Sectigo roots
that ship inside Laidline, so tokens from the default TSAs are verified when they arrive on every deployment, with no
setup. If you use another TSA, set signing.tsa.roots_path to its published roots; the literal builtin covers only
timestamp.digicert.com and timestamp.sectigo.com, so with any other host configured it loads no roots. The usage
statement says clock: customer_only (no TSA time is used for licence purposes) when roots_path is null or empty,
when no TSA URL is configured, when a checkpoint covering the month has no verified token (for instance a TSA outage
while it was written), or when roots_path is builtin with a TSA host other than those two. A finished month
still closes in all of those cases: only a covering checkpoint whose token was checked against the roots in force and
failed (verified_against_roots: false) blocks closing, because absence of a token is not failure. A failed verdict
is permanent for that month in the statement (the covering checkpoint is the first one past the month's end and never
changes); the export still verifies the token with --tsa-roots pointing at the roots it chains to. Each TSA URL is
tried for a token that verifies; when none does, the first well-formed token is still stored, marked
verified_against_roots: false, so a roots file never costs a checkpoint its timestamp. Only verified tokens feed
licence decisions.
Late anchoring. The usage statement flags late_anchoring:<stream> for each evidence stream whose covering
checkpoint carries a timestamp that was verified against your TSA roots and is dated more than one day after the end
of the month. Verified times exist by default (the built-in roots, for the default TSAs); the flag is never set in
the cases listed under Clock, for instance signing.tsa.roots_path null or empty, or a timestamp authority that
is not reachable.
Licence files
A licence is a JSON document signed by Laidline (laidline.licence, Ed25519 over the JCS canonical form). It names
the licensee, its validity (not_before, not_after, renewal.grace_days), covers_from, the issuer certificate
serial, the keys it binds (bound_keys.signing_spki_sha256: the SHA-256 of your signing key's public key, the value
checkpoints record as signer.public_key_sha256; or org_ca_sha256: your organisation CA certificate), the grant
(units_per_month or unlimited, optionally its own weight table or lock mode) and the features it includes.
Where Laidline looks:
licence.path(defaultlicence.dbeside the config file): one file or a directory of*.jsonfiles; orLAIDLINE_LICENCE: the licence JSON, base64 on one line (it replaces the path).
Several files may coexist: the valid file bound to this deployment with the latest not_after decides the state;
every other file is reported as a warning with its reason and never decides the state, so a bad file dropped beside
a good one cannot lock you out. Each file and the variable are limited to 1 MiB; anything larger or not parseable is
reported as malformed.
Binding is proven by evidence, never by a configuration file alone (certificate chains are public), and a licence unlocks evidence entries, not whole months of whatever a bundle holds. An entry is licensed when a licence unlocks its month and binds either the active signing key that signs the manifest (once the signing backend has shown it holds that key), or the key that signed a checkpoint whose range holds the entry and whose head matches the log. A month is unlocked only when every deciding entry in it is licensed. So a key rotation does not relock months a licence paid for (the retired key's checkpoints cover them), while another deployment's stream copied into a sink licenses only that stream's entries. An organisation CA counts only on the verified certificate path upward from such a key. Laidline reissues a licence bound to a new key.
Install and renew: laidline register builds the request (your licensee name, signing key fingerprint and chain, your
current issuer number, the fingerprints of your active payload key and your acceptance of the customer agreement;
signed with your signing key; no secrets and no usage unless you add --usage), sends it to the issuing service and
installs the licence and the issuer certificate it returns.
The request document (laidline.request) carries the acceptance as a terms block, {"version", "url", "accepted_at"}: the agreement's version and URL as the package ships them (laidline.licence.terms) and the moment
you accepted, in UTC to the second (2026-10-18T09:30:15Z). The signature covers the block like every other field,
so the acceptance is part of the durable signed record the issuing service keeps, not only of the POST body. The
issuing service refuses a request without the block (terms_required) or with a version it does not accept
(terms_unknown). Renewal is a new file; keep the old one as proof of the months it covered. To install a file you
received by another channel, use laidline licence install and laidline issuer install, in either order, then
reload. The commands are in Commands.
The documents proof. The issuing service returns the licence and issuer certificate of an issued registration
only to the deployment whose signing key the registration binds. A request for them carries a Laidline-Proof
header: a short statement (the registration, the issuing service's address, the time and a random nonce) signed with
the deployment's active signing key. Without a proof, or with one from a key the registration does not bind, the
answer still gives the registration's status, with "documents": "proof_required" in place of the documents. A
proof counts for one request within five minutes of the issuing service's clock, so every fetch signs a new one. The
deployment signs each fetch with its registered key; a rotated key must be bound before it can fetch: run laidline register again with the new key active. Nothing secret is stored beside the licence files, and a host that holds the
signing key (or access to the same KMS key) can fetch.
Automatic renewal
A deployment fetches its renewed licence on its own. When the newest installed licence bound to it is within 7 days
of its not_after, or past it but inside the grace, the server (laidline serve, 30 seconds after start and then
daily) and the commands that read the licence (laidline licence status, laidline usage, laidline evidence export) ask the issuing service for the documents of the registration recorded in licence-fetch.json, with a
documents proof signed by the active signing key (the documents proof), at most once per UTC day.
laidline register and laidline licence fetch record that registration when they install its documents. A
command run without the signing credentials (for example laidline licence status from a shell or cron that cannot
reach the KMS key) logs that it cannot sign the proof and does not use up the day's attempt, so the server still
fetches that day. The issuing service answers with your newest licence and current issuer certificate; when the
answer names a different registration as current (current_registration_id), the file records that one for the next
attempt. The file sits beside the licence directory (beside the config file by default) and also holds the time and
outcome of the last attempt.
A fetched licence is installed only when it verifies under the pinned roots, binds this deployment's signing keys,
has not lapsed and is newer than every installed licence (a later not_after, or the same with a later issued_at);
an issuer certificate in the same answer is installed when it maps the same number and ends later than the installed
one. The previous licence stays installed as proof of the months it covered. A failed or empty fetch never stops or
changes marking: it writes one line to the server log (or to stderr for a command) and is tried again the next day.
The server picks up a new licence within the hour, or at once on SIGHUP. licence.auto_fetch: false turns
automatic renewal off for an air-gapped host, as do LAIDLINE_OFFLINE=1 and a set LAIDLINE_LICENCE (the variable
shadows any installed file); renew such a host with laidline licence install.
More than one host
One organisation registers once. A second host of the same deployment does not register again: a second request from
the same email domain is denied at once on the one-key plans (registered_free and lite), with the hint to copy
the registered host's identity, and goes to review on plans that bind more keys (Starter 2, Growth 5, Scale 10),
where you register with --plan starter, growth or scale from the second host. The second host carries the
registered host's identity instead: laidline.yaml (its watermark.issuer_id included), payload-k1.key, the
signing key and chain (certs/, or access to the same KMS key), the licence files in licence.d/ and
issuer-certificate.json. A host that holds only the licence and the issuer certificate stays
licence_invalid:not_bound, because the licence binds the signing key of the host that made the request; a host
whose watermark.issuer_id is neither the number the certificate maps nor one of the trial numbers it lists as its
own prior numbers (prior_trial_ids; laidline register leaves the file as init wrote it) keeps making shared marks
while serve logs certificate_invalid:number_mismatch. Grade B (confirmed_bytes) on a read needs the asset's registry entry, which
is in the evidence sink of the host that made the mark: give every host of the deployment the same authoritative
evidence sink (s3 or postgres, Sinks); with a per-host file sink a second host reads the
deployment's own registered marks as present_unverified grade D with registry: missing.
Licences in an export
A licensed export carries the licence that unlocks each exported month (licence/licence.json for one licence,
licence/<licence_id>.json each for several, for example after a renewal) and licence/laidline-roots.json; the
issuer certificate travels as licence/issuer-certificate.json once issuer.certificate_path is allowed. The
manifest's licence.licences lists which months each licence unlocks. The bundle's verify.py checks each licence
against the Laidline roots pinned in the script itself (not the bundle's copy), counts only keys that verifiably
signed the bundle (the manifest signer when manifest.sig verified, checkpoint signers whose signatures verified, an
organisation CA on a verified path from one of them; a checkpoint counts only when its head also matches), applies
the same entry-level rule, and reports licence: verified (covers <months>) (at least one month in range covered
and none uncovered), not_covering_range, invalid (<reason>), absent or unsigned_pack (a licence in a bundle
whose manifest is not signed is never reported verified). Months present in the bundle outside the requested range
and not covered are named on a second line.
Commands
laidline register --email ADDRESS --display-name NAME --accept-terms [--plan registered_free] [--interval month|year] [--country CC] [--detect-url URL] [--disclosure-url URL] [--name LEGAL_NAME] [--usage NOTE] [--wait|--no-wait] [--timeout 30m]: registers this deployment. Registration publishes the display name (it must contain the verified domain's label) and the domain beside the issuer number;--displaytakes onlypublic, and--display withheldexits 2. A deployment that does not want a public name stays unregistered, on shared marks.--accept-termsaccepts the Laidline Customer Licence Agreement; the request carries the signedtermsblock (version, URL,accepted_at). Without the flag the command asksAccept the Laidline customer licence agreement <version> at <url>? [y/N]on a terminal (ycounts as the flag); otherwise, or on any other answer, it prints the version and URL and exits 1 before anything is signed or sent. It needs an active payload key (laidline initwrites one; exit 2 otherwise), signs the request with the configured signing key (a KMS, Vault or local key all work) and sends it toissuer.registration_url. When the answer isemail_pendingit prints the address and polls every 5 seconds until--timeout; onissuedit verifies and installs both documents and prints a summary; onrevieworpayment_pendingit prints the reason (or the checkout URL), a hint for the common reasons, and the resume command, and stops waiting. A refused registration exits 1. Running it again afterlaidline payload-keys generatebinds the new key. With an active subscription in the customer portal on your domain,laidline register --plan <paid plan>issues on email verification without a checkout; otherwise the command prints the checkout URL, andlaidline licence fetch REGISTRATION_IDresumes after payment.--intervalsets how a paid plan is billed:month(the default) oryear; every request carries it, and with--plan registered_freeit is accepted and has no effect.laidline licence fetch REGISTRATION_ID [--timeout 30m]: resumes a registration, polls until the documents are issued (printing each state change once), then verifies and installs them; likeregister, it stops onreview(the reason, the hint for the common reasons and the resume command, exit 0) and onpayment_pending(the checkout URL) instead of waiting, and a refused registration prints the hint for the common reasons and exits 1; if--timeoutpasses first it printsstopped waitingand the resume command and exits 0: checklaidline licence status.laidline licence install FILE: verifies alaidline.licenceunder the pinned roots and copies it into the licence directory (licence.path, defaultlicence.d).laidline licence status [--json]: the licence state, plan (an earlier plan id shows as the plan that replaced it,starter (the licence says registered); in JSONplanis the current id andlicence_planthe licence's), allowance, credits used this month per medium, private and shared mark counts, the switch event if there was one, the issuer line (a certificate whose number the config does not use is reported asnumber_mismatchwith the number to set), theflags:line (the JSONflagslist; shown only when a flag applies) and the directory snapshot with its staleness caveat. Likeusageandevidence export, it first fetches a renewed licence when one is due (Automatic renewal).laidline issuer install FILE: installs an issuer certificate or a directory snapshot (by itstype), verified under the pinned roots; a directory's serial must not go backwards.laidline issuer refresh [--url URL]: fetches the directory snapshot fromissuer.directory.refresh_url, verifies it and writes it; on any refusal the installed file stays and the command exits 1. Run it by hand or from cron.laidline usage [--month YYYY-MM] [--tenant TENANT]: prints the signed usage statement of a month (default: this month) from the authoritative evidence sink. A statement carriessuggested_band: the smallest plan whose monthly figure covers the month's credits (registered_freeto 1,200,liteto 15,000,starterto 150,000,growthto 1,000,000,scaleto 10,000,000, thenenterprise; no prices appear in the package).--tenantneeds a licence withtenant_reports; without it the command exits 3.laidline usage report [--month YYYY-MM]: sends the monthly usage report (below). The month must be complete (default: the previous one).
register and licence fetch poll the registration's status without a proof and fetch the documents of an issued
registration with one, signed by the active signing key. When the issuing service answers proof_required, both exit
1 with the issuer did not accept this deployment's signing key as the registration's; bind it by running laidline register again (a host clock more than five minutes off, or an issuer.registration_url that is not the issuing service's address, also causes this), and automatic renewal logs the same reason; marking is unaffected. See
the documents proof.
register, licence install and licence fetch warn when LAIDLINE_LICENCE is set, because the variable shadows
the file just written. Every document is written atomically, so a reload or a concurrent worker never reads a half
file. Every command and its options are in the Command line reference.
The usage report
The report ties the allowance to the registration. It holds the month, the payload key_fp per medium, the credits
per medium, the counts of private and shared marks, the payload_key_switch event if there was one (the earliest,
when replicas each wrote one), the warnings list (for example weights_invalid:licence or
weights_unknown:licence when the month's licence carries a table that could not be used; an empty list otherwise;
the issuing service keeps it with the report) and the product version. It never holds media, prompts or file names,
and it adds no email address of its own. It is signed with the registration's signing key (the same convention as
the request) and carries that key's public certificate chain, so the issuing service can tie it to the account; the
chain holds whatever your certificate holds (an email address in its subject, for example), and the issuing service
receives that.
- Best effort: marking never waits on it, and nothing is checked online before a mark. A failed send from
laidline serveis logged and tried again the next day; the CLI exits 1 and you run it again. issuer.usage_report: falseturns it off (laidline usage reportthen prints that it skipped).laidline serveruns a task that, once a day, sends last month's report when the deployment is registered (an issuer certificate is installed), the setting is on and theusage-reported-<month>marker is absent from the first file sink's evidence directory. The first check runs a minute after start.- The issuing service keeps one report per signing key per Month; when a key reports a Month twice, the higher is
kept. The
usage-reported-<month>marker only saves work in ephemeral containers and on replicas, and the issuer's answer is what decides. - It cannot stop a modified worker from lying. The local switch keeps honest deployments honest; the report, the directory and the agreement's audit and True-Up clauses stand against the rest.
Issuer numbers
The issuer number is 1 to 65,535 everywhere. Bit 15 of the 16-bit issuer field, a wire-format constant, splits registered numbers from trial numbers:
| Range | Numbers | Meaning |
|---|---|---|
| registered | 1 to 32,767 | allocated by Laidline with a signed issuer certificate and never allocated to anyone else. It transfers only to a successor of your business under the customer agreement; after the agreement ends, a transfer takes effect no earlier than 90 days after the end, and the directory shows the number as registered to you until the end date throughout that delay |
| trial | 32,768 to 65,279 | drawn at random by laidline init; unregistered, shared, no sign-up; trial numbers may collide |
| reserved | 65,280 to 65,535 | documentation, tests and benchmarks |
No issuer condition ever stops marking. The issuer state is reported: trial, registered,
registered_no_certificate, certificate_invalid:<reason> (bad_signature, unknown_signer, malformed,
unreadable, number_mismatch, not_bound, revoked), certificate_expired, not_yet_valid, reserved.
Detection always shows a payload's number as a claim ("claimed by issuer N, unconfirmed"); the issuer directory adds
facts about the number (registered to a name, operator of a domain, per a dated snapshot), never an identity for the
mark. The sentences are listed in Issuer lines.
The directory snapshot (laidline.issuer-directory) ships in the package and carries the revocations (licences,
issuing keys, issuer certificates) and the shared pool key (Payload keys: The shared family and the private key).
laidline issuer refresh fetches a newer one and laidline issuer install FILE installs one by hand. A refreshed
snapshot must verify and its serial must not go backwards.
The Laidline roots
Laidline signs with two offline Ed25519 root keys, which sign yearly issuing-key certificates; the issuing keys sign
licences, issuer certificates, directory snapshots and weight tables. Either root alone can certify an issuing key.
The root public keys are pinned in the package (laidline.trust.roots, the tuple ROOT_KEYS) and copied into every
bundle's verify.py.
| Root | Key id | Public key (Ed25519, hex) | Not after |
|---|---|---|---|
| A | 71541a379b752801 |
5fe902399350edec70b5038d8d85fb76d107f2e88337827a9fc475938a131fc8 |
2036-07-28 |
| B | c60506fddbdb4685 |
4965a3e1d6ad06300cfeec5ae4fee087fedecea33cc8c5d2ce2534f3612fd3fa |
2036-07-28 |
The issuing keys (certified by root A) are 88a993b1cc6b3d35 (licences, issuer certificates, weight tables) and
c24f4a81f5625273 (the issuer directory only). The directory snapshot in the package is signed by
c24f4a81f5625273 and carries one shared pool key, pool-0b19a1bc728c92e5; the weight table 1.0.1 in the package is
signed by 88a993b1cc6b3d35. If the directory cannot be verified (for example a refresh was refused), marking uses
the configured payload key and the trial number and logs one warning.
Only issuer.extra_roots_file adds a root, and only under LAIDLINE_ENV=development: point it at a roots file
({"roots": [{key_id, public_key, not_after}]}, the shape of an export's licence/laidline-roots.json). Without
LAIDLINE_ENV=development the file is never opened.
Troubleshooting
| Message | Cause | Fix |
|---|---|---|
error: registration needs the Laidline customer licence agreement <version> at <url>; read it, then run again with --accept-terms |
laidline register ran without --accept-terms outside a terminal, or you answered no |
Read the agreement, then run the command again with --accept-terms. |
error: --name is required when system.provider_legal_name is not set |
The request needs a licensee name | Set system.provider_legal_name in laidline.yaml, or pass --name. |
error: no active payload key; run laidline init first |
The configuration has no active payload key to bind | Run laidline init, or laidline payload-keys generate and add the key. |
error: --display withheld is not offered: ... |
Registered names are always published | Leave --display out; to stay unnamed, stay unregistered on shared marks. |
state: licence_invalid:not_bound |
The licence binds another host's signing key, usually a licence copied without the identity | Copy the whole identity (More than one host), or register this deployment. |
set watermark.issuer_id: N in laidline.yaml and reload; marks stay shared until then |
The configured issuer number differs from the number the certificate maps | Set watermark.issuer_id to N and reload. |
state: licence_expired:grace |
The renewed licence was not fetched: licence.auto_fetch: false, LAIDLINE_OFFLINE=1, a set LAIDLINE_LICENCE, or no network |
Run laidline licence fetch REGISTRATION_ID, or install the renewed file with laidline licence install. |
credits: unknown (no weight table) |
No shipped weight table verified (weights_invalid) |
Reinstall Laidline. |
credits: unknown (no evidence sink) |
The authoritative evidence sink cannot be read | Check evidence.sinks in the configuration. |
self-test cap reached (10 per day); this mark is metered |
More than 10 start-up self-test marks in one UTC day: frequent restarts, or a self-test retried while it fails | Find why the server restarts or why the self-test fails (/readyz names it); the extra marks are counted as credits. |
a per-tenant statement needs a licence with the tenant_reports feature (exit 3) |
--tenant without a licence carrying tenant_reports |
Tenant reports come with Enterprise; drop --tenant otherwise. |
directory: directory snapshot of <date> is older than <N> days; ... |
The installed directory snapshot is stale | Run laidline issuer refresh. |
Reference
- Keys: the shared pool key and your private payload key.
- Evidence: the evidence log, meter blocks and the export bundle.
- Grading: the issuer lines detection prints.
- Configuration: the
licenceandissuersettings. - Production setup: registering a production deployment.
- Command line reference: every command and option.
- Laidline Customer Licence Agreement: the terms.
- Prices: the plans with their prices.