Falcon Media Server External Metadata API Falcon Media Server
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.
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.
| Path | Where | Best for |
|---|---|---|
| Sidecar file | Dropped next to the media file in the watchfolder. | MAM hand-off, file-based delivery, asynchronous workflows. |
| HTTP upload | POST /api/files/upload ? multipart upload plus optional metadata field. | Direct push of file and metadata in a single request. |
| HTTP metadata endpoints | GET/PUT/PATCH/POST on /api/files/:type/:filename/metadata. | Read, replace or enrich metadata after the file has been ingested. |
| Socket.IO event | setFileMetadata 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" }
}
}
| Field | Required | Description |
|---|---|---|
fileName | yes | Basename only. Matches the entry the tag system uses for the file. |
fileType | no | video or graphics. Inferred from extension if omitted. |
externalId | no | Stable ID from the source system. Recommended for correlation. |
source | no | Free text identifying the system that produced the metadata. |
tags | no | Array of tag names. Unioned with existing tags and auto-created in knownTags. |
extended | no | Free-form object. Shallow-merged on PATCH, replaced on PUT. |
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.
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
| Status | Meaning |
|---|---|
200 | Successful read or update. |
400 | Invalid request ? missing type, unsupported file type, or unparseable metadata body. |
404 | Target file is not in the catalogue. |
413 | Upload exceeds the 8 GB cap, or sidecar payload exceeds 1 MB. |
500 | Internal error. See the Media Server log. |
Endpoint summary
| Area | Endpoint | Purpose |
|---|---|---|
| Upload | POST /api/files/upload | Upload a file and optionally attach metadata in the same request. |
| Listing | GET /api/files/{type} | List videos or graphics. Supports detail, tag, limit and offset. |
| Listing | GET /api/files/{type}/{filename} | Read the full record for one file (ffprobe fields, tags, externalId, extended). |
| Metadata | GET /api/files/{type}/{filename}/metadata | Read external metadata for one file. |
| Metadata | PUT /api/files/{type}/{filename}/metadata | Replace the extended object. |
| Metadata | PATCH /api/files/{type}/{filename}/metadata | Shallow-merge into the extended object. |
| Metadata | POST /api/files/{type}/{filename}/metadata | Alias for PATCH, for clients that cannot send PATCH. |
| Sidecar | Watchfolder | Drop .json / .xml / .xmp sidecar next to the media file. |
| Socket | setFileMetadata | Push metadata for one file over socket.io. |
| Socket | requestFileMetadata | Read external metadata for one file over socket.io. |
Upload
Upload a file with metadata
Multipart upload of a video or graphics file. Metadata can travel with the file in one of three ways:
- A JSON or XML string in the
metadataform field. - Individual canonical fields delivered as form fields (
externalId,tags). - No metadata ? upload first, attach metadata later via the metadata endpoints.
| Field | Required | Description |
|---|---|---|
file | yes | The binary file. Multer field name must be file. |
type | yes | video or graphics ? picks the destination folder. |
metadata | no | JSON or XML string. Mapped through the mapping engine. |
externalId | no | Alternative to metadata.externalId. |
tags | no | Comma-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
{type} is video or graphics. Returns a paginated list of files for that type.
| Query | Default | Description |
|---|---|---|
detail | summary | summary for a small list-friendly projection, full for the complete cache record. |
tag | none | Comma-separated list of tag names. Results must contain all listed tags. |
limit | 200 | Maximum entries returned. Hard cap of 5000. |
offset | 0 | Number 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
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" } }
}
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
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
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
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)
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
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>
processEntities:false and rejects any payload that declares a DOCTYPE (XXE / billion-laughs defence). Sidecars are capped at 1 MB.Ordering robustness
| Scenario | Behaviour |
|---|---|
| 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
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
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:
- For each tag not already in
knownTags, appends{ name, color: "#4331ed" }and emitssaveKnownTagsto Falcon Play andknownTagsto all Media Manager browsers. - Merges the tags into the file's entry under
videosExtraConfigorgraphicsExtraConfig. Existing user-added tags are preserved ? this is a union, not a replace. - Emits
saveVideosConfigorsaveGraphicsConfigto Falcon Play andvideosExtraConfig/graphicsExtraConfigto all Media Manager browsers.
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:
| Location | Purpose |
|---|---|
<app-dir>/metadata-mappings/*.json | Ships with the app. |
<appData>/FalconMediaServer/metadata-mappings/*.json | Per-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.
| Syntax | Meaning |
|---|---|
a.b.c | Object navigation. |
xs[*] | Iterate array. A scalar is treated as a one-element array. |
xs[0] | Numeric index. |
@id | XML attribute. The underlying parser prefixes attributes with @. |
#text | XML text node when the element also has attributes. |
Mapping detection
A mapping's match block decides whether the mapping applies to an incoming payload:
| Match | Meaning |
|---|---|
{ "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();
});
});
Recommended integration workflow
- Pick the ingestion path that fits the source: sidecar for file-based MAM hand-off, HTTP upload for direct push, HTTP
PATCHfor enrichment, socket.io for live updates. - If the source uses a custom XML schema, drop a mapping file in
metadata-mappings/so the canonical model is produced automatically. - Always include
externalIdwhen available ? it is the only way to correlate a Falcon file back to the source system after a rename. - Send tags through the top-level
tagsarray, never insideextended? only thetagsarray is routed through the Falcon tag system. - Keep custom fields under
extended? anything underextendedis passed through unmodified. - Test with a single file before mass ingestion.
GET /api/files/{type}/{filename}/metadatashows exactly what landed.
Troubleshooting
| Symptom | Likely cause | What 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.