# Inputs

> Where a project's media comes from — its input folder, an RSS feed or a Media RSS feed — and the feeds a feed project publishes.

A project's config says where its media comes from, in **`sourceMedia`**:

| `@type` | What it is |
|---|---|
| `S3FolderSource` | Files you put into the project's input folder, each processed once. The default. |
| `RssSource` | An RSS 2.0 feed (a podcast feed, say) the project reads every 30 seconds; each item's media is its `<enclosure>`, processed once. |
| `MrssSource` | A Media RSS feed the project reads every 30 seconds; each item's media is its `<media:content>`, processed once. |

`GET /enums/source-types` lists them. Leave `sourceMedia` out and the project reads its input
folder: the stored config then says `"sourceMedia": {"@type": "S3FolderSource"}`. Choose between
the folder and a feed when you create the project and keep it; moving an existing project from one
to the other is not supported. A feed project may switch between the two kinds of feed.

> **Note:**
> Until build `0.9.0-alpha1-2026-10-05T14-57Z-bf4a93` this was a project field of its own,
> `input` (`{"type": "folder"}`). It is gone: a project resource no longer has it, and a call that
> sends it is refused with `422` and
> `The input field is not supported: where the media comes from is config.sourceMedia.` under
> `errors.input`. See the [Changelog](/vod-express/changelog).

## The input folder

```json
{ "sourceMedia": { "@type": "S3FolderSource" } }
```

The project's `storage.inputPath`, an S3 folder. Put files there [through the API](/vod-express/uploads/api-uploads)
or [with the AWS CLI and the upload role](/vod-express/uploads/aws-role); every file is processed once, and
[Files](/vod-express/concepts/files) describes what happens to it. The upload pages describe this source.

`S3FolderSource` takes no other fields. Its location is the project's own, so a `path` sent with it
is refused.

## Feeds

```json
{
  "name": "Supercanal DR",
  "config": {
    "sourceMedia": { "@type": "MrssSource", "url": "https://nmxmam.nmax.tv/feeds/supercanal-dr-feed" },
    "transcription": { "@type": "Speechmatics", "language": "en" },
    "translations": [
      {
        "language": "es",
        "engine": { "@type": "Gemini" },
        "ttsOptions": { "provider": { "@type": "Deepdub", "voice": "auto" } }
      }
    ],
    "outputs": [
      {
        "@type": "MrssOutput",
        "language": "es",
        "items": [
          {
            "@type": "VideoFile",
            "ext": "mp4",
            "nameTemplate": "${name}.${ext}",
            "dubTracks": [{ "language": "es", "title": "Spanish" }]
          }
        ]
      }
    ]
  }
}
```

