Create a chain.
Session-only.
There is no is_default, and until v0.115.1 this said there was —
sending it got a 201 and no effect, because unknown fields are ignored.
A chain is made the default by a device binding or an API token; see
the Chain schema.
A new chain is never shared. Sharing is switched on deliberately, from
the chain drawer or Patch → Decks, and never as a side effect of
creating something.
Request body required
application/json
object
description string | null
components array required
each item object
type string required
What kind of box a step is.
sourcetransportdacamptransducernetworkbluetooth
name string required
Always written, so deleting the gear leaves the chain readable.
detail string | null
Free text about this step. Always present; null when there is none.
Guessed from the gear type when a step is first picked and never
re-applied, so an override sticks.
equipment_id integer | null · int64
The equipment row this step is, set when it was picked from your
gear. Omitted for free text — a chain can name something you do not
own (a friend's amp, a rental).
since integer | null · int64
Unix seconds from which this box was in the path. Omitted means "for
as long as the chain has existed", which is what every step written
before this field meant.
Chain and gear hours are derived from the listens, so without it a box
added to a chain that already has history is credited with all of it.
Stamped automatically on gear a chain did not name before; send one to
correct the date, or null to say it was always there.
listening_context string
How you listen through a chain. Attention, not company — the two are
orthogonal, and who you were with is company on the listen.
activeactive-mobilepassivebackgroundunknown
icon string | null
A glyph name from the UI's set. Not validated here — an unknown value
falls back to a generic mark on render, which is a better failure than
rejecting a save because the front end learned a new icon first.
Responses
application/json
object
A row that was just created.
id integer · int64 required
400 Malformed or rejected input.
application/json
object
Every error body in the API has this shape.
code integer · int32 required
error string required
Human-readable. Not a stable identifier — do not branch on it.
401 No valid session cookie or token. Also returned when a token is
presented to a session-only endpoint — the endpoint does not accept
tokens at all, so the scope is irrelevant.
application/json
object
Every error body in the API has this shape.
code integer · int32 required
error string required
Human-readable. Not a stable identifier — do not branch on it.
{
"code": 401,
"error": "Authentication required. Log in to access this endpoint."
}
409 A chain of that name already exists for this user.
application/json
object
Every error body in the API has this shape.
code integer · int32 required
error string required
Human-readable. Not a stable identifier — do not branch on it.
PATCH /api/v1/chains/{id} # Session
Update a chain.
Session-only, and a whole-record replace despite the verb — it takes
the same body as the create. Every field the editor owns is written, so
an omitted icon, description or listening_context is cleared.
Send the full record back.
Two fields it deliberately does not touch: shared, which is
switched from the chain drawer or Patch → Decks — writing it here would
un-publish a chain as a side effect of renaming it — and total_hours,
which is derived from the listens.
A component naming gear the chain did not name before is stamped with
since at the moment of the save. That is what stops a box added to a
chain with history being credited with all of it; send your own since
to correct the date, which is usually a few days before anybody got
round to recording it.
Request body required
application/json
object
description string | null
components array required
each item object
type string required
What kind of box a step is.
sourcetransportdacamptransducernetworkbluetooth
name string required
Always written, so deleting the gear leaves the chain readable.
detail string | null
Free text about this step. Always present; null when there is none.
Guessed from the gear type when a step is first picked and never
re-applied, so an override sticks.
equipment_id integer | null · int64
The equipment row this step is, set when it was picked from your
gear. Omitted for free text — a chain can name something you do not
own (a friend's amp, a rental).
since integer | null · int64
Unix seconds from which this box was in the path. Omitted means "for
as long as the chain has existed", which is what every step written
before this field meant.
Chain and gear hours are derived from the listens, so without it a box
added to a chain that already has history is credited with all of it.
Stamped automatically on gear a chain did not name before; send one to
correct the date, or null to say it was always there.
listening_context string
How you listen through a chain. Attention, not company — the two are
orthogonal, and who you were with is company on the listen.
activeactive-mobilepassivebackgroundunknown
icon string | null
A glyph name from the UI's set. Not validated here — an unknown value
falls back to a generic mark on render, which is a better failure than
rejecting a save because the front end learned a new icon first.
Responses
application/json
object
A row that was just updated.
id integer · int64 required
401 No valid session cookie or token. Also returned when a token is
presented to a session-only endpoint — the endpoint does not accept
tokens at all, so the scope is irrelevant.
application/json
object
Every error body in the API has this shape.
code integer · int32 required
error string required
Human-readable. Not a stable identifier — do not branch on it.
{
"code": 401,
"error": "Authentication required. Log in to access this endpoint."
}
404 No such resource, or it belongs to another user.
application/json
object
Every error body in the API has this shape.
code integer · int32 required
error string required
Human-readable. Not a stable identifier — do not branch on it.
GET /api/v1/chains/variants # Token reador Session
Every chain variant the caller owns.
Session or a token carrying read. A variant is a chain with one step
swapped — two cartridges on one turntable, or the same commute on
different IEMs. They are one chain and a choice, not two chains.
Flat rather than nested under each chain, because the page groups them
itself and one list beats a request per chain.
total_hours here is the listening that went through this variant.
A chain's own total_hours includes every variant of it, since they all
ran through that chain.
Responses
application/json
object
variants array required
each item object
A chain with one step swapped — two cartridges on one turntable, or the
same commute with different IEMs, are one chain and a choice.
id integer · int64 required
user_id integer · int64 required
chain_id integer · int64 required
The chain this is a variant of.
overrides array required
Only the steps that differ from the chain.
each item object
One step of a chain, replaced.
position indexes into the parent chain's components. Only the steps that
differ are stored, which is what makes a variant an override rather than a
copy: editing the shared part of a chain updates every variant of it, and
two cartridges on one turntable stay one chain.
A position past the end of the base components adds a step, so a chain
that never named a cartridge can gain one without being rewritten.
position integer · int64 required
Index into the chain's components. One past the end adds a step.
equipment_id integer | null · int64
The gear fitted at this step. Null removes the step for this variant —
a chain run without its amp, say.
name string | null
Snapshot of the name, so a deleted gear row leaves the variant readable
instead of blank.
created_at integer · int64 required
total_hours number · double required
The listening that went through this variant, which is a different
question from a chain's total — a chain's hours include every variant
of it, since they all ran through the same chain.
401 No valid session cookie or token. Also returned when a token is
presented to a session-only endpoint — the endpoint does not accept
tokens at all, so the scope is irrelevant.
application/json
object
Every error body in the API has this shape.
code integer · int32 required
error string required
Human-readable. Not a stable identifier — do not branch on it.
{
"code": 401,
"error": "Authentication required. Log in to access this endpoint."
}
403 Authenticated, but not permitted. Either the token lacks the required
scope, or the endpoint needs the admin role. Deliberately not a 401 —
re-authenticating will not help.
application/json
object
Every error body in the API has this shape.
code integer · int32 required
error string required
Human-readable. Not a stable identifier — do not branch on it.
{
"code": 403,
"error": "This token does not have the 'write' scope"
}
POST /api/v1/chains/variants # Session
Create a variant.
Session-only. Only the steps that differ are stored, which is what
makes this an override rather than a copy: editing the shared part of a
chain updates every variant of it.
The chain and every piece of gear named must be the caller's.
Request body required
application/json
object
chain_id integer · int64 required
overrides array
each item object
One step of a chain, replaced.
position indexes into the parent chain's components. Only the steps that
differ are stored, which is what makes a variant an override rather than a
copy: editing the shared part of a chain updates every variant of it, and
two cartridges on one turntable stay one chain.
A position past the end of the base components adds a step, so a chain
that never named a cartridge can gain one without being rewritten.
position integer · int64 required
Index into the chain's components. One past the end adds a step.
equipment_id integer | null · int64
The gear fitted at this step. Null removes the step for this variant —
a chain run without its amp, say.
name string | null
Snapshot of the name, so a deleted gear row leaves the variant readable
instead of blank.
Responses
application/json
object
id integer · int64 required
400 Malformed or rejected input.
application/json
object
Every error body in the API has this shape.
code integer · int32 required
error string required
Human-readable. Not a stable identifier — do not branch on it.
401 No valid session cookie or token. Also returned when a token is
presented to a session-only endpoint — the endpoint does not accept
tokens at all, so the scope is irrelevant.
application/json
object
Every error body in the API has this shape.
code integer · int32 required
error string required
Human-readable. Not a stable identifier — do not branch on it.
{
"code": 401,
"error": "Authentication required. Log in to access this endpoint."
}
POST /api/v1/chains/variants/{id}/promote # Session
Make this setup the chain, and the chain a setup.
For the common case where the variant is the one you actually use — the
other cartridge is in the headshell most of the time, so it should be
what the chain is.
A relabelling, not a change. What was fitted for any given listen is
identical afterwards, and so is every piece of gear's hours.
That is harder than it looks, and it is the whole of this endpoint. An
override is {position, equipment_id} relative to the base, so
another variant that swaps position 2 means "the base, but with X at 2"
— change the base and that sentence names different gear at position 1,
silently, with hours landing on a box that was never fitted. So every
variant is resolved to an absolute list of steps first and re-expressed
against the new base afterwards. Listens follow: those on the chain move
to the new setup that restores it, and those on the promoted setup move
to the chain.
name is what to call the setup the chain is demoted to — the old base
has no name of its own, because it was the chain. Defaults to
"Previous setup".
Refuses a setup that removes a step (400): removal shifts every
later position, so promoting one would change what every other setup's
overrides point at. Nothing in the UI creates such a setup.
Request body required
application/json
object
GET /api/v1/chains/variants — every variant the caller owns.
Flat rather than nested under each chain: the page needs them keyed by
name string | null
What to call the setup the chain is being demoted to. The old base has
no name of its own — it was the chain — so one has to be supplied.
Responses
200 The promoted setup, and the id of the one the chain became.
application/json
object
A setup promoted to be the chain.
promoted integer · int64 required
The setup that is now the chain.
old_base_variant_id integer · int64 required
The variant the old chain became.
400 That setup removes a step, or has none.
application/json
object
Every error body in the API has this shape.
code integer · int32 required
error string required
Human-readable. Not a stable identifier — do not branch on it.
401 No valid session cookie or token. Also returned when a token is
presented to a session-only endpoint — the endpoint does not accept
tokens at all, so the scope is irrelevant.
application/json
object
Every error body in the API has this shape.
code integer · int32 required
error string required
Human-readable. Not a stable identifier — do not branch on it.
{
"code": 401,
"error": "Authentication required. Log in to access this endpoint."
}
application/json
object
Every error body in the API has this shape.
code integer · int32 required
error string required
Human-readable. Not a stable identifier — do not branch on it.
GET /api/v1/chains/duplicates # Session
Chains that look like the same rig, and what folding one would do.
Session-only. The discovery half of the fold below, and the same
preview-then-apply shape the metadata sanitiser uses: this reports, it
never acts.
A pair is reported when the two chains disagree on at most two steps.
distance is how many — 0 is the same wiring under two names, 1 is
the swapped-box case (the same commute on different IEMs) that variants
exist for. Past two they are simply different rigs, and a variant
overriding half a chain is a copy wearing an override's clothes.
The chain with more listening is reported as the base, since it
keeps its id and fewer rows move — but the fold takes an explicit
direction, so this is a suggestion rather than the decision.
Responses
application/json
object
Pairs of chains that look like the same rig. Nothing has been changed.
duplicates array required
each item object
One pair of chains that look like the same rig, and what folding would do.
Reported, never acted on — the same preview-then-apply discipline the
metadata sanitiser follows, and for the same reason: a fold is not
reversible, and a plurality of one step is a weak signal the operator should
see before agreeing to it.
base_chain_id integer · int64 required
base_name string required
base_hours number · double required
fold_chain_id integer · int64 required
fold_name string required
fold_hours number · double required
distance integer · int64 required
How many steps disagree. 0 is the same wiring under two names.
overrides array required
What the folded chain becomes as a variant of the base.
each item object
One step of a chain, replaced.
position indexes into the parent chain's components. Only the steps that
differ are stored, which is what makes a variant an override rather than a
copy: editing the shared part of a chain updates every variant of it, and
two cartridges on one turntable stay one chain.
A position past the end of the base components adds a step, so a chain
that never named a cartridge can gain one without being rewritten.
position integer · int64 required
Index into the chain's components. One past the end adds a step.
equipment_id integer | null · int64
The gear fitted at this step. Null removes the step for this variant —
a chain run without its amp, say.
name string | null
Snapshot of the name, so a deleted gear row leaves the variant readable
instead of blank.
matching_variant array | null
A variant of the base that is already wired this way, as
[id, name] — the state someone is in when they built the variant and
found the old chain still sitting in the list beside it.
fold_blocked_by_variants boolean required
This one carries variants of its own, so it cannot be the side that
folds. Surfaced here rather than left for the fold to refuse.
401 No valid session cookie or token. Also returned when a token is
presented to a session-only endpoint — the endpoint does not accept
tokens at all, so the scope is irrelevant.
application/json
object
Every error body in the API has this shape.
code integer · int32 required
error string required
Human-readable. Not a stable identifier — do not branch on it.
{
"code": 401,
"error": "Authentication required. Log in to access this endpoint."
}
POST /api/v1/chains/{id}/fold # Session
Fold this chain into another as a variant of it.
Session-only. Not reversible.
Every listen, side, session, binding, device default and token default
of this chain is repointed at into_chain_id carrying the variant, and
this chain is then deleted. Because gear hours resolve per
(chain, variant) pair, the moved listening lands on the gear that was
actually fitted — which is what makes two near-duplicate chains' hours
add up instead of sitting in two rows that cannot be compared.
Send either variant_id (a variant of the target chain you already
built) or variant_name (derive a new one from the difference
between the two chains). Sending both, or neither, is a 400: on an
endpoint that cannot be undone, "use the one I built" and "make me
another" must not be the same request with a typo between them.
Refused with a 400 naming the reason when: the chain is folded into
itself, the variant belongs to a different chain (it would resolve to
components never fitted together), the name is already taken on the
target, or this chain carries variants of its own — their positions
index into components the fold replaces, and re-deriving them would be a
guess about which box was fitted.
Request body required
application/json
object
What a fold request has to say. Deliberately not one nullable field.
variant_id (land on one that exists) and variant_name (derive a new one
from the two chains) are different intents, and a single nullable field
would make "use the variant I built" and "make me another one" the same
request with a typo between them — on an endpoint that is not reversible.
into_chain_id integer · int64 required
variant_id integer | null · int64
variant_name string | null
Responses
200 Folded. The counts are what actually moved.
application/json
object
What a fold moved, so the caller can say it rather than claim success.
Reported per table because they are different kinds of thing: listens
are the hours, a token or a device default is future attribution, and
a record on the shelf is the turntable it lives with.
variant_id integer · int64 required
variant_name string required
variant_created boolean required
listens integer · int64 required
sides integer · int64 required
sessions integer · int64 required
releases integer · int64 required
devices integer · int64 required
bindings integer · int64 required
tokens integer · int64 required
400 Malformed or rejected input.
application/json
object
Every error body in the API has this shape.
code integer · int32 required
error string required
Human-readable. Not a stable identifier — do not branch on it.
401 No valid session cookie or token. Also returned when a token is
presented to a session-only endpoint — the endpoint does not accept
tokens at all, so the scope is irrelevant.
application/json
object
Every error body in the API has this shape.
code integer · int32 required
error string required
Human-readable. Not a stable identifier — do not branch on it.
{
"code": 401,
"error": "Authentication required. Log in to access this endpoint."
}
404 No such resource, or it belongs to another user.
application/json
object
Every error body in the API has this shape.
code integer · int32 required
error string required
Human-readable. Not a stable identifier — do not branch on it.
Add gear.
Session-only. The type field is type here and comes back as
equipment_type — see the Equipment schema.
Request body required
application/json
object
purchased_at integer | null · int64
When it was bought, unix seconds. Distinct from first_used, which is
when Tapedeck first saw it — gear is usually owned for a while before
anyone gets round to cataloguing it, and burn-in hours are counted from
the purchase.
Responses
201 Created — or matched. Gear is keyed on (user, name): posting a name you already own returns that row's id and stamps last_used, and every other field you sent is ignored rather than applied. Use PATCH /api/v1/equipment/{id} to change one. Always 201, even when nothing was created.
application/json
object
A row that was just created.
id integer · int64 required
400 Malformed or rejected input.
application/json
object
Every error body in the API has this shape.
code integer · int32 required
error string required
Human-readable. Not a stable identifier — do not branch on it.
401 No valid session cookie or token. Also returned when a token is
presented to a session-only endpoint — the endpoint does not accept
tokens at all, so the scope is irrelevant.
application/json
object
Every error body in the API has this shape.
code integer · int32 required
error string required
Human-readable. Not a stable identifier — do not branch on it.
{
"code": 401,
"error": "Authentication required. Log in to access this endpoint."
}