Files
File interface: S3 and SFTP drops
The zero-integration way in: publish your catalog, rate cards, and avails as file drops, and return delivery posts through the same channel. A drop is a set of files landed under your prefix, over S3 or SFTP, registered by a manifest. Accepted formats per type: csv, parquet, xlsx, json.
s3://vamos-ingest/{seller_id}/{drop_id}/
catalog.csv
rates.csv
avails.csv
manifest.json
Ingestion is asynchronous; a clean drop publishes in minutes. The result lands back in the drop as success.json, or as errors.json naming every rejected row and its code:
{
"drop_id": "drop_2026-W40-001",
"status": "partial",
"rows_written": 840,
"errors": [
{
"file": "avails.csv",
"row": 147,
"error_code": "UNKNOWN_PACKAGE_ID",
"value": "acme-fringe-old"
}
]
}
catalog.integrity_failed event with the conflict report attached.Register a file drop
POST/v1/me/files/manifestcatalog:write
Register the manifest for a landed drop; ingestion starts on registration.
Request body
| Field | Type | Description |
|---|---|---|
schema_version required | string | Manifest schema version. Current version: 1.0. |
drop_id required | string | The drop the files landed under. |
effective | object | Coverage window the drop asserts: {start, end}, ISO dates. |
files[] required | array of object | One entry per landed file. |
files[].filename required | string | File name inside the drop. |
files[].type required | enum | What the file carries: catalog, rate_cards, avails, or delivery_posts. |
files[].format | enum | csv, parquet, xlsx, or json. Default: inferred from the file extension. |
files[].sha256 required | string | Checksum of the landed file; the file's identity for idempotency. |
A manifest for a full catalog drop:
{
"schema_version": "1.0",
"drop_id": "drop_2026-W41-001",
"effective": {
"start": "2026-10-05",
"end": "2026-12-27"
},
"files": [
{
"filename": "catalog.csv",
"type": "catalog",
"format": "csv",
"sha256": "9f86d081..."
},
{
"filename": "rates.csv",
"type": "rate_cards",
"format": "csv",
"sha256": "b94f6f12..."
},
{
"filename": "avails.csv",
"type": "avails",
"format": "csv",
"sha256": "3a7bd29c..."
}
]
}
sha256 values all match an already-processed drop replays the original result instead of reprocessing.Response
Registration is accepted and processing starts; results land in the drop as success.json or errors.json.
202 Response
{
"drop_id": "drop_2026-W41-001",
"status": "processing",
"results_uri": "s3://vamos-ingest/acme/drop_2026-W41-001/"
}
Errors
| Status | Code | When |
|---|---|---|
| 404 | NOT_FOUND | drop_id does not match a landed drop under your prefix. |
| 422 | INVALID_INPUT | A sha256 does not match the landed file, or a manifest entry names a file missing from the drop. |
Upload a file (buyer)
POST/v1/uploadsorders:write
Where a buyer's file comes from. Register the file, PUT the bytes, then reference the upload_id: on a document order's document.upload_id, or as an attachment alongside a structured order. The seller-side drop manifest above is a different thing entirely: that is a seller publishing its catalog, this is a buyer handing over one document.
Request body
| Field | Type | Description |
|---|---|---|
filename required | string | The name as you hold it. Recorded, never interpreted: the format check reads the bytes, not the extension. |
format required | enum | pdf, xlsx, or csv. A declaration, verified against the bytes when they arrive: pdf and xlsx by signature, csv structurally (see the callout under Send the bytes). |
size_bytes required | integer | The size you are about to send. Over the platform maximum, the mint is refused before you transfer anything. |
Response
201 Response
{
"upload_id": "upl_4b2c91d0e7a3",
"buyer_entity": "yourco",
"filename": "q4-buysheet.pdf",
"format": "pdf",
"size_bytes": 48211,
"status": "pending",
"sha256": null,
"expires_at": "2026-10-06T00:00:00Z",
"created_at": "2026-10-05T00:00:00Z",
"upload_url": "/v1/uploads/upl_4b2c91d0e7a3/content"
}
Errors
| Status | Code | When |
|---|---|---|
| 422 | INVALID_INPUT | An unsupported format, a missing filename, or a size_bytes over the platform maximum. details.field names which one. Nothing is minted. |
Send the bytes
PUT/v1/uploads/{id}/contentorders:write
PUT the raw bytes to the upload_url from the mint. The platform hashes what arrives and checks it against the format you declared.
200 Response
{
"upload_id": "upl_4b2c91d0e7a3",
"status": "uploaded",
"sha256": "9f2c...",
"size_bytes": 48211
}
What the csv check accepts. CSV has no signature, so the check is structural: every non-empty row carries the same number of fields as the header, quoted commas are respected, and a leading {, [, or < is refused because JSON and XML are what gets sent by mistake. Single-column files are valid and accepted. Ragged rows, binary, and anything that is not valid UTF-8 are refused.
Exactly one PUT. Once a hash exists the record is read-only and a second PUT returns 409 CONFLICT with the stored hash untouched. That is deliberate: an attachment whose bytes could be replaced after its hash was checked is not evidence of anything. If you sent the wrong file, mint a new upload; if it is already on an order, revise the order.
Errors
| Status | Code | When |
|---|---|---|
| 404 | NOT_FOUND | No upload with that ID under your credentials. An upload belonging to a different acting buyer reads as 404 rather than 403, so an ID never confirms whose it is. |
| 409 | CONFLICT | The upload is not pending: it already has bytes, was rejected, or expired. details.status says which. |
| 422 | INVALID_INPUT | The bytes contradict the declared format. The record becomes rejected and can never be attached; details.detected_format says what arrived, or null when nothing recognisable did. |
Read an upload
GET/v1/uploads/{id}orders:write
The current state of one upload: its status, and its sha256 once bytes have arrived. Buyer-only. A seller never reads an upload directly; it reads a document or attachment through the order it belongs to, so an upload you never reference is invisible to every seller.
| Field | Type | Description |
|---|---|---|
status | enum | pending (awaiting bytes), uploaded (bytes and hash recorded, ready to reference), rejected (contradicted its declared format), expired. No status ever returns to pending. |
sha256 | string | Of the bytes as received. null until they arrive. Check it against your own copy. |
expires_at | ISO datetime | When an unreferenced upload lapses. See the callout below. |
Expiry bounds an upload no order references, and an upload on an order is pinned. An upload that never received bytes, and one that received them and was never used, both lapse to expired past expires_at and can no longer be referenced. The moment an order references it, the expiry stops applying: an order that reaches placed carrying a buysheet whose bytes later vanished would be worse than one that never carried it, so an attached file stays resolvable for the life of its order, terminal states included. There is no delete operation; to change what an order carries, revise the order.
EDI (agency and traffic systems)
EDI is the TV industry's electronic paper trail, and it runs in both directions: station traffic and billing systems generate it (invoices, as-run detail), and agency systems of record (Prisma, Strata) consume it. VAMOS plugs into both ends of that flow.
Out, for buyers. If your billing lives in an agency system of record, VAMOS delivers confirmed orders over the established interchange (structured XML via the industry's email transport), so order and billing records land in the same place your current orders land. Include your estimate_id in buyer_metadata and it is echoed on the delivery, so your billing system can match the record.
In, for sellers. Your traffic system already produces EDI invoices and as-run log times. Point those feeds at VAMOS like any other file drop and they attach to orders as reconciliation inputs: air dates, times, and copy against what was booked, in the paper your plant already prints.