//SCHEMADocs
Console
Reference

Schema (SDL)

The whole API as GraphQL SDL. The descriptions inside are the implementation's own notes; the resource pages are the customer-facing documentation.

Download it from /schema.graphql — a stable URL, so a build step can fetch it directly:

curl -o schema.graphql https://docs.schema.fm/schema.graphql

Use it to generate typed clients (graphql-codegen, gql.tada, graphql-client for Rust, Apollo iOS/Kotlin) or to validate your queries in CI before they ever reach the API. It is generated from the server's code, and the server's test suite fails if this copy drifts from it.

schema.graphql
input AddOrgMemberInput {
	"""
	Their email address, which is also their login. Stored verbatim;
	uniqueness is case-insensitive, so `Alice@` and `alice@` are one person.
	"""
	email: String!
	"""
	How to show them in a list. Optional — an address alone is a usable
	account, and guessing a name from the local part gets it wrong.
	"""
	displayName: String
	"""
	`owner`, `admin` or `member`. Defaults to **member**, which is the least
	privileged of the three: an unstated role should never be the powerful
	one.
	"""
	role: String
}

input AddResourceInput {
	releasePk: Int!
	isrc: String
	"""
	DDEX type — `MusicalWorkSoundRecording` for a track,
	`FrontCoverImage` for artwork. Cover art is a resource like any other;
	nothing about this path treats it specially.
	"""
	resourceType: String!
	"""
	The AVS kind to intern `resourceType` under. Defaults to
	`SoundRecordingType`; artwork needs `ImageType`.
	"""
	resourceTypeKind: String! = "SoundRecordingType"
	title: String
	durationSeconds: Int
	"""
	Track number. Appended after the current last when omitted.
	"""
	sequence: Int
}

"""
A member just added, and the one and only appearance of their password.

Same contract as an API key and a delivery account: shown once, stored as an
argon2id hash, never recoverable.
"""
type AddedMember {
	userPk: Int!
	email: String!
	role: String!
	"""
	`null` when the address already belonged to a //SCHEMA user. They joined
	with the credential they already had, which this organization neither
	sees nor replaces.
	"""
	password: String
	"""
	False when an existing user joined rather than a new account being made.
	Worth showing: "we made them an account" and "we gave an existing
	account access to yours" are different sentences.
	"""
	created: Boolean!
}

"""
A fault in this catalog that only the customer can fix.
"""
type Alert {
	alertPk: Int!
	kind: AlertKind!
	severity: AlertSeverity!
	"""
	One line. Quotes sender-supplied text (filenames): render it as text.
	"""
	title: String!
	"""
	What //SCHEMA saw, why it matters, and what to do. Quotes
	sender-supplied text: render it as text.
	"""
	description: String!
	"""
	The thing that most likely needs correcting.
	"""
	fix: AlertTarget!
	"""
	Everything else worth a link.
	"""
	related: [AlertTarget!]!
	"""
	When it was first raised.
	"""
	raisedAt: DateTime!
	"""
	When the condition was last observed. One alert per condition: seeing
	it again moves this and `occurrences`, and adds no alert.
	"""
	lastSeenAt: DateTime!
	occurrences: Int!
	"""
	Whether the signed-in person has read it. Always `false` for an API key.
	"""
	read: Boolean!
	"""
	Whether `dismissAlert` may close it. A blocking alert is closed by its
	fix, except where dismissing is itself a decision //SCHEMA can act on.
	"""
	dismissable: Boolean!
	resolvedAt: DateTime
	resolution: AlertResolution
	"""
	The note given when it was dismissed.
	"""
	dismissNote: String
	"""
	The delivery that raised it, when there was one.
	"""
	delivery: Message
}

"""
One page of alerts.
"""
type AlertConnection {
	nodes: [Alert!]!
	"""
	Pass as `after` for the next page. `null` on the last page.
	"""
	endCursor: String
	hasNextPage: Boolean!
}

"""
The inbox's headline numbers. Open alerts only.
"""
type AlertCounts {
	open: Int!
	"""
	Open and blocking: the console's nav badge.
	"""
	blocking: Int!
	"""
	Open and not read by the signed-in person. Equal to `open` for an API
	key, which has no read state.
	"""
	unread: Int!
}

"""
What kind of fault.
"""
enum AlertKind {
	"""
	A delivered file was refused at intake — today, a lossy audio master.
	"""
	FILE_REJECTED
	"""
	A file's bytes contradict the hash its delivery declared.
	"""
	HASH_MISMATCH
	"""
	A console delivery could not tell which waiting file its message names.
	"""
	AMBIGUOUS_CLAIM
}

"""
How an alert closed.
"""
enum AlertResolution {
	"""
	The condition cleared.
	"""
	FIXED
	"""
	Someone decided it needs no fix.
	"""
	DISMISSED
	"""
	Its subject was deleted.
	"""
	SUPERSEDED
}

"""
Whether something is held because of it.
"""
enum AlertSeverity {
	"""
	Something is held (a release is `pending`) until it is fixed.
	"""
	BLOCKING
	"""
	Nothing is held, but the catalog is wrong or will degrade.
	"""
	WARNING
}

"""
Which alerts to list.
"""
enum AlertStatus {
	"""
	Not resolved: blocking first, then most recently seen.
	"""
	OPEN
	"""
	Fixed, dismissed or superseded: most recently resolved first.
	"""
	RESOLVED
}

"""
Something an alert links to.
"""
type AlertTarget {
	kind: AlertTargetKind!
	releasePk: Int
	resourcePk: Int
	messagePk: Int
	"""
	A name for the link, e.g. a track's title and ISRC. Sender-supplied
	text: render it as text.
	"""
	label: String!
}

"""
What an `AlertTarget` points at.
"""
enum AlertTargetKind {
	"""
	A release: `releasePk`.
	"""
	RELEASE
	"""
	A track on a release: `releasePk` and `resourcePk`.
	"""
	TRACK
	"""
	A delivered message: `messagePk`.
	"""
	DELIVERY
	"""
	The catalog's deliveries, for a fault with no subject yet.
	"""
	DELIVERIES
}

"""
An API key as the console lists it. Carries no secret and cannot — the
plaintext existed once, when it was minted.
"""
type ApiKey {
	apiKeyPk: Int!
	catalogPk: Int!
	name: String!
	"""
	The leading, non-secret segment. Enough to tell two keys apart, far too
	little to help anyone holding it.
	"""
	tokenPrefix: String!
	scopes: [String!]!
	createdAt: DateTime!
	expiresAt: DateTime
	"""
	Best-effort and lagging: the auth path deliberately does not write on
	every request, so this answers "is this key dormant" rather than "when
	exactly was it last used".
	"""
	lastUsedAt: DateTime
	revokedAt: DateTime
}

"""
One cover at one of your organization's sizes.
"""
type Artwork {
	"""
	The label asked for.
	"""
	size: String!
	"""
	What is rendered for it.
	"""
	spec: String!
	status: EncodingStatus!
	"""
	The rendition's dimensions once `READY`; before that, what they will be
	(`null` until the cover has been measured).
	"""
	width: Int
	height: Int
	"""
	`jpeg` or `png`.
	"""
	format: String!
	contentType: String!
	"""
	`null` until `READY`.
	"""
	byteSize: Int
	"""
	A short-lived signed URL for the rendition; `null` unless `READY`.
	Minted, and logged, only when selected.
	"""
	url(
		"""
		Lifetime in seconds; capped by the store.
		"""
		ttlSeconds: Int
	): String
}

"""
How the cover is fitted to a size's bounds.
"""
enum ArtworkCrop {
	"""
	Exactly the size: scaled to cover it, center-cropped.
	"""
	FILL
	"""
	Within the size, aspect kept, nothing cropped.
	"""
	FIT
}

"""
What a rendition is written as.
"""
enum ArtworkFormat {
	JPEG
	PNG
}

"""
One of the organization's sizes.
"""
type ArtworkSize {
	artworkSizePk: Int!
	"""
	What `artwork(size:)` takes.
	"""
	label: String!
	width: Int!
	height: Int!
	crop: ArtworkCrop!
	format: ArtworkFormat!
	"""
	JPEG quality, 1–100. `null` for PNG.
	"""
	quality: Int
	"""
	Whether a smaller cover is scaled up to the size.
	"""
	upscale: Boolean!
	"""
	What is rendered: `img-fill-600x600-jpeg-q85-v1`. Sizes with the same
	spec share renditions, in this organization or any other.
	"""
	spec: String!
	"""
	When this size started rendering its current spec: when it was created,
	or when its dimensions, crop, format or quality last changed (which
	gives it a new `artworkSizePk`). A rename keeps it.
	"""
	configuredFrom: DateTime!
	updatedAt: DateTime!
}

"""
The bounds a size must fall within — asked rather than hardcoded, so the
console's form offers exactly what will be accepted.
"""
type ArtworkSizeLimits {
	minPx: Int!
	maxPx: Int!
	maxSizes: Int!
	maxLabelLength: Int!
	defaultJpegQuality: Int!
}

"""
Where invoices are sent. Read with `BillingRead`.
"""
type BillingContact {
	legalName: String!
	billingEmail: String!
	postalAddress: String
	currency: String!
	paymentTermsDays: Int!
	"""
	Whether a payment method can be managed yet. False means onboarding is
	incomplete on //SCHEMA's side, not that the customer did anything wrong.
	"""
	paymentReady: Boolean!
}

"""
One charge, as it will appear on the invoice.
"""
type BillingLine {
	"""
	`platform_fee`, `storage_overage`, `ingest`, `migration`, `credit`,
	`adjustment`.
	"""
	kind: String!
	description: String!
	"""
	Scaled by 10^6 — `1.5` is `1500000`. Integers all the way to the screen,
	because a float here is a rounding disagreement with the invoice.
	"""
	quantityMicros: Int!
	unit: String!
	unitPriceMicros: Int!
	amountCents: Int!
}

"""
Spend so far this period, and what it is heading for.
"""
type BillingPeriod {
	periodStart: String!
	periodEnd: String!
	currency: String!
	"""
	Accrued so far — the days that have actually happened.
	"""
	toDateCents: Int!
	"""
	What the period is heading for if it continues like this.
	"""
	projectedCents: Int!
	"""
	A plausible band around the projection, widened where delivery volume
	has been uneven. Equal on both sides once the period is complete.
	"""
	projectedLowCents: Int!
	projectedHighCents: Int!
	"""
	One sentence naming the method, so the number is never presented bare.
	"""
	confidence: String!
	"""
	True once every day has been observed. **Still not an invoice** — that
	comes from a frozen rollup.
	"""
	isFinal: Boolean!
	"""
	When today's share was measured. `toDateCents` is as of this instant.
	"""
	asOf: DateTime!
	"""
	The projected charges, broken out. Matches the invoice's own line kinds
	exactly: if a line will appear on the invoice it appears here, and
	nowhere else does a charge get introduced.
	"""
	lines: [BillingLine!]!
}

"""
A delivered release that cannot go live yet, and why.
"""
type BlockedRelease {
	releasePk: Int!
	upc: String
	title: String
	updatedAt: DateTime!
	"""
	Everything in the tracklist, artwork included.
	"""
	resources: Int!
	"""
	Still waiting for their bytes.
	"""
	unbacked: Int!
	"""
	Front covers backed by bytes. Zero blocks release.
	"""
	covers: Int!
	"""
	What is blocking it, in sentences. Assembled here rather than in SQL so
	the counts stay comparable and the wording stays changeable.
	"""
	problems: [String!]!
}

type Catalog {
	catalogPk: Int!
	slug: String!
	displayName: String!
	orgPk: Int!
	orgSlug: String!
	orgDisplayName: String!
	"""
	This organization's own SFTP hostname — `bandcamp.ftp.schema.fm`.
	`null` for an org that only ingests through the API.
	
	Per-DSP as of `0006`. It was previously one service-level string shared
	by every deployment, which stopped being true the moment more than one
	DSP could buy the software.
	"""
	sftpHost: String
	"""
	The port that host listens on — 22 in production, 2222 in the dev
	compose stack. Installation config, not per-DSP, so it is not stored
	beside the hostname.
	"""
	sftpPort: Int!
	"""
	Where this organization's SFTP endpoint stands: `ready`, `provisioning`
	(requested, not reachable yet — a new org's first few minutes),
	`delayed` (an attempt failed and is being retried), `off` (it existed
	and has been turned off — a canceled trial, a suspension), or `none`.
	
	`sftpHost` stays `null` until `ready`, so without this a console cannot
	tell "being set up" from "your plan has no endpoint". `failed` is
	reported as `delayed` and `last_error` is not exposed: the cause is
	infrastructure the customer cannot act on, and it retries on its own.
	"""
	sftpEndpointStatus: String!
}

