Falcon Media Server External Metadata API Falcon Media Server

Base URL: http://<media-server>:8080
Format: JSON requests and JSON responses (XML accepted on selected metadata endpoints)
Authentication: Trusted LAN ? no bearer token required

Overview

The Falcon Media Server External Metadata API lets third-party systems attach structured metadata to the video and graphics files Falcon Media Server already manages. The same canonical model is used regardless of how metadata enters the system, so an integration can pick whichever ingestion path fits the source.

Typical use cases include MAM hand-off (Dalet, Avid, Vidispine, EVS, in-house MAMs), automated ingest scripts that drop files plus sidecars into a watchfolder, browser-driven metadata edits from Falcon Media Manager, and Falcon Play pushing metadata it has received from upstream systems.

Tags are first-class. Any tag delivered as external metadata is auto-created in knownTags and merged into the file's tag list using the same socket events the Tag Management modal uses. Falcon Play needs zero changes to receive tags this way.

Ingestion paths

Four ingestion paths exist. All of them produce the same canonical object and call the same applyExternalMetadata() orchestrator inside Falcon Media Server.

PathWhereBest for
Sidecar fileDropped next to the media file in the watchfolder.MAM hand-off, file-based delivery, asynchronous workflows.
HTTP uploadPOST /api/files/upload ? multipart upload plus optional metadata field.Direct push of file and metadata in a single request.
HTTP metadata endpointsGET/PUT/PATCH/POST on /api/files/:type/:filename/metadata.Read, replace or enrich metadata after the file has been ingested.
Socket.IO eventsetFileMetadata on the same socket.io endpoint as the Media Manager UI.Live UI edits and system-to-system push.

Canonical metadata model

Every ingestion path is normalised to this shape before it is applied. Only fileName is required. fileType is inferred from the file extension if omitted. The extended bag is free-form and passed through unchanged ? this is where any MAM-specific fields live.

{
  "fileName":    "interview.mp4",
  "fileType":    "video",
  "externalId":  "MAM-9876",
  "source":      "dalet",

  "tags":        ["Breaking News", "Sports"],

  "extended":    {
    "title":        "Interview with PM",
    "description":  "Long-form sit-down",
    "location":     { "name": "Christiansborg", "lat": 55.676, "lon": 12.580 },
    "people":       [{ "name": "Mette F.", "role": "PM" }],
    "rightsExpiry": "2027-01-01",
    "customFields": { "anything": "you-want" }
  }
}
FieldRequiredDescription
fileNameyesBasename only. Matches the entry the tag system uses for the file.
fileTypenovideo or graphics. Inferred from extension if omitted.
externalIdnoStable ID from the source system. Recommended for correlation.
sourcenoFree text identifying the system that produced the metadata.
tagsnoArray of tag names. Unioned with existing tags and auto-created in knownTags.
extendednoFree-form object. Shallow-merged on PATCH, replaced on PUT.
Tag handling. The tags array is the only field routed through the Falcon tag system. Tags inside extended are not picked up. Always send tags at the top level.

Base URL and request format

http://<media-server>:8080

The Media Manager runs on port 8080. All HTTP endpoints listed below live on the same origin. Send JSON bodies with:

Content-Type: application/json

The metadata PUT/PATCH/POST endpoints also accept raw XML when sent with:

Content-Type: application/xml

Authentication

Falcon Media Server is designed to run on a trusted LAN behind Falcon Play. The external metadata endpoints do not currently require a bearer token. If the Media Server needs to be reachable from an untrusted network, place it behind a reverse proxy that adds authentication or restricts access by IP.

Production warning. Do not expose port 8080 directly to the public internet. The Media Manager UI and the upload endpoint share that port.

Response model

Successful HTTP responses use a flat shape with an explicit ok flag:

{
  "ok": true,
  "fileName": "interview.mp4",
  "fileType": "video",
  "applied": { "tags": 2, "extended": 3, "externalId": true }
}

Read responses include the current external metadata for the file:

{
  "ok": true,
  "fileName": "interview.mp4",
  "fileType": "video",
  "externalId": "MAM-9876",
  "metadataSource": "dalet",
  "extended": { "title": "Interview with PM" }
}

Error responses use:

{
  "ok": false,
  "error": "Human-readable message"
}

Status codes

StatusMeaning
200Successful read or update.
400Invalid request ? missing type, unsupported file type, or unparseable metadata body.
404Target file is not in the catalogue.
413Upload exceeds the 8 GB cap, or sidecar payload exceeds 1 MB.
500Internal error. See the Media Server log.

