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 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 `` 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/`, 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-` — 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 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 `` 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 }