Deliveries
A delivery is a DDEX ERN message plus the media files it references. Labels and distributors usually deliver over SFTP; you can also deliver through the API, which runs exactly the same pipeline.
Files and the message may arrive in either order. Until every referenced file has arrived — and the release has cover art — the release is blocked: held out of the catalog, and listed by blockedReleases. When the last file lands, it goes live automatically.
- querymessages
- querydeliveryRejections
- queryblockedReleases
- queryoutstandingFiles
- queryingestionStats
- mutationdeliverMessage
- mutationdeliverFile
- mutationdiscardBlockedRelease
The Message object#
One DDEX message delivered into your catalog.
Attributes
messagePkInt!Unique identifier.ddexMessageIdString!TheMessageIdfrom the message header.fileNameString!The file name it arrived under.senderDpidString!The DPID the message claims as its sender.- The sender's
PartyNamefrom the message header, verbatim, ornullwhen the message gave none. As self-asserted assenderDpid, and for display only. Messages received before 2026-10-04 have none. statusString!received,parsing,ingested,partial(ingested but still waiting for files) orfailed.- Why the message failed, when
statusisfailed. dateReceivedDateTime!When the message arrived.- A short-lived signed URL for the original XML, byte-for-byte as delivered. Requires
media_read.Arguments
ttlSecondsIntHow long the URL stays valid, in seconds. Default 300, capped at 3600.
The BlockedRelease object#
A delivered release held out of the catalog, and why.
Attributes
releasePkInt!The release.- Its UPC.
- Its title.
updatedAtDateTime!When it last changed.resourcesInt!Resources on the release, artwork included.unbackedInt!How many of them are still waiting for their file.coversInt!Front cover images with a file. Zero blocks the release.problems[String!]!What is blocking it, as sentences you can show to a person.
The OutstandingFile object#
A file a delivered message referenced that has not arrived.
Attributes
deliveryNamespaceString!The delivery it belongs to. Treat as opaque and pass it back to deliverFile unchanged.relativeUriString!The path exactly as the message wrote it. May include directories, so match a local file name against its end.- The release waiting for it.
- That release's title.
The StatusCount object#
How many messages had one status.
Attributes
statusString!A message status — see Message.status.countInt!Number of messages.
The DeliveredMessage object#
The result of deliverMessage.
Attributes
messagePkInt!The message's id.deliveryNamespaceString!The delivery's namespace. Pass it todeliverFilefor each media file.awaitingFilesInt!How many referenced files are still missing.0means the delivery is complete.duplicateBoolean!trueif this exact message had already been ingested. Nothing changed; not an error.claimedFilesInt!How many files delivered ahead of this message — with nodeliveryNamespace, waiting by filename — it claimed and matched. Already excluded fromawaitingFiles.ambiguousFiles[String!]!Waiting filenames this message could not claim unambiguously: more than one waiting file of that name, or one file two of its entries would both want. Deliver them again with this message'sdeliveryNamespace.purged[PurgedRelease!]!For aPurgeReleaseMessage, what happened to each release it named. Empty for any other message.
The PurgedRelease object#
One release a PurgeReleaseMessage named, and what the purge did to it.
Attributes
- The UPC the purge named, if any.
- The GRid the purge named, if any.
- The release in your catalog it matched.
nullwhenoutcomeisUNKNOWN. outcomePurgeOutcome!What the purge did — see PurgeOutcome.
{
"upc": "0196871402293",
"grid": null,
"releasePk": 48213,
"outcome": "WITHDRAWN"
}The PurgeOutcome enum#
What a purge did to one release it named.
Values
WITHDRAWNThe release was live, or waiting for files, and is nowwithdrawn.ALREADY_WITHDRAWNIt was already withdrawn. Nothing changed; a resent purge is not an error.DRAFTThe identifier belongs to a draft you have not published. Nothing to take back, so nothing changed.UNKNOWNYour catalog has no release with that identifier. Nothing was created.OTHER_ACCOUNTA different SFTP delivery account delivered the release, so this purge may not withdraw it. Never returned bydeliverMessage, which has no delivery account.
The DeliveredFile object#
The result of deliverFile.
Attributes
deliveryNamespaceString!The namespace you delivered to.relativeUriString!The path you delivered.outcomeString!promoted— matched, and the resource now has its bytes.awaiting— delivered without adeliveryNamespace: stored, and waiting for a message delivered within 24 hours that names it to claim it.orphan— stored, but no message is waiting for it yet (it may simply be early, or the path may be wrong).duplicate— these bytes were already delivered at this path.hashMismatch— matched, but the bytes differ from the checksum the message declared; the resource is markedfailed, and a corrected re-delivery clears it.- The resource this file satisfied, for
promotedandhashMismatch.
The DeliveryRejection object#
A file refused at intake and not stored. Today that means a lossy audio
master: //SCHEMA accepts WAV, AIFF, FLAC and ALAC, decided from the
file's header. A release waiting for the file stays pending until an
accepted one arrives at the same path.
Attributes
rejectionPkInt!Unique identifier, asdelivery.file_rejectedcarries it.deliveryNamespaceString!The batch the file arrived in.relativeUriString!The file's path within the batch.- The file's SHA-256 when it was refused after upload (the API);
nullover SFTP, where it is refused before being stored. - The file's size.
detectedFormatString!What the header says it is, e.g.mp3oraac.reasonString!What arrived and what is accepted.rejectedAtDateTime!When it was refused.
{
"rejectionPk": 77,
"deliveryNamespace": "BATCH_20261023",
"relativeUri": "resources/A1.mp3",
"sha256": null,
"byteSize": 8412330,
"detectedFormat": "mp3",
"reason": "MP3 is not accepted as a master: deliver WAV, AIFF, FLAC or ALAC",
"rejectedAt": "2026-10-23T14:02:11Z"
}List messages#
Recent DDEX messages delivered into your catalog, newest first.
Arguments
- Maximum number to return. Clamped to 1–200.
Returns
A list of Message objects.
query {
messages(limit: 2) {
messagePk
fileName
status
failureReason
dateReceived
}
}curl https://api.schema.fm/graphql \
-H "Authorization: Bearer $SCHEMA_API_KEY" \
-H "Content-Type: application/json" \
--data-binary @- <<'EOF'
{
"query": "query {\n messages(limit: 2) {\n messagePk\n fileName\n status\n failureReason\n dateReceived\n }\n}"
}
EOFconst res = await fetch("https://api.schema.fm/graphql", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.SCHEMA_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
query: `
query {
messages(limit: 2) {
messagePk
fileName
status
failureReason
dateReceived
}
}
`,
}),
});
const { data, errors } = await res.json();{
"data": {
"messages": [
{
"messagePk": 220318,
"fileName": "0196871402293.xml",
"status": "ingested",
"failureReason": null,
"dateReceived": "2026-09-17T09:14:03.118220+00:00"
},
{
"messagePk": 220301,
"fileName": "0196871399012.xml",
"status": "partial",
"failureReason": null,
"dateReceived": "2026-09-17T08:51:47.902114+00:00"
}
]
}
}List rejected files#
Files refused at intake, newest first. Today that means lossy audio masters — MP3, AAC, Ogg, WMA, or a WAV holding compressed audio — decided from the file's header, over SFTP and the API alike. A release waiting for one stays blocked until an accepted file arrives at the same path. delivery.file_rejected announces each one.
Arguments
- How many to return. Default 50, at most 200.
Returns
A list of DeliveryRejection.
{
deliveryRejections(limit: 1) {
relativeUri
detectedFormat
reason
}
}curl https://api.schema.fm/graphql \
-H "Authorization: Bearer $SCHEMA_API_KEY" \
-H "Content-Type: application/json" \
--data-binary @- <<'EOF'
{
"query": "{\n deliveryRejections(limit: 1) {\n relativeUri\n detectedFormat\n reason\n }\n}"
}
EOFconst res = await fetch("https://api.schema.fm/graphql", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.SCHEMA_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
query: `
{
deliveryRejections(limit: 1) {
relativeUri
detectedFormat
reason
}
}
`,
}),
});
const { data, errors } = await res.json();{
"data": {
"deliveryRejections": [
{
"relativeUri": "resources/A1.mp3",
"detectedFormat": "mp3",
"reason": "MP3 is not accepted as a master: deliver WAV, AIFF, FLAC or ALAC"
}
]
}
}List blocked releases#
Delivered releases that cannot go live yet, and why. This is a worklist, not an approval queue: when the missing files arrive the release goes live by itself.
Arguments
- Maximum number to return. Clamped to 1–200.
Returns
A list of BlockedRelease objects.
query {
blockedReleases(limit: 5) {
releasePk
upc
title
resources
unbacked
covers
problems
}
}curl https://api.schema.fm/graphql \
-H "Authorization: Bearer $SCHEMA_API_KEY" \
-H "Content-Type: application/json" \
--data-binary @- <<'EOF'
{
"query": "query {\n blockedReleases(limit: 5) {\n releasePk\n upc\n title\n resources\n unbacked\n covers\n problems\n }\n}"
}
EOFconst res = await fetch("https://api.schema.fm/graphql", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.SCHEMA_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
query: `
query {
blockedReleases(limit: 5) {
releasePk
upc
title
resources
unbacked
covers
problems
}
}
`,
}),
});
const { data, errors } = await res.json();{
"data": {
"blockedReleases": [
{
"releasePk": 48277,
"upc": "0196871399012",
"title": "Tidewater",
"resources": 11,
"unbacked": 2,
"covers": 0,
"problems": ["2 of 11 files have not arrived yet", "no cover art"]
}
]
}
}List outstanding files#
Files that delivered messages referenced but that have not arrived.
Use it to route a file you are about to deliver: match the file's
name against relativeUri, and pass that entry's
deliveryNamespace to deliverFile.
Arguments
- Maximum number to return. Clamped to 1–1000.
Returns
A list of OutstandingFile objects.
query {
outstandingFiles(limit: 2) {
deliveryNamespace
relativeUri
releasePk
title
}
}curl https://api.schema.fm/graphql \
-H "Authorization: Bearer $SCHEMA_API_KEY" \
-H "Content-Type: application/json" \
--data-binary @- <<'EOF'
{
"query": "query {\n outstandingFiles(limit: 2) {\n deliveryNamespace\n relativeUri\n releasePk\n title\n }\n}"
}
EOFconst res = await fetch("https://api.schema.fm/graphql", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.SCHEMA_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
query: `
query {
outstandingFiles(limit: 2) {
deliveryNamespace
relativeUri
releasePk
title
}
}
`,
}),
});
const { data, errors } = await res.json();{
"data": {
"outstandingFiles": [
{
"deliveryNamespace": "20260917_0851_0196871399012",
"relativeUri": "resources/A1_Tidewater.flac",
"releasePk": 48277,
"title": "Tidewater"
},
{
"deliveryNamespace": "20260917_0851_0196871399012",
"relativeUri": "resources/cover.jpg",
"releasePk": 48277,
"title": "Tidewater"
}
]
}
}Ingestion statistics#
Message counts by status over a trailing window — the numbers behind an ingestion-health dashboard.
Arguments
- Window length in days, ending now. Clamped to 1–365.
Returns
A list of StatusCount objects, one per status that occurred.
query {
ingestionStats(days: 7) {
status
count
}
}curl https://api.schema.fm/graphql \
-H "Authorization: Bearer $SCHEMA_API_KEY" \
-H "Content-Type: application/json" \
--data-binary @- <<'EOF'
{
"query": "query {\n ingestionStats(days: 7) {\n status\n count\n }\n}"
}
EOFconst res = await fetch("https://api.schema.fm/graphql", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.SCHEMA_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
query: `
query {
ingestionStats(days: 7) {
status
count
}
}
`,
}),
});
const { data, errors } = await res.json();{
"data": {
"ingestionStats": [
{ "status": "ingested", "count": 412 },
{ "status": "partial", "count": 9 },
{ "status": "failed", "count": 2 }
]
}
}Deliver a DDEX message#
Ingest a DDEX ERN message you have already uploaded with requestDeliveryUpload. It goes through exactly the same parse-and-ingest pipeline as an SFTP delivery, and your catalog's activation settings apply.
Requires both catalog_write and media_write.
Delivering the same file again is safe: it is recognized and
reported as duplicate: true with nothing changed.
A PurgeReleaseMessage withdraws the release it names from your
catalog — and only yours — exactly as
withdrawRelease would. It never
creates a release and never changes metadata; purged reports what
it did to each release it named.
Parse and mapping failures are returned as errors whose message
starts ERN parse error or ERN map error.
Arguments
- SHA-256 of the uploaded XML, as 64 lowercase hex characters.
- The original file name, for the delivery log.
Returns
A DeliveredMessage. Pass its deliveryNamespace to deliverFile for each media file.
mutation {
deliverMessage(
sha256: "4c1d8e7f6a5b4c3d2e1f0a9b8c7d6e5f4a3b2c1d0e9f8a7b6c5d4e3f2a1b0c9d"
fileName: "0196871402293.xml"
) {
messagePk
deliveryNamespace
awaitingFiles
duplicate
}
}curl https://api.schema.fm/graphql \
-H "Authorization: Bearer $SCHEMA_API_KEY" \
-H "Content-Type: application/json" \
--data-binary @- <<'EOF'
{
"query": "mutation {\n deliverMessage(\n sha256: \"4c1d8e7f6a5b4c3d2e1f0a9b8c7d6e5f4a3b2c1d0e9f8a7b6c5d4e3f2a1b0c9d\"\n fileName: \"0196871402293.xml\"\n ) {\n messagePk\n deliveryNamespace\n awaitingFiles\n duplicate\n }\n}"
}
EOFconst res = await fetch("https://api.schema.fm/graphql", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.SCHEMA_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
query: `
mutation {
deliverMessage(
sha256: "4c1d8e7f6a5b4c3d2e1f0a9b8c7d6e5f4a3b2c1d0e9f8a7b6c5d4e3f2a1b0c9d"
fileName: "0196871402293.xml"
) {
messagePk
deliveryNamespace
awaitingFiles
duplicate
}
}
`,
}),
});
const { data, errors } = await res.json();{
"data": {
"deliverMessage": {
"messagePk": 220344,
"deliveryNamespace": "console-4c1d8e7f6a5b",
"awaitingFiles": 3,
"duplicate": false
}
}
}Deliver a file#
Land an uploaded media file against a delivery, exactly as if it had arrived over SFTP. If a message is waiting for it, the resource is backed and — once nothing else is missing — the release goes live.
The file may arrive before its message; it is kept and matched
when the message lands. See outcome on
DeliveredFile for what happened.
Arguments
deliveryNamespaceStringThe delivery the file belongs to. Must match the message's namespace exactly — take it fromdeliverMessageoroutstandingFiles. Omit it when no message expects the file yet: the file is stored and waits by its bare filename (relativeUriwith no directory), and the message, delivered within 24 hours, claims it.- The file's path exactly as the message's
<File>element wrote it, e.g.resources/A1_Tidewater.flac. - SHA-256 of the uploaded bytes.
Returns
mutation {
deliverFile(
deliveryNamespace: "console-4c1d8e7f6a5b"
relativeUri: "resources/A1_Signal_Fire.flac"
sha256: "9f2c4e0d4b1a7c3e8f6d5b2a1c0e9d8f7a6b5c4d3e2f1a0b9c8d7e6f5a4b3c2d"
) {
outcome
resourcePk
}
}curl https://api.schema.fm/graphql \
-H "Authorization: Bearer $SCHEMA_API_KEY" \
-H "Content-Type: application/json" \
--data-binary @- <<'EOF'
{
"query": "mutation {\n deliverFile(\n deliveryNamespace: \"console-4c1d8e7f6a5b\"\n relativeUri: \"resources/A1_Signal_Fire.flac\"\n sha256: \"9f2c4e0d4b1a7c3e8f6d5b2a1c0e9d8f7a6b5c4d3e2f1a0b9c8d7e6f5a4b3c2d\"\n ) {\n outcome\n resourcePk\n }\n}"
}
EOFconst res = await fetch("https://api.schema.fm/graphql", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.SCHEMA_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
query: `
mutation {
deliverFile(
deliveryNamespace: "console-4c1d8e7f6a5b"
relativeUri: "resources/A1_Signal_Fire.flac"
sha256: "9f2c4e0d4b1a7c3e8f6d5b2a1c0e9d8f7a6b5c4d3e2f1a0b9c8d7e6f5a4b3c2d"
) {
outcome
resourcePk
}
}
`,
}),
});
const { data, errors } = await res.json();{
"data": {
"deliverFile": {
"outcome": "promoted",
"resourcePk": 771201
}
}
}Discard a blocked release#
Throw away a blocked delivery that will never be completed. Only
pending releases can be discarded — they were never in the catalog,
so nothing is taken back. A live release cannot be removed this way.
Arguments
- The blocked release.
Returns
true if it was discarded; false if no pending release matched (unknown, not yours, or already live).
mutation {
discardBlockedRelease(releasePk: 48277)
}curl https://api.schema.fm/graphql \
-H "Authorization: Bearer $SCHEMA_API_KEY" \
-H "Content-Type: application/json" \
--data-binary @- <<'EOF'
{
"query": "mutation {\n discardBlockedRelease(releasePk: 48277)\n}"
}
EOFconst res = await fetch("https://api.schema.fm/graphql", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.SCHEMA_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
query: `
mutation {
discardBlockedRelease(releasePk: 48277)
}
`,
}),
});
const { data, errors } = await res.json();{
"data": {
"discardBlockedRelease": true
}
}