"""
A copyright notice — the © line.

The **song**, not the recording: ℗ covers the master, © covers the
composition, and a label routinely holds one and not the other. Kept as its
own type rather than a `kind` on [`PLine`] so a client cannot conflate them
by forgetting a filter (`0009`).

Qualified by language, script and territory exactly as a [`PLine`] is.
"""
type Cline {
	"""
	`CLineText`, e.g. `(C) 1994 EMI Music Japan Inc.`
	"""
	value: String!
	"""
	`Year`, verbatim as delivered. A **string** for the same reason
	[`PLine::year`] is.
	"""
	year: String
	"""
	`CLineCompany`. Usually absent — most senders put the company only
	inside `value`.
	"""
	company: String
	language: String
	script: String
	territory: String
	isDefault: Boolean!
}

"""
Where a clip sits in the recording.
"""
type Clip {
	startMs: Int!
	durationMs: Int!
}

"""
One ℗ or © notice, for `plines` or `clines`.

One input type serves both because the fields are identical; which right
it states is decided by the list it is in, and the two are stored apart.
"""
input CopyrightLineInput {
	"""
	The notice as it should be displayed, e.g. `(P) 2026 Example Records`.
	Stored verbatim.
	"""
	text: String!
	"""
	The year, as text. Not validated as a number, matching what a
	delivered notice may carry.
	"""
	year: String
	"""
	The rights-holder, when it is not only inside `text`.
	"""
	company: String
	"""
	BCP-47 tag (`en`, `ja-Jpan`), when the notice is in a particular
	language. The script subtag is extracted exactly as it is from a
	delivery.
	"""
	languageAndScriptCode: String
	"""
	Leave unset unless the notice really is territory-qualified. `null` is
	**not** the same as `Worldwide`.
	"""
	territory: String
	"""
	The default among several. At most one per list; when none is marked,
	the first is.
	"""
	isDefault: Boolean! = false
}

input CreateArtworkSizeInput {
	"""
	A short slug: a–z, 0–9, `-` and `_`, up to 40 characters, starting with
	a letter or digit. Unique in the organization.
	"""
	label: String!
	width: Int!
	height: Int!
	crop: ArtworkCrop!
	format: ArtworkFormat!
	"""
	JPEG only, 1–100; defaults to 85. Leave it out for PNG.
	"""
	quality: Int
	"""
	Scale a smaller cover up to the size. Defaults to false.
	"""
	upscale: Boolean! = false
}

input CreatePartyInput {
	"""
	Required. A party with no name identifies nobody, and the DAL refuses to
	mint one on either ingress path.
	"""
	name: String!
	"""
	DDEX party identifier, if the customer has one. Parties **are** deduped
	by DPID within a catalog, because a DPID is an identifier rather than a
	label — so passing the same one twice returns the same party.
	"""
	dpid: String
	"""
	Interned under `PartyNameType`. `FullName` unless you say otherwise.
	"""
	nameType: String! = "FullName"
}

input CreateReleaseInput {
	"""
	Optional **while drafting only**. `releases_has_identity` requires a UPC
	or a GRid to leave `draft`, so this is a deferral rather than a
	relaxation — a release with neither has no identity and would multiply on
	every redelivery. The one exception is an organization //SCHEMA has
	exempted because it does not deliver DDEX (`organizations.require_upc`,
	`0041`): its releases may publish with neither.
	"""
	upc: String
	grid: String
	title: String
	"""
	`Album`, `Single`, … Interned verbatim; an unknown value is accepted and
	marked `observed` rather than rejected, exactly as a delivered one is.
	"""
	releaseType: String
	"""
	The release's ℗ notices — who owns the sound recordings.
	
	Leave it out to keep what is there. Pass a list to **replace** them
	all; `[]` removes every one. Not required to publish: a release can
	legitimately carry none.
	"""
	plines: [CopyrightLineInput!]
	"""
	The release's © notices — who owns the compositions, artwork and text.
	Same rule as `plines`: absent keeps, a list replaces, `[]` clears.
	"""
	clines: [CopyrightLineInput!]
	"""
	The label(s) the release came out on. Same rule as `plines`.
	
	Each names a party made with `createParty`, not a string, so that two
	spellings of one label stay one label and `releases(labelPartyPk:)`
	finds everything on it.
	"""
	labels: [LabelInput!]
}

input CreateWebhookEndpointInput {
	"""
	Where to send. `https://` on the default port; never a private,
	loopback or link-local address.
	"""
	url: String!
	description: String
	"""
	At least one, from `webhookEventTypes`.
	"""
	eventTypes: [String!]!
	"""
	Only events from these catalogs of the organization's. Omit for every
	catalog, including ones created later.
	"""
	catalogPks: [Int!]
}

"""
A new endpoint, and the one and only appearance of its signing secret.
"""
type CreatedWebhookEndpoint {
	endpoint: WebhookEndpoint!
	"""
	`whsec_…`. Copy it now — it cannot be read back. Verify each delivery's
	`webhook-signature` with it.
	"""
	secret: String!
}

"""
One party credited in one role.
"""
type Credit {
	party: Party!
	"""
	Verbatim DDEX role — `MainArtist`, `StudioProducer`, `Composer`, …
	"""
	role: String
	"""
	Which vocabulary the role came from: `DisplayArtistRole` for billing,
	`ResourceContributorRole` for the people who worked on the recording.
	
	Exposed because the two cannot be told apart by the role string alone.
	`MainArtist` and `FeaturedArtist` exist only in the first;
	`ResourceContributorRole` has `FoleyArtist` and `MakeUpArtist`, so a
	client filtering for performers by spelling gets the make-up department.
	"""
	roleKind: String
	"""
	`null` means the credit was not territory-qualified, which is NOT the
	same as an explicit `Worldwide`.
	"""
	territory: String
	"""
	Billing order, when the sender expressed one.
	"""
	sequence: Int
}

input CreditInput {
	partyPk: Int!
	"""
	Verbatim DDEX role — `MainArtist`, `Producer`, `Composer`, … Not
	`Label`: a label is set with `labels` on the release.
	"""
	role: String!
	"""
	The AVS kind to intern `role` under. Release-level credits are
	`DisplayArtistRole`; track-level ones are usually
	`ResourceContributorRole`.
	"""
	roleKind: String! = "DisplayArtistRole"
	"""
	Leave unset unless the credit really is territory-qualified. `null` is
	**not** the same as `Worldwide` — `0001` decision 6 keeps the two
	distinguishable so //SCHEMA can regenerate what was asserted.
	"""
	territory: String
	"""
	Billing order.
	"""
	sequence: Int
}

"""
Implement the DateTime<Utc> scalar

The input/output is a string in RFC3339 format.
"""
scalar DateTime

"""
A deal's availability window — `DealTerms/ValidityPeriod`, DDEX's own
concept of when a commercial model may sell this release.

Distinct from [`Release::available_from`]: that field is this *catalog's*
publish gate, set once a release clears its own activation rules; this is
what the label itself asserted, per deal, and a release can carry several.
"""
type Deal {
	"""
	`CommercialModelType`, e.g. `SubscriptionModel`, `AdvertisementSupportedModel`.
	"""
	commercialModel: String
	"""
	`UseType`, e.g. `Stream`, `PermanentDownload`.
	"""
	useType: String
	start: DateTime
	end: DateTime
}

"""
What happened to a file delivered through the console.

The four outcomes are `MatchOutcome`'s, passed through rather than
flattened, because they mean genuinely different things to the operator who
just dropped the file and each wants different words:

- `awaiting` — delivered with no `deliveryNamespace`: stored, and waiting for
its message. A console message delivered within 24 hours that names this
file claims it (`deliverMessage`'s `claimedFiles`).
- `promoted` — it matched an expectation and the track now has its bytes.
- `orphan` — stored, but nothing is waiting for it. Not an error: the file
may simply have arrived before its message. Worth saying out loud, though,
because the likelier cause in a console is a mistyped path.
- `duplicate` — these exact bytes were already landed at this exact path.
- `hashMismatch` — it matched, but contradicts the `<HashSum>` the message
declared. The resource is marked failed and NOT promoted. A corrected
re-upload clears it without operator action.
"""
type DeliveredFile {
	deliveryNamespace: String!
	relativeUri: String!
	"""
	`promoted` | `awaiting` | `orphan` | `duplicate` | `hashMismatch`.
	"""
	outcome: String!
	"""
	Set for `promoted` and `hashMismatch` — the resource this satisfied.
	"""
	resourcePk: Int
}

"""
What happened to a DDEX message delivered through the console.
"""
type DeliveredMessage {
	messagePk: Int!
	"""
	Where its media must be delivered to match. The console reuses it via
	`outstandingFiles`; nobody has to read it.
	"""
	deliveryNamespace: String!
	"""
	Resources referenced whose bytes have not arrived. Zero means the
	delivery is already complete — every file was either included or already
	in the store from a previous delivery.
	"""
	awaitingFiles: Int!
	"""
	The dedup gate fired: this exact message was already ingested and
	nothing changed. The happy path for a re-drop, not an error.
	"""
	duplicate: Boolean!
	"""
	Files delivered through the console BEFORE this message — waiting
	because nothing expected them yet — that it claimed by filename and
	matched. `awaitingFiles` already excludes them.
	"""
	claimedFiles: Int!
	"""
	Waiting filenames this message would have claimed but could not
	unambiguously: more than one waiting file of that name, or one that two
	of its entries would both want. Deliver those again now that the message
	exists, and they route normally.
	"""
	ambiguousFiles: [String!]!
	"""
	For a `PurgeReleaseMessage`, what became of each release it named;
	empty for any other message. A purge creates nothing and only ever
	withdraws, so this is the whole of what it did.
	"""
	purged: [PurgedRelease!]!
}

"""
An SFTP login this organization has issued to one of its senders.

Carries no secret. The password exists in plaintext exactly once, in the
[`super::mutation::IssuedAccount`] returned by the mutation that created it;
afterwards only `hasPassword` remains, and a lost one is reset rather than
recovered.
"""
type DeliveryAccount {
	deliveryAccountPk: Int!
	"""
	The SFTP login. Unique on this org's endpoint, not globally.
	"""
	username: String!
	"""
	Who it was issued to — 'The Orchard', 'Sony Music UK'. The DSP's own
	label for a partner //SCHEMA has no record of.
	"""
	displayName: String!
	hasPassword: Boolean!
	"""
	Live keys only. Zero with `hasPassword: false` means the account cannot
	authenticate at all — created dormant, awaiting a key.
	"""
	keyCount: Int!
	createdAt: DateTime!
	"""
	Last time something arrived on this account, best-effort.
	"""
	lastSeenAt: DateTime
	"""
	When access was withdrawn. Revoked accounts stay listed on purpose — the
	support question is "did we ever grant this, and when did it end".
	"""
	revokedAt: DateTime
}

"""
A public key authorized on a delivery account, shown by fingerprint.

The key itself is not returned. It is not secret, but it is also not useful
to the console, and a field nobody reads is a field that eventually leaks
something adjacent to it.
"""
type DeliveryKey {
	deliveryAccountKeyPk: Int!
	"""
	OpenSSH's `SHA256:…` form, so it can be compared against what
	`ssh-keygen -lf` prints on the sender's machine.
	"""
	fingerprint: String!
	createdAt: DateTime!
	revokedAt: DateTime
}

"""
A file refused at intake.
"""
type DeliveryRejection {
	rejectionPk: Int!
	deliveryNamespace: String!
	relativeUri: String!
	"""
	`null` when it was refused before being stored (SFTP refuses first).
	"""
	sha256: String
	byteSize: Int
	"""
	What the header says it is: `mp3`, `aac`…
	"""
	detectedFormat: String!
	reason: String!
	rejectedAt: DateTime!
}