A feed project reads your feed every 30 seconds while it is running. For each item it downloads the
item's media into the project's input folder and processes it once, with the project's config, as
it would an uploaded file. The results go wherever `outputs` says: the output folder, a
[re-published feed](#the-output-feed) per language, or both.

Two kinds of feed are read, and the kind says which element of an item holds its media:

| `sourceMedia.@type` | The feed | An item's media |
|---|---|---|
| `RssSource` | [RSS 2.0](https://www.rssboard.org/rss-specification): a podcast feed, or any feed that links a file per item | Its `<enclosure url="…">`, whatever the `type` says |
| `MrssSource` | RSS 2.0 with [Media RSS](https://www.rssboard.org/media-rss) (`xmlns:media="http://search.yahoo.com/mrss/"`): video syndication | Its `<media:content url="…">`: the item's own, else those of its first `<media:group>`; the one marked `isDefault="true"`, else the first |

Each kind reads only its own element. A feed whose items carry `<enclosure>`s read as an
`MrssSource` has nothing to process, and the other way round; the [check](#checking-a-feed) says so
for every item.

- `sourceMedia.url` (string, required): The feed. `https://` only, at most 2048 characters, without a user name or password in it, on a host that resolves to a public address — the same checks as a [webhook URL](/vod-express/guides/webhooks). It is checked when you save the project: | Message under `errors["/config/sourceMedia/url"]` | Cause | |---|---| | `The URL must be an https:// address.` | An `http://` or other URL. | | `The URL must not contain a user name or password.` | `https://user:secret@…`. | | `The URL’s host does not resolve to a public address, so the feed cannot be fetched.` | A host that does not resolve, or resolves to a private network. | A new URL (or the same URL under the other kind of source) is then fetched and read the way the project reads it ([what it needs](#the-feed)), and refused when the project could never process anything from it: | Message under `errors["/config/sourceMedia/url"]` | Cause | |---|---| | `None of the feed’s 25 items has a media file to download (the first: …). …` | Every item lacks the media element the source kind reads: a feed for web pages or search engines rather than for syndication, or a feed of the other kind. | | `The feed answered HTTP 301, a redirect to http://…, which is not followed: a redirect must lead to another HTTPS address.` | A redirect from HTTPS to HTTP. Save an HTTPS address that serves the feed. | | `The feed answered HTTP 404; it must answer 200 with the feed.` | Any answer other than `2xx`. | | `The URL does not answer an RSS feed: …` | Not XML, an Atom feed, or a document with a `DOCTYPE`. | | `The feed could not be fetched: …` | No answer within 15 seconds, more than 5 redirects, or a TLS or connection error. | A feed with no items yet is accepted. Saving a project without changing its URL or its kind does not fetch the feed again.

### The feed

An RSS 2.0 document with one `<item>` per clip. For an `RssSource`, the item's media is its
`<enclosure>`:

```xml
<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>The Fishing Hour</title>
    <item>
      <guid isPermaLink="false">fishing-hour-142</guid>
      <title>Episode 142: Tides</title>
      <enclosure url="https://cdn.example.com/fishing-hour/142.mp3" length="31457280" type="audio/mpeg" />
    </item>
  </channel>
</rss>
```

For an `MrssSource`, it is the item's `<media:content>`:

```xml
<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:media="http://search.yahoo.com/mrss/">
  <channel>
    <title>Supercanal DR</title>
    <item>
      <guid isPermaLink="false">supercanal-dr-2026-10-20-0930</guid>
      <pubDate>Tue, 20 Oct 2026 09:30:00 GMT</pubDate>
      <media:title>Morning news, 20 October</media:title>
      <media:description>The 09:30 bulletin.</media:description>
      <media:content url="https://media.example.com/supercanal/2026-10-20-0930.mp4" type="video/mp4" />
    </item>
  </channel>
</rss>
```

| Element | Meaning |
|---|---|
| `guid` | The item's identity: the file's name is made from it. Without one, the media URL stands in. |
| `enclosure` or `media:content` | Its `url` is a direct link to the media file (an MP4 on your storage), not a player page. |

The feed and every media file are fetched from SyncWords' processing platform, so they must be
reachable from the internet without signing in.

An item is skipped, at every read, when the project cannot download its media:

- it has no `<enclosure>` (an `RssSource`) or no `<media:content>` (an `MrssSource`), or that
  element has no `url` — `<media:thumbnail>` and `<link>` to a player page are not media;
- the media URL's file name has no extension (`…/clip.mp4`, not `…/clip?id=7`).

Redirects are followed, for the feed and for its media, up to 5 of them and never from HTTPS to
HTTP.

### Checking a feed

`GET /projects/{id}/source-feed` reads the project's feed the way the project does, at any time,
and says how many items it has, how many the project downloads, and why each skipped one is
skipped. `POST /source-feeds/check` with `{"url": "…", "sourceType": "RssSource"}` gives the same
answer before there is a project (`sourceType` is `MrssSource` when left out):

```json
{
  "data": {
    "url": "https://feeds.example.com/videos.xml",
    "sourceType": "MrssSource",
    "checkedAt": "2026-10-06T19:10:32.000Z",
    "state": "no_usable_items",
    "problem": "None of the feed’s 25 items has a media file to download (the first: no <media:content>). Each item needs a <media:content url=\"…\"> linking the file itself, such as an .mp4.",
    "httpStatus": 200,
    "items": 25,
    "usableItems": 0,
    "skippedItems": [
      {
        "guid": "https://www.example.com/video/6406369231112",
        "title": "Fisherman rescued after calling for help",
        "link": "https://www.example.com/video/6406369231112",
        "reason": "no <media:content>"
      }
    ]
  }
}
```

A feed of the other kind is told apart in the reason: `no <media:content>; its media is an
<enclosure>, which an RSS source reads`.

### The files it makes

Each item's media becomes one input file, named from a hash of the item's `guid` and a hash of its
media URL, with the media's extension: `4f1c2b9a7d3e.8a2b4c6d.mp4`. From there it is an input file
like any other: [`GET /projects/{id}/files`](/vod-express/concepts/files) lists it with its state and outputs,
a webhook reports it `done` or `failed`, it is [billed](/vod-express/concepts/how-it-works#billing) when done,
and it can be [retried](/vod-express/concepts/files#retry). Output names come from that name, so a
`nameTemplate` of `${name}.${ext}` writes `4f1c2b9a7d3e.8a2b4c6d.mp4`.

- An item the feed keeps listing is processed once.
- An item whose media URL changes gets a new name, since the second hash changes, and is processed
  again, as new content. New bytes at the same URL are not noticed.
- A feed that cannot be read (unreachable, or not a feed) is read again 30 seconds later; nothing
  fails.

> **Warning:**
> Build `0.9.0-alpha1-2026-10-07T11-55Z-b1b8d1` changed the second hash from one of the media URL
> and duration to one of the URL alone. A feed project moved onto it from an earlier build sees
> every item under a new name, and processes them all again.

**A feed project takes no uploads.** The feed is its only source: an upload through the API is
refused with `409` and `code: "project_reads_feed"`:

```json
{
  "message": ["This project reads its media from a feed; files cannot be uploaded to it."],
  "code": "project_reads_feed"
}
```

**A feed project is never stopped for being idle.** With every item processed, it is waiting for
the feed's next one, which only a running project fetches. It keeps running until you call
`POST /projects/{id}/stop`; start it again to resume reading the feed. See
[Auto-stop](/vod-express/concepts/projects#auto-stop).

### The output feed

An `RssOutput` (with an `RssSource`) or an `MrssOutput` (with an `MrssSource`) publishes your feed
again, in one language, with the dubbed files in place of the originals:

```json
{
  "@type": "MrssOutput",
  "language": "es",
  "items": [
    {
      "@type": "VideoFile",
      "ext": "mp4",
      "nameTemplate": "${name}.${ext}",
      "dubTracks": [{ "language": "es", "title": "Spanish" }]
    }
  ]
}
```

- Every item whose media is done links the dubbed file instead of the original: in its
  `<enclosure>` (`RssOutput`), whose `length` becomes the new file's size, or its `<media:content>`
  (`MrssOutput`), whose `fileSize` does; `type` and `medium` follow when the container changed.
- Items not done yet are left out, and appear once they are.
- `<channel><language>` is set to `language`. The standard elements of the channel and of each
  item (`title`, `link`, `description`, `guid`, the media element) are written first, and
  everything else in the feed (dates, images, `itunes:` and other elements) follows as you wrote
  it.
- The feed is written again after every file and every 30 seconds.

It is public, without a token, so players, aggregators and partners can read it:

| What | Call |
|---|---|
| The feed | `GET /feeds/{projectId}/{language}/feed.xml` (`application/xml`) |
| A media file, as the feed links it | `GET /feeds/{projectId}/{language}/media/{name}`: a `302` to a presigned URL, valid for six hours |

```bash
curl -s "$API/feeds/$PROJECT/es/feed.xml"
```

Until the project has read your feed and written its copy, and for a language the project does not
publish, both answer `404` with `code: "feed_not_found"`. Only the feed's own files are served this
way; the project's other outputs still need a token.

| Field | |
|---|---|
| `language` | Required. The transcription language or a translation language; one feed per language. |
| `items` | Required. **Exactly one** `VideoFile` or `AudioFile`: the file each feed item links to. Other items, a `SubtitleFile` for instance, are written beside it and not linked yet. |

A feed output needs the source of its kind: an `RssOutput` an `RssSource`, an `MrssOutput` an
`MrssSource`; in a folder project either is refused. The feed's address and folders are the API's,
so `path`, `mediaPath` and `mediaUrl` are refused if sent.

The output feed's files are also ordinary outputs, under `output/feeds/<language>/`, beside the
feed itself at `output/feeds/<language>.xml`: they appear with those paths in each input's
`outputs` and in `GET /projects/{id}/outputs`. A feed project can also list an `S3FolderOutput` to
write files into the output folder. See [Outputs](/vod-express/configuration/outputs#destinations).