Endpoint summary

AreaEndpointPurpose
UploadPOST /api/files/uploadUpload a file and optionally attach metadata in the same request.
ListingGET /api/files/{type}List videos or graphics. Supports detail, tag, limit and offset.
ListingGET /api/files/{type}/{filename}Read the full record for one file (ffprobe fields, tags, externalId, extended).
MetadataGET /api/files/{type}/{filename}/metadataRead external metadata for one file.
MetadataPUT /api/files/{type}/{filename}/metadataReplace the extended object.
MetadataPATCH /api/files/{type}/{filename}/metadataShallow-merge into the extended object.
MetadataPOST /api/files/{type}/{filename}/metadataAlias for PATCH, for clients that cannot send PATCH.
SidecarWatchfolderDrop .json / .xml / .xmp sidecar next to the media file.
SocketsetFileMetadataPush metadata for one file over socket.io.
SocketrequestFileMetadataRead external metadata for one file over socket.io.

Upload

Upload a file with metadata

POST/api/files/upload

Multipart upload of a video or graphics file. Metadata can travel with the file in one of three ways:

  1. A JSON or XML string in the metadata form field.
  2. Individual canonical fields delivered as form fields (externalId, tags).
  3. No metadata ? upload first, attach metadata later via the metadata endpoints.
FieldRequiredDescription
fileyesThe binary file. Multer field name must be file.
typeyesvideo or graphics ? picks the destination folder.
metadatanoJSON or XML string. Mapped through the mapping engine.
externalIdnoAlternative to metadata.externalId.
tagsnoComma-separated string or array. Alternative to metadata.tags.

Successful response:

{
  "ok": true,
  "fileName": "interview.mp4",
  "fileType": "video",
  "bytes": 524288000,
  "metadataAccepted": true
}

The file lands on disk first; the metadata is parked as an orphan and applied as soon as the post-upload scan registers the file. The upload size cap is 8 GB.

curl -X POST \
  -F "file=@interview.mp4" \
  -F "type=video" \
  -F 'metadata={
        "tags": ["Breaking News"],
        "externalId": "MAM-9876",
        "extended": { "title": "Interview with PM" }
      }' \
  http://media:8080/api/files/upload

Listing files

Two list-style endpoints expose the Media Server's cache so external systems can browse what is currently catalogued without opening the Media Manager UI. Both endpoints honour the same tag system used by the rest of Falcon.

List files of a type

GET/api/files/{type}

{type} is video or graphics. Returns a paginated list of files for that type.

QueryDefaultDescription
detailsummarysummary for a small list-friendly projection, full for the complete cache record.
tagnoneComma-separated list of tag names. Results must contain all listed tags.
limit200Maximum entries returned. Hard cap of 5000.
offset0Number of entries to skip. Use with limit for pagination.

Response shape:

{
  "ok": true,
  "fileType": "video",
  "detail": "summary",
  "count": 42,
  "limit": 200,
  "offset": 0,
  "files": [ ... ]
}

Summary projection

Each entry in files[] when detail=summary contains just enough to render a list view:

{
  "fileName":       "interview.mp4",
  "fileType":       "video",
  "bytes":          524288000,
  "duration":       245.5,
  "resolution":     "1920x1080",
  "lastModified":   "2026-06-11T09:42:13.000Z",
  "externalId":     "MAM-9876",
  "metadataSource": "dalet",
  "tags":           ["Breaking News", "Sports"],
  "title":          "Interview with PM",
  "hasExtended":    true
}

Full projection

When detail=full each entry is the complete cache record ? every ffprobe field plus tags, externalId, metadataSource and the full extended bag. Use this when an external system needs to mirror the Media Server catalogue.

Examples

curl 'http://media:8080/api/files/video?detail=summary&limit=50'
curl 'http://media:8080/api/files/graphics?detail=full'
curl 'http://media:8080/api/files/video?tag=Sport,Live&limit=20&offset=0'

Read one file

GET/api/files/{type}/{filename}

Returns the full record for a single file, identical to one entry from the detail=full listing. Responds 404 if the file is not in the catalogue.

curl http://media:8080/api/files/video/interview.mp4
{
  "ok": true,
  "fileName":       "interview.mp4",
  "fileType":       "video",
  "duration":       245.5,
  "resolution":     "1920x1080",
  "fileSize":       524288000,
  "lastModified":   "2026-06-11T09:42:13.000Z",
  "externalId":     "MAM-9876",
  "metadataSource": "dalet",
  "tags":           ["Breaking News", "Sports"],
  "extended":       { "title": "Interview with PM", "location": { "name": "Christiansborg" } }
}
Listing vs metadata. GET /api/files/{type}/{filename} returns the full cache record. GET /api/files/{type}/{filename}/metadata returns only the external metadata fields (externalId, metadataSource, extended). Use whichever fits your integration.