"""
A party as the authoring surface sees it.
"""
type DraftParty {
	partyPk: Int!
	name: String!
	dpid: String
}

"""
A resource as the authoring surface sees it.

Deliberately not [`super::data::Resource`]: that type carries credits,
localized titles and ℗ notices assembled by the read loader, none of which
authoring has produced yet. Returning it would mean answering questions the
caller has not asked and this path cannot yet answer.
"""
type DraftResource {
	"""
	The handle to upload against.
	"""
	resourcePk: Int!
	isrc: String
	"""
	`incoming` until bytes land, then `analyzing` → `stored`.
	"""
	state: String!
}

"""
One file of a resource's audio, in one codec and quality — the delivered
original among them.
"""
type Encoding {
	"""
	The stored profile: `original`, `aac-256-v1`, `aac-128-v1+clip30-v1`.
	"""
	profile: String!
	"""
	The encoding half of it — what `url(encoding:)` takes, pinned —
	`original` for the delivered bytes.
	"""
	encoding: String!
	"""
	`null` for full length.
	"""
	clip: Clip
	"""
	These are the bytes the sender delivered.
	"""
	original: Boolean!
	status: EncodingStatus!
	container: String
	codec: String
	"""
	`null` when not yet known: a master not yet analyzed.
	"""
	lossless: Boolean
	bitrateKbps: Int
	sampleRateHz: Int
	bitDepth: Int
	channels: Int
	durationMs: Int
	byteSize: Int
	"""
	Lowercase hex SHA-256 of the file: verify what you downloaded.
	"""
	sha256: String
}

"""
An encoding that can be asked for.
"""
type EncodingProfile {
	"""
	The pinned name, `aac-256-v1`.
	"""
	encoding: String!
	"""
	The family, `aac-256`, which follows the newest version.
	"""
	family: String!
	"""
	The stored profile: the encoding, with `+clip30-v1` for a clip.
	"""
	profile: String!
	clip: Boolean!
	codec: String!
	bitrateKbps: Int
	lossless: Boolean!
	"""
	Within `media_preview`.
	"""
	preview: Boolean!
	reach: EncodingReach!
	description: String!
}

"""
Which masters a profile is made for without being asked.
"""
enum EncodingReach {
	"""
	Every audio master, moments after it lands.
	"""
	EVERY_MASTER
	"""
	Every audio master an organization with lossless media holds —
	enterprise organizations, and any other staff have turned it on for.
	"""
	ENTERPRISE
	"""
	Only after `requestEncoding`.
	"""
	ON_REQUEST
}

"""
Whether an encoding can be fetched yet.
"""
enum EncodingStatus {
	"""
	Mint it with `url(encoding:, clip:)`.
	"""
	READY
	"""
	Being made, or about to be: an eager profile, or one requested.
	"""
	PENDING
	"""
	The worker gave up on it; //SCHEMA staff have the reason.
	"""
	FAILED
}

type HttpHeader {
	name: String!
	value: String!
}

type InvoiceSummary {
	invoicePk: Int!
	periodStart: String!
	periodEnd: String!
	status: String!
	currency: String!
	totalCents: Int!
	issuedAt: String
	dueAt: String
}

input IssueDeliveryAccountInput {
	"""
	The SFTP login. Unique on this organization's endpoint, not globally:
	two DSPs may each have an `orchard`.
	
	Must be POSIX-shaped (3–32 characters, starting with a letter, then
	`a–z`, `0–9`, `_`, `-`) because it becomes a real daemon user. The rule
	is enforced by a CHECK constraint rather than restated here, so there is
	only one place for it to be wrong.
	"""
	username: String!
	"""
	Who the account is for, in human terms: 'The Orchard', 'Sony Music UK'.
	"""
	displayName: String!
	"""
	Which of this organization's catalogs these deliveries land in. Defaults
	to the one the credential is already scoped to, which is the right
	answer for every org that has exactly one.
	
	A catalog belonging to another organization is refused by the database's
	composite FK, not by a check in this resolver.
	"""
	catalogPk: Int
	"""
	Generate a password. Defaults to **false**: keys are the better shape
	for machine-to-machine delivery, so the more secure option is the one
	you get by saying nothing.
	
	A key-only account cannot authenticate until
	[`AdminMutation::add_delivery_key`] gives it one. That is intentional —
	it is created dormant rather than created open.
	"""
	withPassword: Boolean
}

"""
A newly issued account, and the one and only appearance of its password.

Same contract as an API key: shown once, stored as a hash, never
recoverable. A lost password is reset, which is why
[`AdminMutation::reset_delivery_password`] exists.
"""
type IssuedAccount {
	deliveryAccountPk: Int!
	username: String!
	"""
	`null` for a key-only account. Copy it now — it cannot be read back.
	"""
	password: String
}

"""
The label a release came out on.

A **party**, not a string: ERN 4.3 references one from the message's
`PartyList`, so the label arrives with its DPID and its localized names, and
two spellings of one label do not become two labels. That is what makes
grouping a catalog by label work at all (`0010`).

Deliberately not a [`Credit`]: a label did not perform, write or produce
anything, and putting it among the contributors would place it in every
"who played on this" answer.
"""
type Label {
	"""
	Stable handle for "every release on this label".
	"""
	partyPk: Int!
	"""
	The label's default name. Null only when the delivery named a party with
	no name at all.
	"""
	name: String
	"""
	DDEX Party Identifier, when the sender supplied one.
	"""
	dpid: String
	"""
	`LabelType` — `DisplayLabel`, `UserDefined`. Usually absent: most deliveries
	name one label and leave its role implicit.
	"""
	labelType: String
	"""
	Null means the sender did not territory-qualify it, which is NOT the
	same as Worldwide.
	"""
	territory: String
	isDefault: Boolean!
	sequence: Int
}

"""
A label a release came out on, for `labels`.
"""
input LabelInput {
	"""
	The label, from `createParty`.
	"""
	partyPk: Int!
	"""
	`LabelType` — `DisplayLabel` or `UserDefined`. Usually left out: most releases
	name one label and leave its role implicit.
	"""
	labelType: String
	"""
	Leave unset unless the label really is territory-qualified (a reissue
	can have a different label per territory). `null` is **not** the same
	as `Worldwide`.
	"""
	territory: String
	"""
	Display order when there are several.
	"""
	sequence: Int
	"""
	The default among several. At most one; when none is marked, the
	first is.
	"""
	isDefault: Boolean! = false
}

"""
One label, with how much of this catalog is on it.
"""
type LabelSummary {
	"""
	Pass to `releases(labelPartyPk:)` for everything on this label.
	"""
	partyPk: Int!
	name: String
	dpid: String
	"""
	DISTINCT releases: a release naming this label under two territories is
	two `release_labels` rows and still one release.
	"""
	releases: Int!
}

"""
One text value with its full ERN qualification.

ERN carries titles and names many times over — once per language/script, and
once per territory — and the schema keeps every variant. The scalar `title`
and `party.name` fields resolve one of these for convenience; this is the
whole set.
"""
type LocalizedText {
	value: String!
	"""
	`DisplayTitle`, `FormalTitle`, `FullName`, …
	"""
	type: String
	"""
	The **entire** `LanguageAndScriptCode` as the sender wrote it —
	`ja-Jpan`, `ja-Latn`, `zh-Hans-CN` — not just the language subtag.
	//SCHEMA stores it verbatim so the ERN can be regenerated exactly.
	"""
	language: String
	"""
	The ISO 15924 subtag pulled out of `language` as a query convenience, so
	the two deliberately overlap: `ja-Jpan` yields script `Jpan`.
	"""
	script: String
	"""
	`ApplicableTerritoryCode`. `null` means unqualified, which is NOT the
	same as an explicit `Worldwide`.
	"""
	territory: String
	"""
	The sender's `IsDefault`. Frequently false on *every* variant, which is
	why picking one automatically is lossy.
	"""
	isDefault: Boolean!
}

"""
What a resource's file is.
"""
enum MediaKind {
	AUDIO
	IMAGE
	VIDEO
	OTHER
}

type Message {
	messagePk: Int!
	ddexMessageId: String!
	fileName: String!
	senderDpid: String!
	senderName: String
	status: String!
	failureReason: String
	dateReceived: DateTime!
	"""
	A short-lived signed URL for the **original ERN XML**, exactly as
	delivered.
	
	This is the provenance link the product note asks for: every catalog row
	traces to a message, and the message can still produce the bytes that
	produced it. It is also the DDEX inspector's input — "why is this
	malformed" is unanswerable without the literal file, which is why
	`blob-store` keeps it rather than only the parse.
	
	# Why `MediaRead` and not `CatalogRead`
	
	The raw delivery carries more than the display metadata a player needs:
	commercial terms, sender DPIDs, the full deal structure. A key powering
	an app's track listing has no business reading it. `MediaRead` is
	//SCHEMA's blob-access grant, so the raw document sits behind the same
	gate as the masters — narrow first, relaxable later.
	"""
	sourceUrl(
		"""
		Lifetime in seconds; capped by the store.
		"""
		ttlSeconds: Int
	): String!
}

"""
A freshly minted API key, and the only appearance of its token.

Stored as a SHA-256 digest thereafter, so this is not "shown once" as a
policy — it is shown once because no other copy exists.
"""
type MintedApiKey {
	apiKeyPk: Int!
	"""
	Copy it now. There is no way to retrieve it later.
	"""
	token: String!
	"""
	The non-secret leading segment, also persisted, so the console can
	identify this key in a list afterwards.
	"""
	tokenPrefix: String!
}

"""
The mutation root, merged the same way the query root is.

Authoring (catalog rows, on `CatalogWrite`), administration (the
credentials that let somebody else write them, on `ConfigWrite`), and
self-service account changes (on no scope at all — see [`account`]). Kept
apart because the scope that guards each differs, and a single root would
make it easy to write a new administrative mutation behind an authoring
check.
"""
type Mutation {
	"""
	Start a release. It is created in `draft` and is not part of the catalog
	until published — it does not appear in `releases` or `release(upc:)`.
	"""
	createRelease(input: CreateReleaseInput!): Release!
	"""
	Amend a release — fix a UPC, add the title that was missing, set the
	type, the ℗ and © notices or the label.
	
	Only fields you pass are touched; an omitted one keeps its current value
	rather than being cleared, so a client that does not know about a field
	cannot erase it. The lists (`plines`, `clines`, `labels`) are the one
	way to clear: passing one replaces every entry, so `[]` removes them.
	
	# Works on published and delivered releases
	
	Including ones that arrived over DDEX. An operator who finds a typo in a
	delivered release must be able to correct it without asking the label to
	redeliver.
	
	Editing a release a **delivery** last asserted marks it
	`locallyModified` (`0007`). That is not a warning — a corrected release
	is usually more accurate than what arrived, which is why somebody
	corrected it. It records that the row no longer matches what the label
	sent, which matters because a redelivery is last-writer-wins and will
	overwrite it.
	"""
	updateRelease(releasePk: Int!, input: CreateReleaseInput!): Release!
	"""
	Rename a track, or correct its ISRC or duration.
	
	Exists so the file can come first: drop the audio, then name it. Locking
	the title to creation time would force one workflow on everybody, and
	the one it forces is the wrong way round for anybody working from a
	folder of files.
	
	Absent fields are left alone rather than cleared.
	"""
	updateResource(		releasePk: Int!,		resourcePk: Int!,		title: String,		isrc: String,		durationSeconds: Int,
		"""
		The track's ℗ notices. Absent keeps them; a list replaces them all; `[]` clears.
		"""
		plines: [CopyrightLineInput!],
		"""
		The track's © notices — usually for artwork, text or video. Same rule as `plines`.
		"""
		clines: [CopyrightLineInput!]
	): Boolean!
	"""
	Put the tracks in a given order.
	
	`resourcePks` is the new order, first to last. Tracks in the release but
	absent from the list keep their relative position after the listed ones,
	so a partial list reorders rather than truncates.
	
	Track numbers are positions, recomputed as 1..n — they are not resource
	ids and never were, though a release that had tracks removed used to
	look like they were, because removal left permanent gaps.
	
	Returns `false` if any id names a resource outside this release, and
	applies nothing: half of a bad order is a tracklist nobody asked for.
	"""
	reorderTracks(releasePk: Int!, resourcePks: [Int!]!): Boolean!
	"""
	Take a track or image back off a draft, and delete it.
	
	Idempotent: removing something already gone returns `false` rather than
	erroring, so a double-click is not a failure.
	
	Any uploaded bytes stay in storage: media is stored by content address
	and may back other resources.
	"""
	removeResource(releasePk: Int!, resourcePk: Int!): Boolean!
	"""
	Withdraw a release-level credit.
	
	The party itself survives — parties are reused across releases and
	deduped by DPID, so deleting one because a credit was removed would take
	down every other release naming them.
	"""
	removeReleaseCredit(releasePk: Int!, partyPk: Int!, role: String!, roleKind: String! = "DisplayArtistRole"): Boolean!
	"""
	Withdraw a track-level credit. Same party-survives rule as
	`removeReleaseCredit`.
	"""
	removeResourceCredit(resourcePk: Int!, partyPk: Int!, role: String!, roleKind: String! = "ResourceContributorRole"): Boolean!
	"""
	Add a track or an image to a draft release.
	"""
	addResource(input: AddResourceInput!): DraftResource!
	"""
	Create a party — an artist, label, producer or writer.
	
	Parties belong to the catalog that created them, so this is the
	customer's own namespace. Reuse the returned `partyPk` to credit the same
	party again: creating one twice by name makes two parties, deliberately,
	because matching on a bare name would merge two artists who happen to
	share one.
	"""
	createParty(input: CreatePartyInput!): DraftParty!
	"""
	Credit a party on a draft release — a display artist, or anyone
	credited at album level rather than on one track. The label is not a
	credit: set it with `labels` on `createRelease` / `updateRelease`.
	"""
	creditRelease(releasePk: Int!, input: CreditInput!): Release!
	"""
	Credit a party on one track.
	
	Distinct from [`Self::credit_release`] because DDEX distinguishes them: a
	licensed-in track on a compilation keeps its own performers while the
	release carries the compiler's label.
	"""
	creditResource(resourcePk: Int!, input: CreditInput!): DraftResource!
	"""
	Publish a draft: assert it, and put it in the catalog.
	
	This is the act with consequences — everything before it is private to
	the catalog that authored it. Publication is what mints the `sources` row
	recording *who* asserted the release, which is why `created_by` is the
	**publisher** rather than whoever started the draft: drafting is
	collaborative, and the person who clicked Publish is the one making the
	claim.
	
	Idempotent by construction rather than by checking: the source's
	idempotency key is `{releasePk}:{revision}`, so a second click carries
	the same key and is absorbed by a unique index. Editing the draft bumps
	the revision, so a publish after edits is a genuine second source.
	"""
	publishRelease(releasePk: Int!): PublishResult!
	"""
	Ask for permission to upload bytes for a resource.
	
	The client hashes locally and declares the digest; //SCHEMA authorizes
	**exactly** that content address and the store refuses anything else. The
	server cannot compute the address itself — keys are content addresses, so
	it would need the bytes it is trying to avoid receiving.
	"""
	requestUpload(		resourcePk: Int!,
		"""
		Lowercase hex SHA-256 of the file.
		"""
		sha256: String!,
		"""
		Size in bytes. Rejected above 5 GiB — use SFTP for larger files.
		"""
		byteSize: Int!
	): UploadTicket!
	"""
	A presigned PUT for a file being **delivered**, not authored.
	
	The difference from [`request_upload`] is what it is scoped to. That one
	names a `resourcePk` and backs a draft the operator is building; this one
	names nothing, because a delivered file arrives *before* //SCHEMA knows
	what it belongs to — which is the entire out-of-order premise. The
	binding happens in [`deliver_file`], after the bytes exist.
	
	Scoping it to nothing is safe because the key is a content address: the
	caller can only write to `sha256/<their own digest>`, and a caller who
	already has the bytes learns nothing by storing them. Tenancy attaches
	at the row, not at the object — the same reason `ContentKey` carries no
	catalog (`blob-store/src/key.rs`).
	"""
	requestDeliveryUpload(
		"""
		Lowercase hex SHA-256 of the file.
		"""
		sha256: String!,
		"""
		Size in bytes. Rejected above 5 GiB — use SFTP for larger files.
		"""
		byteSize: Int!
	): UploadTicket!
	"""
	Discard a blocked delivery that will never be completed.
	
	Restricted to `pending` releases by the DAL, which is what makes it a
	safe thing to put a button on: a pending release was held out of the
	catalog and no DSP ever saw it, so discarding takes nothing back. The
	same operation against a live release would be a retraction, and that is
	a takedown rather than a cleanup.
	
	Returns `false` when nothing matched — an unknown release, another
	catalog's, or one that has since gone live. Deliberately not an error:
	the honest answer to "remove this" when it is already gone is that it is
	gone.
	"""
	discardBlockedRelease(releasePk: Int!): Boolean!
	"""
	Withdraw a release from this catalog — a takedown.
	
	The manual counterpart of a DDEX `PurgeReleaseMessage`, for a takedown
	that arrived any other way: an email from the label, a rights dispute, a
	mistake. Both end in the same transition and both announce
	`release.state_changed` with `to: "withdrawn"`.
	
	Withdrawal is a state, not a delete — rows, resources and bytes stay —
	so it can be undone with `reinstateRelease`. It is recorded as a source
	naming whoever withdrew it (no one, for an API key), the way publishing
	is. A release already withdrawn is returned unchanged, so a double-click
	reads as success; a draft is refused, because nothing has been
	published to take back.
	"""
	withdrawRelease(releasePk: Int!): Release!
	"""
	Put a withdrawn release back in the catalog.
	
	It returns to the state it was withdrawn from. A release that was live
	goes back to `active`; one withdrawn while its media was still landing
	goes back to `pending` and goes live when its files arrive, as it would
	have — or at once, if they arrived while it was withdrawn. A redelivery
	of the release does **not** reinstate it: a takedown stays until
	someone chooses to undo it, here.
	
	A release that is not withdrawn is returned unchanged.
	"""
	reinstateRelease(releasePk: Int!): Release!
	"""
	Ingest a DDEX message uploaded through the console.
	
	Same pipeline as the SFTP worker — `parse_ddex` → `map` →
	`ingest_message` — deliberately, so a delivery completed by hand and one
	that arrived over SFTP produce identical rows. Availability comes from
	the catalog's own config for the same reason: a console delivery must not
	quietly bypass an `auto_activate` policy the operator set.
	
	# The namespace is derived from the bytes
	
	SFTP takes it from the batch directory. There is no directory here, so it
	is `console-<first 12 of the SHA-256>` — which makes redelivering the
	same file a **no-op by construction** rather than by a check: it lands in
	the same namespace, hits the dedup gate on `ern_messages`, and any media
	already matched against that namespace stays matched. Randomising it
	would have made every re-drop a fresh half-empty delivery.
	"""
	deliverMessage(
		"""
		Lowercase hex SHA-256 of the already-uploaded XML.
		"""
		sha256: String!,
		"""
		The original filename, for the delivery log.
		"""
		fileName: String!
	): DeliveredMessage!
	"""
	Land a delivered file against a namespace, exactly as SFTP would.
	
	This is the console's entry to the same path
	`DataStore::record_landed_file` serves for the ingest worker — same
	staging, same inline match against `expected_files`, same automatic
	promotion when the last file arrives. A file dropped in a browser and a
	file dropped over SFTP become indistinguishable rows, which is the
	property worth protecting: the operator completing a delivery by hand
	must not produce a second-class delivery.
	
	`deliveryNamespace` is the caller's to supply because only the caller
	knows whether this is a new batch or bytes for an existing one. The
	console resolves it from `outstandingFiles`; nothing here validates that
	it matches anything, because a file legitimately arrives *before* the
	message that expects it, and rejecting the early half would break
	out-of-order delivery in the name of tidiness.
	
	**Omit it when nothing expects the file yet** — media dropped before its
	message. The file is stored and waits under its bare filename
	(`relativeUri`, no directories), and the console message that names it
	claims it within 24 hours (`deliverMessage`). That is what makes arrival
	order not matter in the console as well as over SFTP.
	"""
	deliverFile(
		"""
		The batch key, matching the message's exactly. Omit it when no message expects the file yet: it then waits, by filename, to be claimed.
		"""
		deliveryNamespace: String,
		"""
		The <File> URI this satisfies, verbatim from the message.
		"""
		relativeUri: String!,
		"""
		Lowercase hex SHA-256 of the uploaded bytes.
		"""
		sha256: String!
	): DeliveredFile!
	"""
	Record that the bytes are in place, and back the resource with them.
	
	//SCHEMA confirms with the store rather than believing the client: a
	`Stored` — and therefore the row this writes — is obtainable only for
	bytes that are actually present, so `completeUpload` cannot lie about a
	file. That is store-then-record surviving a flow where the API never
	touched the bytes.
	"""
	completeUpload(resourcePk: Int!, sha256: String!): DraftResource!
	"""
	Ask for an on-demand encoding of a resource — an MP3 for a download
	store, a WAV, ALAC, a clip of an encoding other than AAC 128.
	
	Writes intent and returns: the encoding is `PENDING` until the worker
	has made it, and `resource.encoded` says when. Asking twice is one
	request; asking for one that exists returns it `READY`. The same scopes
	as minting it apply — there is no asking for what you could not fetch.
	"""
	requestEncoding(		resourcePk: Int!,
		"""
		A family ("mp3-320") or a pinned encoding ("mp3-320-v1").
		"""
		encoding: String!,
		"""
		The clip of that encoding rather than full length.
		"""
		clip: Boolean! = false
	): Encoding!
	"""
	Mint an API key for this organization.
	
	# A credential can never mint one more powerful than itself
	
	Requested scopes are checked against the caller's own. Without that, a
	key holding `ConfigWrite` could mint a key holding everything, and
	revoking the first would not revoke what it had created — privilege
	escalation that survives the revocation meant to stop it.
	
	The same rule makes the console's checkbox list honest: it asks
	`grantableScopes` and offers exactly what will be accepted.
	
	# Scoped to one catalog
	
	Defaulting to the caller's. A catalog belonging to another organization
	is refused by the composite foreign key on `(catalog_pk, org_pk)`, in
	the database rather than in a check here that could be forgotten.
	"""
	mintApiKey(		name: String!,
		"""
		Wire spellings: catalog_read, catalog_write, config_read, config_write, media_read, media_preview, media_lossless, media_write.
		"""
		scopes: [String!]!,		catalogPk: Int,
		"""
		Optional expiry. A key with none never expires on its own.
		"""
		expiresAt: DateTime
	): MintedApiKey!
	"""
	Revoke a key, immediately and permanently.
	
	The row survives with its `revoked_at` set: a boolean would answer the
	access question and lose the audit one. Idempotent — a second call does
	not move the timestamp, so "when did this stop working" keeps its first
	answer.
	"""
	revokeApiKey(apiKeyPk: Int!): Boolean!
	"""
	Add somebody to this organization.
	
	# Same organization, always
	
	The organization is `p.org_pk` — taken from the credential, never from
	an argument. There is no way to spell "add this person to a different
	org" because the mutation does not accept one, which is the same
	property that keeps authoring mutations inside one catalog.
	
	# A new account, or an existing person joining
	
	If the address is unknown, an account is created and a password comes
	back once. If it is a //SCHEMA user already — the consultant working for
	two labels — they gain a membership and their existing credential is not
	touched, so `password` is null. [`auth::membership`] explains why that
	distinction is a tenancy boundary rather than a convenience.
	
	# Roles
	
	`owner`, `admin` or `member`. Only an owner may confer ownership; see
	[`may_confer_ownership`]. Ask `grantableRoles` rather than guessing.
	"""
	addOrgMember(input: AddOrgMemberInput!): AddedMember!
	"""
	Change a member's role.
	
	Refuses on the caller's own row. Somebody stepping down or locking
	themselves out should be a deliberate act by *another* administrator,
	not a mis-click on the row that happens to be yours — and the version of
	this that goes wrong ends with an organization nobody can administer.
	
	Also refuses to demote the last owner. That invariant lives in
	[`auth::membership::set_role`], inside the transaction, so it cannot be
	lost to a race between two administrators.
	"""
	setOrgMemberRole(		userPk: Int!,
		"""
		Wire spellings: owner, admin, member.
		"""
		role: String!
	): Boolean!
	"""
	Remove somebody from this organization.
	
	Their //SCHEMA account survives — membership is the org-scoped fact, and
	the person may belong to another organization whose access this one has
	no business ending. Access here stops immediately whatever sessions they
	hold, because the principal is rebuilt from `org_members` on every
	request.
	
	Returns false when they were not a member, so a `userPk` belonging to
	somebody else's organization is a no-op rather than an error that
	confirms the id exists.
	"""
	removeOrgMember(userPk: Int!): Boolean!
	"""
	Give a member a freshly generated password, ending their sessions.
	
	The only recovery path: //SCHEMA stores an argon2id hash, so a forgotten
	password is replaced rather than looked up. Shown once, like every other
	secret here.
	
	Refused for somebody who also belongs to another organization — that
	would be a cross-tenant account takeover — and refused on the caller's
	own row, which would log them out and hand them a password they would
	then have to type back in.
	"""
	resetOrgMemberPassword(userPk: Int!): String!
	"""
	Issue an SFTP login on this organization's endpoint.
	"""
	issueDeliveryAccount(input: IssueDeliveryAccountInput!): IssuedAccount!
	"""
	Replace an account's password with a freshly generated one, invalidating
	the old one immediately. The only remedy for a lost password.
	"""
	resetDeliveryPassword(deliveryAccountPk: Int!): String!
	"""
	Withdraw an account's access.
	
	The row survives, because `sources` points at it and provenance must
	outlive the credential that produced it. Idempotent: a second call does
	not move the timestamp, so "when did access end" keeps its first answer.
	"""
	revokeDeliveryAccount(deliveryAccountPk: Int!): Boolean!
	"""
	Authorise a public key on an account.
	
	Takes the `authorized_keys` line the sender produced
	(`ssh-ed25519 AAAA… alice@laptop`) and stores it verbatim. Several keys
	may be live at once, which is what makes rotation gapless: add the new
	one, confirm it works, then revoke the old one.
	"""
	addDeliveryKey(		deliveryAccountPk: Int!,
		"""
		An authorized_keys line, e.g. 'ssh-ed25519 AAAA… alice@laptop'.
		"""
		publicKey: String!
	): DeliveryKey!
	"""
	Withdraw one key, leaving any others live.
	"""
	revokeDeliveryKey(deliveryAccountKeyPk: Int!): Boolean!
	"""
	Set this organization's SFTP hostname — `bandcamp.ftp.schema.fm`.
	
	Provisioning DNS and a listener for that name is an operations act
	outside //SCHEMA's control, so this records the intent rather than
	performing it. It is here so a DSP's endpoint is data an operator can
	set, not a redeploy.
	
	Returns the accounts unchanged — the hostname moves, the logins do not.
	"""
	setSftpHost(
		"""
		A bare hostname, no scheme and no port.
		"""
		host: String
	): [DeliveryAccount!]!
	"""
	Change the signed-in user's own password.
	
	Requires the current password — proving you still hold it is what
	makes this different from
	[`super::admin::AdminMutation::reset_org_member_password`], which an
	administrator can do without one because they are not the account
	holder. Revokes every other session this user holds, so a stolen
	cookie stops working the moment its owner changes the password
	specifically to invalidate it; the session making this call is left
	alone.
	
	Refused for an API key — there is no password behind one — and while a
	//SCHEMA employee is impersonating, so staff can never change a
	customer's password while wearing their identity.
	"""
	changePassword(oldPassword: String!, newPassword: String!): Boolean!
	"""
	Change where invoices are sent.
	
	**The whole of `BillingWrite`.** Currency, payment terms and the legal
	entity are contract terms and are not parameters here — no customer
	credential can change what they are charged, and that asymmetry is the
	design rather than an unfinished mutation.
	"""
	updateBillingContact(billingEmail: String!, postalAddress: String): BillingContact!
	"""
	Mint a Stripe Billing Portal session and return the URL to send the
	customer to.
	
	This is the whole of //SCHEMA's payment-method handling. The card is
	entered on Stripe's page, never on ours, so no card number reaches this
	codebase and PCI scope stays at SAQ-A.
	
	Behind `BillingWrite` rather than `BillingRead`: the session lets the
	holder change the payment instrument, which is not a read even though
	this call returns a string.
	"""
	createBillingPortalSession: String!
	"""
	Cancel your trial. Nothing is charged; your SFTP endpoint is turned off
	now; the console and API keep working until the trial's end, when the
	organization and its data are deleted. Resumable until then.
	
	Idempotent: canceling a canceled trial returns it unchanged and sends
	no second email.
	"""
	cancelTrial(reason: String): Trial!
	"""
	End the trial now and start paying: your card is charged today instead
	of when the trial ends, and the subscription starts with it.
	
	Records the request; the charge follows within seconds, made by the
	billing service rather than this API. Refused for a canceled trial —
	resume it first, so that "canceled" never quietly turns into a charge.
	Idempotent: asking twice is one request.
	"""
	startSubscription: Trial!
	"""
	Undo a cancellation, any time before the trial ends. The trial keeps
	its original end date, and the SFTP endpoint is set up again — on a new
	address, since the old one went back to the pool.
	"""
	resumeTrial: Trial!
	"""
	Create an endpoint. Returns its signing secret, once.
	
	Every owner of the organization is emailed — who created it, where it
	sends and what it receives — in the same transaction. If none can be,
	the endpoint is not created.
	"""
	createWebhookEndpoint(input: CreateWebhookEndpointInput!): CreatedWebhookEndpoint!
	"""
	Change an endpoint. Any change that lets it receive more than before —
	a new URL, more event types or catalogs, turning it back on — emails
	every owner, in the same transaction.
	"""
	updateWebhookEndpoint(input: UpdateWebhookEndpointInput!): WebhookEndpoint!
	"""
	Replace an endpoint's signing secret. Returns the new one, once. The old
	one keeps signing beside it for 24 hours, so a receiver can switch over
	without missing a delivery.
	"""
	rotateWebhookSecret(endpointPk: Int!): String!
	"""
	Delete an endpoint, with its delivery log. Returns false if this
	organization has no endpoint by that id.
	"""
	deleteWebhookEndpoint(endpointPk: Int!): Boolean!
	"""
	Send a `webhook.test` event to this endpoint, through the same path as
	every other event — signed, retried and logged — so a test that arrives
	and verifies means the integration works. Returns the event's id.
	"""
	sendTestWebhook(endpointPk: Int!): String!
	"""
	Send one delivery again, with the same `webhook-id`, as a fresh run of
	attempts. Safe on a delivery that succeeded: receivers dedupe on the id.
	"""
	redeliverWebhook(deliveryPk: Int!): Boolean!
	"""
	Add an artwork size. Renditions of every cover your catalogs hold are
	made from the moment it is saved; until each is ready,
	`artwork(size:)` answers `PENDING`.
	"""
	createArtworkSize(input: CreateArtworkSizeInput!): ArtworkSize!
	"""
	Change a size. Only what is given changes. A new label re-renders
	nothing; new dimensions, crop, format or quality are a new rendition,
	`PENDING` until made, and the old one is retired once nobody uses it.
	That second kind of change returns the size under a new
	`artworkSizePk`: the old definition is kept, retired, because billing
	prices past days from it.
	"""
	updateArtworkSize(input: UpdateArtworkSizeInput!): ArtworkSize!
	"""
	Remove a size. `artwork(size:)` with its label is an error from now on.
	Its renditions are retired once no organization holding the cover uses
	the same spec. `false` when there was no such size.
	"""
	deleteArtworkSize(artworkSizePk: Int!): Boolean!
	"""
	Close an open alert without fixing it, saying why. Only where the
	alert's `dismissable` is true; anything else is closed by its fix.
	"""
	dismissAlert(		alertPk: Int!,
		"""
		Why it needs no fix. Required, and kept with the alert.
		"""
		note: String!
	): Alert!
	"""
	Mark alerts read for the signed-in person. Returns how many were newly
	marked; ids that are not this catalog's alerts are ignored. Sessions
	only.
	"""
	markAlertsRead(alertPks: [Int!]!): Int!
}