Metadata endpoints

Read metadata

GET/api/files/{type}/{filename}/metadata

Returns the current external metadata for one file. Responds 404 if the file is not in the catalogue.

curl http://media:8080/api/files/video/interview.mp4/metadata
{
  "ok": true,
  "fileName": "interview.mp4",
  "fileType": "video",
  "externalId": "MAM-9876",
  "metadataSource": "dalet",
  "extended": {
    "title": "Interview with PM",
    "location": { "name": "Christiansborg" }
  }
}

Patch metadata

PATCH/api/files/{type}/{filename}/metadata

Shallow-merges the incoming canonical object into the file's extended bag. Tags are unioned with existing tags. Use this for incremental enrichment.

curl -X PATCH \
  -H "Content-Type: application/json" \
  -d '{
        "tags": ["Politics"],
        "extended": { "rightsExpiry": "2028-01-01" }
      }' \
  http://media:8080/api/files/video/interview.mp4/metadata
{
  "ok": true,
  "fileName": "interview.mp4",
  "fileType": "video",
  "applied": { "tags": 1, "extended": 1, "externalId": false }
}

Replace metadata

PUT/api/files/{type}/{filename}/metadata

Replaces the entire extended object. Existing keys not present in the new payload are removed. Tags are still unioned ? they live in the tag system, not in extended.

curl -X PUT \
  -H "Content-Type: application/xml" \
  --data-binary @asset.xml \
  http://media:8080/api/files/video/interview.mp4/metadata

Patch metadata (alias)

POST/api/files/{type}/{filename}/metadata

Alias for PATCH. Provided for clients that cannot send PATCH through their HTTP layer or proxy.

Sidecar files

Drop a sidecar file in the watchfolder next to the media file. Falcon Media Server's chokidar watcher detects sidecars on add/change and routes them to the metadata pipeline instead of the normal scan debouncer.

Discovery order

FILE{media}.{ext}.json | .xml | .xmp ? {media}.json | .xml | .xmp

For a media file interview.mp4, the watcher looks for sidecars in this order:

interview.mp4.json     ? preferred
interview.mp4.xml
interview.mp4.xmp
interview.json
interview.xml
interview.xmp

JSON sidecar

File interview.mp4.json next to interview.mp4 in the watchfolder:

{
  "fileName": "interview.mp4",
  "fileType": "video",
  "externalId": "MAM-9876",
  "source": "dalet",
  "tags": ["Breaking News", "Sports"],
  "extended": {
    "title": "Interview with PM",
    "location": { "name": "Christiansborg" },
    "people": [{ "name": "Mette F." }]
  }
}

XML sidecar

XML payloads are translated through a per-MAM mapping file (see Mapping engine below). The bundled generic-mam mapping recognises this shape:

<Asset id="MAM-9876">
  <FileName>interview.mp4</FileName>
  <MediaType>Video</MediaType>
  <Title>Interview with PM</Title>
  <Description>Long-form sit-down</Description>
  <Categories>
    <Category>Breaking News</Category>
    <Category>Sports</Category>
  </Categories>
  <Location>Christiansborg</Location>
  <People>
    <Person>Mette F.</Person>
  </People>
</Asset>
Security. The XML parser runs with processEntities:false and rejects any payload that declares a DOCTYPE (XXE / billion-laughs defence). Sidecars are capped at 1 MB.

Ordering robustness

ScenarioBehaviour
Sidecar arrives before the media file.Stored in an in-memory orphan map; drained on the next scan.
Media file arrives before the sidecar.Sidecar add event applies it immediately.
Sidecar updated later.Chokidar change re-applies it; extended merges, tags union.
Server restart with pre-existing sidecars on disk.Scanned and applied a few seconds after init.
Media file renamed in the UI.Sidecars follow the file; cached extended is preserved.
Media file deleted.Sidecars and cached extended are removed.
Corrupt or oversized sidecar.Rejected with a warning in the server log; the media file is untouched.

Socket.IO events

Falcon Media Server runs a socket.io server on the same port (8080). Browser and system clients can push or read metadata over the same connection used by the Media Manager UI.

Push metadata

SOCKETsetFileMetadata (ack)

Push a canonical metadata object for a single file. Same semantics as the HTTP PATCH endpoint.