"""
ISO 8601 calendar date without timezone.
Format: %Y-%m-%d

# Examples

* `1994-11-13`
* `2000-02-24`
"""
scalar NaiveDate

"""
A person with access to this organization.

Carries no credential material and cannot: passwords are argon2id hashes in
`user_identities`, and `hasPassword` is the only thing about them the
console needs.
"""
type OrgMember {
	userPk: Int!
	email: String!
	displayName: String
	"""
	`owner`, `admin` or `member`.
	"""
	role: String!
	"""
	`invited`, `active`, `suspended` or `deactivated`. Only `active` may
	sign in, so a row's existence is not permission to log in.
	"""
	status: String!
	"""
	Whether they hold a password at all. False for somebody who signs in
	only through an external provider.
	"""
	hasPassword: Boolean!
	"""
	When they joined **this** organization, which may be long after their
	//SCHEMA account was created.
	"""
	joinedAt: DateTime!
	lastLoginAt: DateTime
	"""
	True when this person also belongs to another organization. Surfaced
	because it is why `resetOrgMemberPassword` refuses for them — their
	credential is not this organization's to replace.
	"""
	sharedWithOtherOrgs: Boolean!
	"""
	True for the caller's own row, so the console can refuse to let somebody
	remove or demote themselves without comparing ids it would have to be
	told.
	"""
	isYou: Boolean!
}

"""
A file a delivered message is still waiting for.

Exposed so the console can resolve a dropped file's `deliveryNamespace`
without the operator ever seeing one. A file arriving over SFTP carries its
namespace in its path; a file dropped in a browser carries nothing, and this
is what recovers it.
"""
type OutstandingFile {
	"""
	Echo this back on `deliverFile`. Opaque — do not parse it.
	"""
	deliveryNamespace: String!
	"""
	The `<File>` URI verbatim, as the message wrote it. May contain
	directories, so a dropped filename matches it by *suffix*, not equality.
	"""
	relativeUri: String!
	releasePk: Int
	title: String
}

"""
A party: an artist, label, publisher or person.

Parties belong to the catalog and are reused within it, so the same
artist reached through two different releases is the same `partyPk`.
"""
type Party {
	partyPk: Int!
	"""
	DDEX party identifier, when the sender supplied one. Many parties —
	especially individual performers — legitimately have none.
	"""
	dpid: String
	"""
	One name, chosen by the sender's `IsDefault` flag.
	
	**Lossy, and often arbitrary.** A party routinely carries the same name
	romanized and in its own script (`Saeko Shu` / `しゅうさえこ`) with
	neither marked default, in which case this picks whichever the database
	returns first. Use [`Party::names`] whenever the script matters.
	"""
	name: String
	"""
	Every name this party carries, in all languages, scripts and
	territories.
	"""
	names(
		"""
		Filter by text type, e.g. "DisplayTitle".
		"""
		type: String,
		"""
		Whole LanguageAndScriptCode, e.g. "ja-Jpan" or "ja-Latn". Not a prefix — use `script` to select by script alone.
		"""
		language: String,
		"""
		ISO 15924 script subtag, e.g. "Jpan" or "Latn".
		"""
		script: String,
		"""
		Applicable in this territory: rows scoped to it, plus unqualified rows where nothing more specific exists.
		"""
		territory: String,
		"""
		Only rows whose IsDefault matches. Note many senders flag none, so `true` can legitimately return nothing.
		"""
		isDefault: Boolean
	): [LocalizedText!]!
}

"""
A phonogram copyright notice — the ℗ line.

Qualified by language, script and territory exactly as a title is, because
ERN gives `PLine` the same attributes.
"""
type Pline {
	"""
	`PLineText`, e.g. `(P) 1994 EMI Music Japan Inc.`
	"""
	value: String!
	"""
	`Year`, verbatim as delivered. A **string**, not an integer: //SCHEMA
	never lets validation gate delivery, so a sender writing `1994-01`
	reaches the catalog rather than failing the message. Cast at the edge.
	"""
	year: String
	"""
	`PLineCompany`. Usually absent — most senders put the company only
	inside `value`.
	"""
	company: String
	language: String
	script: String
	territory: String
	isDefault: Boolean!
}

"""
The terms in force, and the next ones if a change is scheduled.

Read-only. Nothing a customer can do here changes what they are charged —
see `Scope::BillingWrite`.
"""
type PriceTerms {
	effectiveFrom: String!
	"""
	The platform fee per `fee_interval` — a month's, or a year's.
	"""
	platformFeeMicros: Int!
	"""
	`month` or `year`: how often the platform fee recurs.
	"""
	feeInterval: String!
	"""
	`advance` when the platform fee is charged before the period it covers
	(self-serve plans), `arrears` when it is invoiced after. Usage — storage
	overage and delivery — is always invoiced after.
	"""
	feeTiming: String!
	includedGb: Int!
	overageMicrosGbMonth: Int!
	ingestMicrosGb: Int!
	includedRequests: Int
	"""
	When the rate changes next, if a deal has been scheduled.
	
	Surfaced so a customer learns about a scheduled change from the console
	rather than from an invoice — which is the whole point of letting a deal
	be entered months ahead.
	"""
	nextChangeOn: String
}

"""
The result of attempting to publish.

Validation problems come back as **data, not errors**. "No UPC yet" is an
expected answer the console renders as a checklist, and a GraphQL `errors`
entry is the wrong shape for it: errors null out the field, carry no
structure, and can only report one thing at a time. `problems` reports
everything wrong in one pass so a user fixes it in one pass.
"""
type PublishResult {
	"""
	True when the release is now in the catalog — including when a previous
	call already put it there, so a double-click reads as success.
	"""
	published: Boolean!
	"""
	Everything blocking publication. Empty when `published`.
	"""
	problems: [String!]!
	"""
	The release as it now stands.
	"""
	release: Release
}

"""
What a purge did to one release it named.
"""
enum PurgeOutcome {
	"""
	It was live (or held, awaiting files) and is now withdrawn.
	"""
	WITHDRAWN
	"""
	It had already been withdrawn. The purge's intent already held.
	"""
	ALREADY_WITHDRAWN
	"""
	The identifier belongs to an unpublished draft — nothing to take back.
	"""
	DRAFT
	"""
	This catalog holds no release with that identifier. Nothing was created.
	"""
	UNKNOWN
	"""
	Another delivery account delivered it, so this purge may not withdraw
	it. Not reachable from the console, which has no delivery account.
	"""
	OTHER_ACCOUNT
}

"""
One release a purge named.
"""
type PurgedRelease {
	upc: String
	grid: String
	"""
	The release it resolved to; `null` when `outcome` is `UNKNOWN`.
	"""
	releasePk: Int
	outcome: PurgeOutcome!
}

"""
Both query roots.
"""
type Query {
	"""
	Who this credential is. Requires authentication but no scope: a caller
	must always be able to discover what it holds.
	"""
	viewer: Viewer!
	"""
	The catalog this credential acts on.
	
	Takes no id argument on purpose. The catalog comes from the credential,
	never from the query — a resolver that accepted a `catalogPk` would need
	its own authorization check, and that is the check that eventually gets
	forgotten.
	"""
	catalog: Catalog
	"""
	One release by primary key, **including drafts**.
	
	The data-plane surface deliberately hides drafts: `releases` and
	`release(upc:)` both exclude them, because an unpublished release is not
	part of the catalog yet. That leaves authoring with no way to read back
	its own work, which is what this is for — and why it lives on the control
	plane beside the other console-facing fields.
	
	Gated on `CatalogWrite`, not `CatalogRead`: seeing half-finished work is
	an authoring privilege. A read-only integration powering someone's app
	has no business seeing a release nobody has published.
	"""
	draftRelease(releasePk: Int!): Release
	"""
	Work in progress — releases still being authored.
	
	Gated on `CatalogWrite` for the same reason `draftRelease` is: seeing
	unpublished work is an authoring privilege, not a reading one.
	"""
	draftReleases(limit: Int! = 25): [Release!]!
	"""
	Recent deliveries into this catalog, newest first.
	"""
	messages(limit: Int! = 25): [Message!]!
	"""
	Deliveries that arrived but cannot go live, and what is blocking each.
	
	`pending` releases are excluded from `releases` — they are not part of
	the catalog, because nothing should go out missing its audio or its
	sleeve. That makes them invisible, which is worse than showing them
	wrongly: a blocked delivery is something an operator has to chase.
	
	Promotion is automatic. When the missing files land — by redelivery or
	by the reconcile sweep — the release goes `active` without anybody
	pressing anything. This is a worklist, not an approval queue.
	"""
	blockedReleases(limit: Int! = 25): [BlockedRelease!]!
	"""
	Every label in this catalog, busiest first.
	
	The catalog's label axis: grouping releases by who put them out is one
	of the two or three things anybody browsing a catalog actually wants,
	and `0010` indexes `(party_pk, release_pk)` so this is a scan of the
	index rather than of the releases.
	
	Drafts and pending deliveries are excluded, matching `releases` — a
	half-arrived delivery should not inflate a label's count.
	"""
	labels(limit: Int! = 200): [LabelSummary!]!
	"""
	Files delivered messages referenced that have not arrived yet.
	
	The console uses this to route a dropped file: match its name against
	`relativeUri` and reuse the `deliveryNamespace` of whatever wanted it.
	That is the whole reason an operator never has to know what a namespace
	is — SFTP recovers it from the directory, and this recovers it from the
	expectation the message already recorded.
	
	`CatalogRead`, not `MediaWrite`: it reveals filenames a message
	referenced, which is metadata, and refusing it to a read-only credential
	would make the delivery UI unable to explain itself.
	"""
	outstandingFiles(limit: Int! = 200): [OutstandingFile!]!
	"""
	Every API key this organization holds, newest first.
	
	Never returns a token. The plaintext exists once, in the response to the
	mutation that created it, and is stored only as a SHA-256 digest
	thereafter — so this could not return one even if a caller asked.
	
	`ConfigRead`, not `CatalogRead`: enumerating an organization's
	credentials is an administrative act, and a key that lists a catalog's
	tracks has no business doing it.
	"""
	apiKeys: [ApiKey!]!
	"""
	Everyone with access to this organization, longest-standing first.
	
	# Org-scoped, and the org comes from the credential
	
	`p.org_pk`, never an argument — the same rule every other resolver
	follows for `catalog_pk`. There is no way to ask about another
	organization's membership because no resolver accepts an organization,
	so none can forget to check one.
	
	`ConfigRead` rather than `CatalogRead`: a list of who can reach a
	catalog is administrative, and a key that reads a track listing has no
	business enumerating the people behind it.
	"""
	orgMembers: [OrgMember!]!
	"""
	The roles this caller may confer on a member.
	
	Asked rather than hardcoded, for the same reason `grantableScopes` is:
	offering a choice the server will refuse is worse than not offering it.
	Only an owner may confer ownership — see
	[`super::admin::AdminMutation::add_org_member`].
	"""
	grantableRoles: [String!]!
	"""
	The scopes this caller could grant to a new key.
	
	A credential may never mint one more powerful than itself, so the
	console asks rather than guessing — offering a checkbox that the server
	will refuse is worse than not offering it.
	"""
	grantableScopes: [String!]!
	"""
	The SFTP logins this organization has issued, newest first.
	
	# Scoped by org, not by catalog
	
	Every other field here is catalog-scoped, because the catalog is the
	isolation boundary. Delivery accounts are the exception by construction:
	the endpoint hostname belongs to the *organization* (`0006`), so an org
	with a production and a sandbox catalog issues accounts on one endpoint
	and needs to see them together. Reading the org's own credentials is not
	a cross-tenant read — `p.org_pk` comes from the credential, exactly as
	`p.catalog_pk` does.
	
	`ConfigRead` rather than `CatalogRead`: these are credentials, and an
	integration that lists a catalog's tracks has no business enumerating
	who may deliver into it.
	"""
	deliveryAccounts: [DeliveryAccount!]!
	"""
	Public keys authorized on one delivery account.
	
	Returns empty rather than erroring for an account belonging to another
	organization — the org predicate is in the query, so a probe for
	somebody else's account is indistinguishable from one with no keys.
	"""
	deliveryAccountKeys(deliveryAccountPk: Int!): [DeliveryKey!]!
	"""
	The values of one DDEX vocabulary, for a picker.
	
	# This cannot mint a row, and that is the point
	
	It selects. `common::vocab`'s rule is that resolve-or-create belongs to
	ingest alone, because a value arriving from a query parameter must never
	be able to write into the `observed` drift report — the report means "a
	DSP sent us something DDEX does not define", and a typo'd search box
	would make it mean nothing.
	
	The same reasoning is why this query exists at all: with the allowed
	values readable, the console can offer them instead of a free-text box,
	and an authoring mutation can refuse what is not among them.
	
	`search` is a case-insensitive substring; omit it for the whole kind.
	
	The cap is 1000, which is above the largest set that exists
	(`InstrumentType`, 709) rather than an arbitrary round number — clients
	are expected to fetch a whole vocabulary once and filter locally, since
	these change when DDEX ships an AVS revision and not otherwise. A 500
	cap silently truncated `CurrentTerritoryCode` at 503.
	"""
	vocabValues(
		"""
		e.g. "DisplayArtistRole", "ResourceContributorRole".
		"""
		kind: String!,		search: String,		limit: Int! = 50
	): [VocabValue!]!
	"""
	Whether DDEX enumerates a kind — i.e. whether authoring will refuse a
	value outside the set.
	
	The console asks so it knows whether to present a closed picker or a
	picker-with-free-text. `GenreText` and `LanguageCode` are genuinely open
	sets and a closed list there would be wrong.
	"""
	vocabKindIsClosed(kind: String!): Boolean!
	"""
	Message counts by status over a trailing window — the dashboard's
	ingestion-health numbers.
	"""
	ingestionStats(days: Int! = 30): [StatusCount!]!
	"""
	Releases in this catalog.
	
	"In this catalog" is `releases.catalog_pk`, not a filter that could be
	omitted: releases are per-catalog (`0001` decision 2), so a release this
	catalog was never delivered is a different row entirely. Without
	`states`, the published catalog: `active` and `withdrawn`.
	"""
	releases(		limit: Int! = 50,		offset: Int! = 0,
		"""
		Partial, case- and accent-insensitive match against the release's title, a release-level display artist credit, or a label — matching ANY of the three, not all. Accents fold both ways, so `sinead` finds `Sinéad`.
		"""
		q: String,
		"""
		Only releases in these states. Omitted, `ACTIVE` and `WITHDRAWN` — the published catalog. Naming `DRAFT` needs `catalog_write`, as `draftReleases` does.
		"""
		states: [ReleaseState!]
	): [Release!]!
	"""
	Total releases [`Self::releases`] can page through for the same
	search — the count a paging client needs to know when to stop asking,
	since a page that happens to come back full is not on its own proof
	there is another.
	"""
	releasesCount(		q: String,
		"""
		Only releases in these states. Omitted, `ACTIVE` and `WITHDRAWN` — the published catalog. Naming `DRAFT` needs `catalog_write`, as `draftReleases` does.
		"""
		states: [ReleaseState!]
	): Int!
	"""
	One release by UPC.
	
	`null` covers both "no such UPC" and "exists in the corpus but not in
	your catalog", deliberately indistinguishably — otherwise this endpoint
	would answer "has //SCHEMA been delivered this release by anyone?" to
	any caller with a key.
	"""
	release(upc: String!): Release
	"""
	The encodings this organization can ask for — listed here so clients
	never hardcode one. `url(encoding:)` takes `encoding` (pinned) or
	`family` (newest version), with `clip: true` for the rows where `clip`
	is true.
	
	Per organization, so it needs a credential: organizations with lossless
	media (enterprise by default) see the lossless profiles, everyone else
	the lossy ones. Read on every request, like the gate in [`mint`], so
	the list changes the moment a plan does.
	"""
	encodingProfiles: [EncodingProfile!]!
	"""
	Files refused at intake in this catalog, newest first: today, lossy
	audio masters. A release waiting for one stays `pending` until an
	accepted file arrives at the same path.
	"""
	deliveryRejections(limit: Int! = 50): [DeliveryRejection!]!
	"""
	Quantities for the current period. `UsageRead` only.
	"""
	usage: Usage!
	"""
	Spend so far and the forecast. `BillingRead` — this carries rates.
	"""
	billingPeriod: BillingPeriod!
	"""
	The terms in force, and the next scheduled change. `BillingRead`.
	"""
	priceTerms: PriceTerms
	"""
	Where invoices are sent, and whether a card can be managed yet.
	`BillingRead`.
	"""
	billingContact: BillingContact
	"""
	Issued and draft invoices, newest period first. `BillingRead`.
	
	//SCHEMA renders no PDF — an invoice document is Stripe's to host, and
	rebuilding that would be rebuilding a solved thing with worse
	deliverability.
	"""
	invoices(
		"""
		How many to return; capped at 36.
		"""
		limit: Int
	): [InvoiceSummary!]!
	"""
	Your organization's trial, or `null` when it is not in one. `UsageRead`
	— when the trial ends is not commercial detail, and a member planning
	deliveries has every reason to know it.
	"""
	trial: Trial
	"""
	Every event type an endpoint can subscribe to. `webhook.test` is not
	among them: it is sent to one endpoint on request (`sendTestWebhook`).
	"""
	webhookEventTypes: [WebhookEventType!]!
	"""
	This organization's webhook endpoints, oldest first. No secret is ever
	returned: it was shown once, when created or rotated.
	"""
	webhookEndpoints: [WebhookEndpoint!]!
	"""
	One endpoint, or `null` if this organization has none by that id.
	"""
	webhookEndpoint(endpointPk: Int!): WebhookEndpoint
	"""
	Your organization's artwork sizes, by label. They apply to every catalog
	it owns; ask for a cover by label with `Resource.artwork(size:)`.
	"""
	artworkSizes: [ArtworkSize!]!
	"""
	The bounds a size must fall within.
	"""
	artworkSizeLimits: ArtworkSizeLimits!
	"""
	This catalog's alerts: faults only the customer can fix, one per
	condition. Open alerts list blocking first, then most recently seen.
	"""
	alerts(		status: AlertStatus! = OPEN,
		"""
		Only alerts of this kind.
		"""
		kind: AlertKind,
		"""
		Only alerts about this release.
		"""
		releasePk: Int,
		"""
		At most this many, up to 200. Defaults to 50.
		"""
		first: Int,
		"""
		`endCursor` from the previous page.
		"""
		after: String
	): AlertConnection!
	"""
	Counts of this catalog's open alerts.
	"""
	alertCounts: AlertCounts!
}