socket.emit('setFileMetadata', {
  fileName: 'Carlo.mov',
  fileType: 'video',
  externalId: 'MAM-CARLO-7',
  tags: ['Musik', 'LiveSession'],
  extended: {
    title: 'Carlo ? Live Session #7',
    session: { venue: 'Vega Lille Sal', date: '2026-05-22' }
  }
}, (ack) => {
  // { ok: true, fileName: 'Carlo.mov', fileType: 'video',
  //   applied: { tags: 2, extended: 2, externalId: true } }
});

Read metadata

SOCKETrequestFileMetadata (ack)

Read current external metadata for one file. Same payload shape as the HTTP GET endpoint.

socket.emit('requestFileMetadata',
  { type: 'video', fileName: 'Carlo.mov' },
  (data) => {
    // { ok, fileName, externalId, metadataSource, extended }
  }
);

Tag integration

This is the central piece. When external metadata arrives with tags, Falcon Media Server:

  1. For each tag not already in knownTags, appends { name, color: "#4331ed" } and emits saveKnownTags to Falcon Play and knownTags to all Media Manager browsers.
  2. Merges the tags into the file's entry under videosExtraConfig or graphicsExtraConfig. Existing user-added tags are preserved ? this is a union, not a replace.
  3. Emits saveVideosConfig or saveGraphicsConfig to Falcon Play and videosExtraConfig / graphicsExtraConfig to all Media Manager browsers.
Falcon Play needs no changes for tags. The events listed above are the same events the Tag Management modal uses today, so Falcon Play picks tags up automatically when delivered through external metadata.

Mapping engine

Different MAMs export different field names. Mapping files translate raw parsed payloads into the canonical model. They live in two folders ? bundled and per-deploy overrides:

LocationPurpose
<app-dir>/metadata-mappings/*.jsonShips with the app.
<appData>/FalconMediaServer/metadata-mappings/*.jsonPer-deploy overrides. Same source name overrides the bundled mapping.

A mapping file looks like this:

{
  "source": "generic-mam",
  "match": { "rootElement": "Asset" },
  "fields": {
    "fileName":              "FileName",
    "fileType":              "MediaType",
    "externalId":            "@id",
    "tags":                  "Categories.Category[*]",
    "extended.title":        "Title",
    "extended.description":  "Description",
    "extended.location":     "Location",
    "extended.people":       "People.Person[*]"
  },
  "valueMap": {
    "fileType": { "Video": "video", "Image": "graphics" }
  }
}

Path syntax

A small dotted subset of JSONPath. Works on both JSON and parsed XML.

SyntaxMeaning
a.b.cObject navigation.
xs[*]Iterate array. A scalar is treated as a one-element array.
xs[0]Numeric index.
@idXML attribute. The underlying parser prefixes attributes with @.
#textXML text node when the element also has attributes.

Mapping detection

A mapping's match block decides whether the mapping applies to an incoming payload:

MatchMeaning
{ "rootElement": "Asset" }Top-level key or XML root element must be Asset.
{ "hasField": "Categories.Category" }Path must resolve to something non-undefined.
{ "sourceField": "@type", "sourceValue": "video" }Path must equal the given value.

If multiple mappings match, the first match wins. If no mapping matches but the payload already looks canonical (has fileName plus at least one of tags/extended/externalId/fileType), it is accepted as-is ? this is the "Falcon canonical" fast-path.

Where the metadata is stored

External metadata is attached directly to the file's metadata object in the cache. The same object is emitted in returnVideoData / returnGraphicsData to Falcon Play and in fileList to the Media Manager.

{
  "name": "interview.mp4",
  "fileName": "interview.mp4",
  "duration": 245.5,
  "resolution": "1920x1080",
  // ? ffprobe fields ?

  "externalId":     "MAM-9876",
  "metadataSource": "dalet",
  "extended": {
    "title": "Interview with PM",
    "location": { "name": "Christiansborg" }
  }
}

The cache is persisted at:

Linux:   ~/.config/FalconMediaServer/metadataCache.json
macOS:   ~/Library/Application Support/FalconMediaServer/metadataCache.json
Windows: %APPDATA%\FalconMediaServer\metadataCache.json

Common examples

Drop a JSON sidecar (watchfolder)

cat > /var/falcon/media/interview.mp4.json <<'JSON'
{
  "fileName": "interview.mp4",
  "tags": ["Breaking News"],
  "extended": { "title": "Interview with PM" }
}
JSON

Within ~1 second of the file being stable, the Media Server log shows:

? External metadata applied ? interview.mp4
   (source: sidecar:json, tags: 1, extended: 1)
?? Tag save ? saveVideosConfig | interview.mp4 | tags: [Breaking News]

Upload a file and its metadata in one request

curl -X POST \
  -F "file=@interview.mp4" \
  -F "type=video" \
  -F 'metadata={"tags":["Breaking"],"extended":{"title":"PM"}}' \
  http://media:8080/api/files/upload

Patch additional fields later

curl -X PATCH \
  -H "Content-Type: application/json" \
  -d '{"tags":["Politics"],"extended":{"rightsExpiry":"2028-01-01"}}' \
  http://media:8080/api/files/video/interview.mp4/metadata

Push an XML payload mapped through generic-mam

curl -X PUT \
  -H "Content-Type: application/xml" \
  --data-binary @asset.xml \
  http://media:8080/api/files/video/interview.mp4/metadata

Read the current metadata for a file

curl http://media:8080/api/files/video/interview.mp4/metadata

List all videos with a tag

curl 'http://media:8080/api/files/video?tag=Breaking%20News&detail=summary'

Mirror the full graphics catalogue

curl 'http://media:8080/api/files/graphics?detail=full&limit=5000'

Python ? upload plus metadata in one call

import requests, json

with open("interview.mp4", "rb") as f:
    r = requests.post(
        "http://media:8080/api/files/upload",
        files={"file": f},
        data={
            "type": "video",
            "metadata": json.dumps({
                "tags": ["Breaking News", "Sports"],
                "externalId": "MAM-9876",
                "extended": {
                    "title": "Interview with PM",
                    "people": [{"name": "Mette F.", "role": "PM"}],
                },
            }),
        },
    )
print(r.json())

Node.js ? push metadata over socket.io

const { io } = require("socket.io-client");
const sock = io("http://media:8080");

sock.on("connect", () => {
  sock.emit("setFileMetadata", {
    fileName: "Carlo.mov",
    fileType: "video",
    tags: ["Musik"],
    extended: { title: "Carlo ? Live Session #7" }
  }, (ack) => {
    console.log(ack);
    sock.close();
  });
});
  1. Pick the ingestion path that fits the source: sidecar for file-based MAM hand-off, HTTP upload for direct push, HTTP PATCH for enrichment, socket.io for live updates.
  2. If the source uses a custom XML schema, drop a mapping file in metadata-mappings/ so the canonical model is produced automatically.
  3. Always include externalId when available ? it is the only way to correlate a Falcon file back to the source system after a rename.
  4. Send tags through the top-level tags array, never inside extended ? only the tags array is routed through the Falcon tag system.
  5. Keep custom fields under extended ? anything under extended is passed through unmodified.
  6. Test with a single file before mass ingestion. GET /api/files/{type}/{filename}/metadata shows exactly what landed.

Troubleshooting

SymptomLikely causeWhat to check
404 File not found on the metadata endpoint.The Media Server has not yet scanned the file, or the filename does not match the sanitised name on disk.Wait for the next scan, or query GET /api/files/{type}/{filename}/metadata after the upload response.
Tags from the sidecar do not appear on the file card.Falcon Play is not running or not connected ? tag persistence requires Play to acknowledge saveKnownTags / saveVideosConfig.Confirm the Play connection in the Media Server log. The LOG tab shows ?? Tag save ? saveVideosConfig | ....
Sidecar is ignored.Sidecar extension is not one of .json / .xml / .xmp, or the file name does not match a media file.Use the canonical naming pattern {media}.{ext}.json or include fileName inside the sidecar payload.
400 XML sidecar contains DOCTYPE ? refused.XXE defence ? the parser rejects any <!DOCTYPE> declaration.Strip the DOCTYPE line from the payload. Sidecars never need a DTD.
Sidecar over 1 MB is rejected.Hard cap to protect the parser.Move oversize fields out of the sidecar ? sidecars are metadata, not media.
extended appears as [object Object] in the Media Manager popup.Browser cached an older Media Manager build.Hard-reload the browser (Ctrl+Shift+R).
Mapping does not pick up a custom MAM XML.The mapping's match block does not detect the payload.Confirm the root element name, or add a hasField / sourceField match. Restart the Media Server so metadata-mappings/ is re-scanned.

Version notes

This document describes the External Metadata API introduced in Falcon Media Server 0.8.8 (June 2026). The pipeline coexists with the existing watchfolder, chunked socket upload, and graphics asset upload (POST /graphics/assets/upload) endpoints.