type Release {
	releasePk: Int!
	upc: String
	grid: String
	"""
	The default title. Territory-qualified variants are not resolved here —
	choosing one needs a territory, and `0001` decision 6 is explicit that
	an absent territory is not the same as `Worldwide`.
	"""
	title: String
	"""
	`pending`, `active` or `withdrawn` **for this catalog**.
	"""
	state: String!
	availableFrom: DateTime
	updatedAt: DateTime!
	"""
	When this release was last corrected locally after a **delivery**
	asserted it (`0007`). `null` means it still matches what the label sent,
	or that no label ever sent it.
	
	Not a warning. A locally-corrected release is usually more accurate than
	what arrived — that is why somebody corrected it. What it is not, is what
	the label sent, and the difference matters because a redelivery is
	last-writer-wins and will overwrite the correction.
	"""
	locallyModifiedAt: DateTime
	"""
	When this release was withdrawn from this catalog — by a DDEX purge or
	by `withdrawRelease`. `null` unless `state` is `withdrawn`;
	reinstatement clears it.
	"""
	withdrawnAt: DateTime
	"""
	Every resource in this release, flattened into track order.
	
	The common case — a player wants a track list and does not care which
	disc a track sits on. Use [`Release::groups`] when the disc/side
	structure matters.
	
	This is a depth-first flatten of exactly the tree `groups` returns, not
	an independently-ordered query. Two orderings derived separately would
	eventually disagree, and a track list that disagrees with the disc
	listing is worse than either alone.
	"""
	resources(
		"""
		Only resources of this kind — the tracks, or the artwork.
		"""
		kind: MediaKind
	): [Resource!]!
	"""
	Parties credited on the release itself — display artists, label, and
	anyone else credited at album level rather than on one track.
	
	`role` filters case-insensitively, so `contributors(role: "MainArtist")`
	is the artist-name query. Track-level credits live on
	[`Resource::contributors`]; DDEX distinguishes the two and so does this.
	"""
	contributors(
		"""
		Filter by DDEX role, e.g. "MainArtist". Case-insensitive.
		"""
		role: String,
		"""
		Credits applicable in this territory, e.g. "US": rows scoped to it, plus unqualified rows where nothing more specific exists. An explicit Worldwide credit matches only territory: "Worldwide".
		"""
		territory: String
	): [Credit!]!
	"""
	Every title of this release, in all languages, scripts and territories.
	"""
	titles(
		"""
		Filter by text type, e.g. "DisplayTitle".
		"""
		type: String,
		"""
		Whole LanguageAndScriptCode, e.g. "ja-Jpan" or "ja-Latn". Not a prefix — use `script` to select by script alone.
		"""
		language: String,
		"""
		ISO 15924 script subtag, e.g. "Jpan" or "Latn".
		"""
		script: String,
		"""
		Applicable in this territory: rows scoped to it, plus unqualified rows where nothing more specific exists.
		"""
		territory: String,
		"""
		Only rows whose IsDefault matches. Note many senders flag none, so `true` can legitimately return nothing.
		"""
		isDefault: Boolean
	): [LocalizedText!]!
	"""
	Phonogram copyright notices for the release as a whole.
	"""
	plines(
		"""
		Whole LanguageAndScriptCode, e.g. "ja-Jpan".
		"""
		language: String,
		"""
		ISO 15924 script subtag, e.g. "Jpan".
		"""
		script: String,
		"""
		Applicable in this territory: rows scoped to it, plus unqualified rows where nothing more specific exists.
		"""
		territory: String,
		"""
		Only rows whose IsDefault matches. Note many senders flag none, so `true` can legitimately return nothing.
		"""
		isDefault: Boolean
	): [Pline!]!
	"""
	The © notices — the composition/publishing right.
	
	Separate from [`Self::plines`] because they are separate rights. For an
	audio release this is the only level a © appears at: ERN attaches one to
	`Image`, `Text` and `VideoEdition`, but not to `SoundRecordingEdition`.
	
	Filtered on the same qualification axes, and reusing the ℗ filter — the
	two carry identical language/script/territory/isDefault attributes, so a
	second copy of that resolution rule would be a place for them to drift.
	"""
	clines(
		"""
		BCP-47 tag, matched case-insensitively against the whole tag.
		"""
		language: String,
		"""
		ISO 15924 script subtag, e.g. "Latn".
		"""
		script: String,
		"""
		Applicable in this territory: rows scoped to it, plus unqualified rows where nothing more specific exists.
		"""
		territory: String,
		"""
		Only rows whose IsDefault matches. Note many senders flag none, so `true` can legitimately return nothing.
		"""
		isDefault: Boolean
	): [Cline!]!
	"""
	The label(s) this release came out on, default first.
	
	Several is normal rather than exceptional: ERN names an original label
	and a licensor separately, and a reissue can carry a different label per
	territory. Ordering is the sender's — `isDefault`, then the order the
	message listed them in.
	"""
	labels: [Label!]!
	"""
	The disc/side tree, parents first.
	"""
	groups: [ResourceGroup!]!
	"""
	The release's front cover at one of your organization's artwork sizes —
	`resources` filtered to the `FrontCoverImage`, then
	`Resource.artwork(size:)` on it, in one field. `null` when the release
	has no front cover or its file has not arrived. The first in track order
	if a delivery names two.
	"""
	coverArt(
		"""
		A label from artworkSizes, e.g. "tile-600".
		"""
		size: String!
	): Artwork
	"""
	Release-level dates the delivery asserted — `ReleaseDate`,
	`OriginalReleaseDate`, and any sender extension of `EventDateType`.
	"""
	dates: [ReleaseDate!]!
	"""
	Availability windows from the deals this release was delivered with —
	DDEX's own concept of when a commercial model may sell it, as opposed
	to [`Self::available_from`], which is this catalog's own publish gate.
	"""
	deals: [Deal!]!
}

"""
A release-level date the message asserted.

`dateType` is `ReleaseDate`, `OriginalReleaseDate`, or another value a
sender's own `EventDateType` extension supplies — DDEX publishes no
allowed-value set for this element, so the mapper uses the element name
itself.
"""
type ReleaseDate {
	dateType: String!
	territory: String
	value: NaiveDate!
}

"""
A release's availability state in this catalog, as a filter.
"""
enum ReleaseState {
	"""
	Being authored; published by nobody yet.
	"""
	DRAFT
	"""
	Delivered, waiting for its files.
	"""
	PENDING
	"""
	Live.
	"""
	ACTIVE
	"""
	Taken down — by a DDEX purge or `withdrawRelease`.
	"""
	WITHDRAWN
}

"""
One track (or image, or video) as it appears inside a release.
"""
type Resource {
	resourcePk: Int!
	isrc: String
	"""
	The DDEX Type value — `MusicalWorkSoundRecording`, `FrontCoverImage`, …
	"""
	resourceType: String
	title: String
	durationSeconds: Int
	"""
	`incoming` until its file lands, then `analyzing` → `stored`. A resource
	can legitimately exist before its bytes do — that is the out-of-order
	choreography, not an error.
	"""
	state: String!
	byteSize: Int
	"""
	An opaque value that changes whenever this resource's file is replaced,
	for clients to use as a cache version. It is not a content address and
	cannot be used to fetch anything. `null` until bytes are stored.
	"""
	contentVersion: String
	"""
	Position within its group.
	"""
	sequence: Int!
	technical: Technical
	"""
	Audio, image, video or other: measured once analyzed, otherwise read
	from `resourceType`.
	"""
	mediaKind: MediaKind!
	"""
	Parties credited on this recording — performers, producers, writers.
	
	`role` filters case-insensitively; omit it for everyone.
	"""
	contributors(
		"""
		Filter by DDEX role, e.g. "Producer". Case-insensitive.
		"""
		role: String,
		"""
		Credits applicable in this territory, e.g. "US": rows scoped to it, plus unqualified rows where nothing more specific exists. An explicit Worldwide credit matches only territory: "Worldwide".
		"""
		territory: String
	): [Credit!]!
	"""
	Every title of this recording, in all languages, scripts and
	territories.
	
	The scalar `title` picks one; this is the set it picked from. A Japanese
	release routinely carries both `夢のララバイ` and `Yume no Lullaby`, and
	which one a client wants is a client decision.
	"""
	titles(
		"""
		Filter by text type, e.g. "DisplayTitle".
		"""
		type: String,
		"""
		Whole LanguageAndScriptCode, e.g. "ja-Jpan" or "ja-Latn". Not a prefix — use `script` to select by script alone.
		"""
		language: String,
		"""
		ISO 15924 script subtag, e.g. "Jpan" or "Latn".
		"""
		script: String,
		"""
		Applicable in this territory: rows scoped to it, plus unqualified rows where nothing more specific exists.
		"""
		territory: String,
		"""
		Only rows whose IsDefault matches. Note many senders flag none, so `true` can legitimately return nothing.
		"""
		isDefault: Boolean
	): [LocalizedText!]!
	"""
	Phonogram copyright notices for **this recording**.
	
	Distinct from the release's: on a compilation a licensed-in track keeps
	the ℗ of whoever owns that recording, so the two genuinely differ.
	"""
	plines(
		"""
		Whole LanguageAndScriptCode, e.g. "ja-Jpan".
		"""
		language: String,
		"""
		ISO 15924 script subtag, e.g. "Jpan".
		"""
		script: String,
		"""
		Applicable in this territory: rows scoped to it, plus unqualified rows where nothing more specific exists.
		"""
		territory: String,
		"""
		Only rows whose IsDefault matches. Note many senders flag none, so `true` can legitimately return nothing.
		"""
		isDefault: Boolean
	): [Pline!]!
	"""
	Copyright (©) notices for **this resource** — in ERN, those of artwork,
	text and video; a sound recording's © is the release's.
	
	Loaded alongside the ℗ notices since `resource_clines` existed, but not
	exposed until the API could write them: a field you can set and not read
	back is worse than neither.
	"""
	clines(
		"""
		Whole LanguageAndScriptCode, e.g. "ja-Jpan".
		"""
		language: String,
		"""
		ISO 15924 script subtag, e.g. "Jpan".
		"""
		script: String,
		"""
		Applicable in this territory: rows scoped to it, plus unqualified rows where nothing more specific exists.
		"""
		territory: String,
		"""
		Only rows whose IsDefault matches. Note many senders flag none, so `true` can legitimately return nothing.
		"""
		isDefault: Boolean
	): [Cline!]!
	"""
	Every file of this resource: the delivered original, and each encoding
	//SCHEMA has made of it. Lists, never mints — pick one and ask `url`.
	"""
	encodings(
		"""
		Only lossless (true) or only lossy (false) files.
		"""
		lossless: Boolean,
		"""
		Also list encodings being made (PENDING) or that failed (FAILED).
		"""
		includePending: Boolean! = false
	): [Encoding!]!
	"""
	A short-lived signed URL for one file of this resource — the one place
	a media URL is minted.
	
	The caller always names the file: an encoding family (`aac-256`, which
	follows the newest version), a pinned encoding (`aac-256-v1`), or
	`original` — the bytes the sender delivered. `clip: true` asks for the
	preview clip of a lossy encoding. Lossless files (FLAC, WAV, ALAC, a
	lossless original) need `media_lossless` and an organization with
	lossless media (enterprise by default); clips and AAC 128 need only
	`media_preview`.
	
	`null` when the resource has no file yet — a resource legitimately
	exists before its bytes do. Any other `null` comes with an error whose
	`code` says why: `ENCODING_PENDING`, `ENCODING_NOT_REQUESTED`,
	`ENCODING_NOT_AVAILABLE`, `ENCODING_FAILED`,
	`LOSSLESS_NOT_ENABLED`, `UNKNOWN_ENCODING`.
	
	# Why this is safe to sign
	
	`blob_store::signed_get_url` signs whatever it is handed and says so:
	content keys carry no catalog, so possession implies no entitlement, and
	establishing reachability is the caller's job. That is satisfied
	structurally here — a `Resource` can only be built by `build_tree` from
	a `ReleaseContents`, which the loader fetches with the principal's own
	`catalog_pk`, through a DAL query that re-asserts `releases.catalog_pk`.
	There is no path that produces a `Resource` for a release this catalog
	was never delivered.
	
	That guarantee got materially stronger on 2026-08-13. While releases
	were one global corpus, reachability rested on an availability row that
	an *ingest* could create by resolving a UPC — so a mistyped identifier
	could grant a catalog access to another's masters. Releases are now
	per-catalog (`0001` decision 2), and a row belonging to another catalog
	is not merely filtered out, it is unreachable.
	
	The DAL re-asserts it once more: `media::mint` reads the resource back
	by `(catalog_pk, resource_pk)` before signing, so a `resource_pk` from
	anywhere else finds nothing.
	
	The release's state does not gate a URL (Kevin, 2026-10-23): if the
	bytes are here they can be minted, and every mint is logged.
	"""
	url(
		"""
		"original", a family such as "aac-256", or a pinned encoding such as "aac-256-v1".
		"""
		encoding: String!,
		"""
		The preview clip of that encoding.
		"""
		clip: Boolean! = false,
		"""
		Lifetime in seconds; capped by the store.
		"""
		ttlSeconds: Int,
		"""
		Have the browser save a named file (Artist - Title.ext) rather than play it.
		"""
		download: Boolean! = false
	): String
	"""
	This cover at one of your organization's artwork sizes, by its label
	(`artworkSizes`). `media_read`, as for the original. `null` while the
	resource has no file; an unknown label is an `UNKNOWN_ARTWORK_SIZE`
	error listing the organization's labels, and any image but a
	`FrontCoverImage` is `ARTWORK_NOT_FRONT_COVER`. See `graphql::artwork`.
	"""
	artwork(
		"""
		A label from artworkSizes, e.g. "tile-600".
		"""
		size: String!
	): Artwork
}

"""
One node of the disc/side tree.

Recursive by design: ERN nests `ResourceGroup`s, and flattening the tree
here would lose the distinction between a two-disc album and a one-disc
album with twice the tracks.
"""
type ResourceGroup {
	groupPk: Int!
	sequence: Int!
	title: String
	"""
	Resources directly in this group, in track order.
	"""
	resources: [Resource!]!
	"""
	Nested subgroups.
	"""
	children: [ResourceGroup!]!
}

type StatusCount {
	status: String!
	count: Int!
}

"""
What //SCHEMA measured from the file itself — never what the sender
asserted. `null` until the file has been analyzed, usually seconds after it
lands (`resource.analyzed` announces it).
"""
type Technical {
	mediaKind: String
	container: String
	codec: String
	bitrateKbps: Int
	sampleRateHz: Int
	bitDepth: Int
	channels: Int
	durationMs: Int
	lossless: Boolean
	integratedLufs: Float
	truePeakDbtp: Float
	widthPx: Int
	heightPx: Int
}

"""
The trial, for an organization that is in one.
"""
type Trial {
	startedAt: DateTime
	"""
	When the first charge falls due — or, once canceled, when access ends
	and the organization's data is deleted.
	"""
	endsAt: DateTime!
	"""
	Set once canceled. Nothing will be charged; resumable until `endsAt`.
	"""
	cancelledAt: DateTime
	"""
	Set once an owner or admin asked to start the subscription now. The
	charge follows within seconds; until it lands the organization is still
	in its trial.
	"""
	conversionRequestedAt: DateTime
	"""
	Whether the caller may cancel or resume: a signed-in owner or admin,
	not impersonated. Lets the console show the button only where it works.
	"""
	mayChange: Boolean!
}

input UpdateArtworkSizeInput {
	artworkSizePk: Int!
	"""
	Renaming never re-renders: renditions are keyed by what is rendered.
	"""
	label: String
	width: Int
	height: Int
	crop: ArtworkCrop
	format: ArtworkFormat
	"""
	`null` for the default (JPEG) or none (PNG). Left out, it is kept —
	unless the format changes, when it resets to that default.
	"""
	quality: Int
	upscale: Boolean
}

input UpdateWebhookEndpointInput {
	endpointPk: Int!
	"""
	A new destination. The signing secret does not change.
	"""
	url: String
	"""
	`null` clears it.
	"""
	description: String
	eventTypes: [String!]
	"""
	`null` for every catalog; omit to leave as it is.
	"""
	catalogPks: [Int!]
	"""
	Turn it off or back on. Turning it off fails what was still pending;
	turning it on receives events from now, never what it missed.
	"""
	enabled: Boolean
}

"""
The answer to "I would like to upload these bytes".
"""
type UploadTicket {
	"""
	True when //SCHEMA already holds these exact bytes — from any catalog,
	since the store is content-addressed and global. Nothing needs to be
	uploaded; call `completeUpload` straight away.
	
	This is the README's "ingestion appears instant for the second DSP",
	surfaced where a client can act on it. Labels reuse one image across a
	single, an EP and an album, so it is a common answer for artwork.
	"""
	alreadyPresent: Boolean!
	"""
	`null` when `alreadyPresent`.
	"""
	uploadUrl: String
	"""
	Headers that **must** be sent with the PUT, exactly as given. They are
	inside the signature: altering or omitting one invalidates the URL, which
	is what stops a client uploading bytes other than the ones it declared.
	"""
	requiredHeaders: [HttpHeader!]!
	expiresInSeconds: Int!
}

"""
Quantities for one period. No money — see the module doc.
"""
type Usage {
	"""
	Half-open. `periodEnd` is the first day *not* included.
	"""
	periodStart: String!
	periodEnd: String!
	"""
	Average bytes delivered and held across the days counted so far, today
	included. Storage overage is priced on this plus `bytesEncodedHeld`.
	"""
	bytesHeld: Int!
	"""
	What the catalogs hold right now. The figure a person means by "how big
	is my catalog"; `bytes_held` is the one they are billed on, and the two
	differ whenever the catalog grew or shrank this month.
	"""
	bytesHeldNow: Int!
	"""
	Average bytes of the encodings //SCHEMA made of those masters — the AAC
	streams and previews, FLAC, requested MP3s — over the same days. Stored
	on your behalf, and priced as storage with `bytesHeld`.
	"""
	bytesEncodedHeld: Int!
	"""
	The encodings held right now.
	"""
	bytesEncodedHeldNow: Int!
	"""
	Bytes delivered during the period, up to now. Asserted, not
	deduplicated — the number a customer can verify against their own
	records.
	"""
	bytesIngested: Int!
	"""
	Complete days with a sample, and days in the period. A figure averaged
	over four days is a different claim from one averaged over thirty-one,
	and the UI should be able to say so.
	"""
	observedDays: Int!
	periodDays: Int!
	"""
	When the live figures were measured.
	"""
	asOf: DateTime!
}

"""
The authenticated caller's own view of itself. The first thing any
integration calls, and the cheapest way to check a key works.
"""
type Viewer {
	"""
	`api_key` or `user`.
	"""
	actorType: String!
	"""
	Present only for human callers.
	"""
	userPk: Int
	orgPk: Int!
	catalogPk: Int!
	"""
	Exactly what this credential may do, so a client can hide what it cannot
	use rather than discovering it through errors.
	"""
	scopes: [String!]!
}

"""
One allowed value of a DDEX vocabulary.
"""
type VocabValue {
	"""
	Verbatim, exactly as the AVS spells it. Never normalized — //SCHEMA has
	to be able to regenerate the literal it stored.
	"""
	value: String!
	"""
	True when DDEX defines this value in an allowed-value set. False means
	it was `observed` — minted at ingest because a delivery used it and
	refusing the delivery was not an option.
	"""
	ddexDefined: Boolean!
}

"""
One event's delivery to one endpoint: the delivery log.
"""
type WebhookDelivery {
	deliveryPk: Int!
	"""
	The `webhook-id` it was sent with, and the envelope's `id`.
	"""
	eventId: String!
	eventType: String!
	occurredAt: DateTime!
	"""
	`pending` (still being tried), `succeeded` or `failed`.
	"""
	state: String!
	attempts: Int!
	lastAttemptAt: DateTime
	"""
	The HTTP status the receiver answered with, if it answered.
	"""
	lastStatus: Int
	"""
	Why the last attempt failed: `http 503`, `timeout after 10s`,
	`redirect to …`, `refused: …`.
	"""
	lastError: String
	"""
	Up to 4 KiB of what the receiver answered.
	"""
	lastResponseExcerpt: String
	lastLatencyMs: Int
	"""
	When the next attempt is due, while `pending`.
	"""
	nextAttemptAt: DateTime
	succeededAt: DateTime
}

"""
A destination for this organization's events.
"""
type WebhookEndpoint {
	endpointPk: Int!
	url: String!
	description: String
	eventTypes: [String!]!
	"""
	`null` for every catalog the organization has, including later ones.
	"""
	catalogPks: [Int!]
	enabled: Boolean!
	enabledAt: DateTime!
	disabledAt: DateTime
	disabledReason: String
	"""
	Since when every delivery has failed, while that is so.
	"""
	failingSince: DateTime
	createdAt: DateTime!
	updatedAt: DateTime!
	"""
	This endpoint's deliveries, newest first. Kept for 30 days.
	"""
	deliveries(
		"""
		At most this many, up to 100. Defaults to 20.
		"""
		first: Int,
		"""
		Only deliveries older than this `deliveryPk`, for the next page.
		"""
		before: Int,
		"""
		Only deliveries in this state: `pending`, `succeeded` or `failed`.
		"""
		state: String
	): [WebhookDelivery!]!
}

"""
One event type an endpoint can subscribe to.
"""
type WebhookEventType {
	"""
	The wire name, e.g. `release.state_changed`.
	"""
	type: String!
	description: String!
}

"""
Directs the executor to include this field or fragment only when the `if` argument is true.
"""
directive @include(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT
"""
Directs the executor to skip this field or fragment when the `if` argument is true.
"""
directive @skip(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT
"""
Provides a scalar specification URL for specifying the behavior of custom scalar types.
"""
directive @specifiedBy(url: String!) on SCALAR
schema {
	query: Query
	mutation: Mutation
}