ION Video API — Partner Integration Guide
Upload a source video and produce virtualized (DREF) assets through a fixed GraphQL pipeline. This guide covers health checks, upload, and virtualization for partner integrations.
1. Overview
ION virtualization turns an uploaded source video into a lightweight virtual MP4.
The player receives metadata (ftyp + moov) immediately and
fetches media data on demand. Partners use this to:
- Upload a source video and receive a UUID
- Virtualize that video through a fixed GraphQL pipeline
- Optionally compose multiple virtualized sources into a timed clip sequence
What you get back
- file.id
- Source video UUID used in every subsequent step
- Virtual DREF MP4
- Metadata-only MP4; media streams at playback time
- assetId
- Timed asset ID for the virtualized or composite result
- mediaServerUrl
- URL the player uses to fetch media data
2. Getting started
| Item | Value |
|---|---|
| Base URL |
Provided by the ION team for your environment
Use the hostname you receive |
| REST | Multipart upload + health checks |
| GraphQL | POST /graphql |
| Auth |
REST health/upload: none. GraphQL: Authorization: Bearer <JWT>
|
| Credentials | JWT issued by the ION team |
Prerequisites
- HTTPS client that can send
multipart/form-dataand JSON - Ability to store the source video UUID between pipeline steps
- A JWT from ION for all GraphQL calls
Quick connectivity check
export BASE_URL="https://<api-base-url>"
export JWT="<provided-jwt>"
curl -s "$BASE_URL/health-check"
curl -s "$BASE_URL/uploads/health-check"
curl -s -X POST "$BASE_URL/graphql" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $JWT" \
-d '{"query":"query { health }"}'
Expected: REST health returns JSON with app_name /
service; GraphQL returns
{ "data": { "health": "ok" } }.
3. Concepts
Source video UUID
Returned as file.id from upload. Pass this as
uuid to reencodeMp4, sliceFmp4, and
createDrefMp4, and as video_id inside composite clips.
Virtualization pipeline
Each source video must complete these steps in order:
Upload → reencodeMp4 → sliceFmp4 → createDrefMp4
createDrefMp4 until re-encode and slice have succeeded for that UUID.
DREF / virtual MP4
createDrefMp4 builds a referenced MP4 (ftyp +
moov only). Media (mdat) is fetched later via
mediaServerUrl. Persist assetId and
mediaServerUrl from a successful response.
Composite (timed) clips
createTimedMp4 stitches one or more already virtualized source videos
into a single timed composite. It is independent of the four-step pipeline, but
every video_id in the clip list must have completed virtualization
first.
4. Authentication
REST (/health-check, /uploads/...)
No authentication.
GraphQL (POST /graphql)
| Header | Value |
|---|---|
Content-Type |
application/json |
Authorization |
Bearer <JWT> |
Auth failures return plain JSON (not a GraphQL envelope):
| Status | Body |
|---|---|
| 403 | { "error": "Forbidden" } |
| 401 | { "error": "Token verification failed" } |
5. Required workflow — single video
Source video UUID (file.id)
Fragmented MP4 suitable for slicing
Init segment + media fragments
Virtual DREF MP4 + assetId
5.1 Health checks
| Method | Path | Purpose |
|---|---|---|
| GET | /health-check |
App liveness |
| GET | /uploads/health-check |
Uploads service liveness |
| GraphQL | query { health } |
GraphQL + JWT check |
REST examples:
{ "app_name": "ion discovery proto" }
{ "service": "uploads" }
5.2 Upload video
POST /uploads/upload-video
201
| Item | Detail |
|---|---|
| Content-Type | multipart/form-data |
| Form field | file (required) |
| Auth | None |
| Success | 201 Created |
Accepted MIME types:
video/mp4, video/quicktime,
video/x-matroska, video/webm,
video/x-msvideo, video/x-ms-wmv,
video/mpeg
Success (abbreviated):
{
"message": "Video uploaded successfully",
"file": {
"id": "018f3a2b-7c4e-7a1d-8f2b-1a2b3c4d5e6f",
"original_name": "clip.mp4",
"mime_type": "video/mp4",
"size": 1234567,
"duration": 10.5
}
}
file.id. That UUID is required for all later steps.
| Status | Body |
|---|---|
| 400 | { "error": "No file uploaded" } |
| 500 | { "error": "<message>" } |
curl -s -X POST "$BASE_URL/uploads/upload-video" \
-F "file=@./clip.mp4"
5.3 GraphQL virtualization
All operations: POST /graphql with Bearer JWT.
Common argument: uuid (String!) — source video UUID from
upload.
data.*.success and top-level errors before advancing.
Step A — reencodeMp4 (mutation)
Converts the source into a fragmented MP4 suitable for slicing.
- Success:
data.reencodeMp4.success === true - Failure: HTTP still 200;
success === falseanderrorset - Prerequisite: Upload completed for that UUID
mutation ReencodeMp4($uuid: String!) {
reencodeMp4(uuid: $uuid) {
success
uuid
message
error
}
}
Step B — sliceFmp4 (mutation)
Splits the fragmented MP4 into an init segment and media fragments.
Operation name is sliceFmp4 (not sliceMp4).
- Prerequisite:
reencodeMp4succeeded for the sameuuid
mutation SliceFmp4($uuid: String!) {
sliceFmp4(uuid: $uuid) {
success
uuid
initPath
fragmentsPath
error
}
}
Step C — createDrefMp4 (query)
Builds the virtual DREF MP4 and registers a timed asset. This is a GraphQL query, not a mutation.
| Field | Meaning |
|---|---|
assetId |
Timed video asset ID for this virtualization |
mediaServerUrl / mdatLocation |
URL used to fetch media data at playback |
outputPath |
Server path (internal; do not rely on filesystem access) |
warnings |
Optional advisories; may be null |
- Failure: HTTP 200 with top-level
errors: [{ "message": "…" }] -
Prerequisite:
reencodeMp4andsliceFmp4succeeded for the sameuuid
query CreateDrefMp4($uuid: String!) {
createDrefMp4(uuid: $uuid) {
success
message
uuid
outputPath
mediaServerUrl
mdatLocation
fileSize
instructions
assetId
warnings
}
}
6. Optional — composite timed clips
After one or more source videos are fully virtualized, stitch them with
createTimedMp4.
mutation CreateTimedMp4($clips: [VideoClipInput!]!) {
createTimedMp4(clips: $clips) {
success
message
uuid
outputPath
mediaServerUrl
mdatLocation
fileSize
instructions
assetId
warnings
}
}
Clip input
| Field | Type | Required | Notes |
|---|---|---|---|
video_id |
UUID | Yes | Source video UUID (file.id) after virtualization |
startTime |
Float (seconds) | No | Default 0. May be snapped to the previous keyframe |
endTime |
Float (seconds) | No | Default: source duration. Values past duration are clamped |
Clips play in array order. The same video_id may appear more than once
with different ranges.
Example variables:
{
"clips": [
{ "video_id": "018f3a2b-7c4e-7a1d-8f2b-1a2b3c4d5e6f" },
{
"video_id": "0190abcd-1234-5678-9abc-def012345678",
"startTime": 10,
"endTime": 60
},
{
"video_id": "018f3a2b-7c4e-7a1d-8f2b-1a2b3c4d5e6f",
"startTime": 5,
"endTime": 10
}
]
}
Notes for integrators
- Every
video_idmust exist and should already be virtualized -
startTimemay be adjusted to a prior keyframe for reliable decode; checkwarnings -
On thrown errors, expect top-level GraphQL
errors(e.g. missing source videos) -
On operation-level failure, inspect
data.createTimedMp4.successwhere applicable
7. Error handling
| Layer | How to detect failure |
|---|---|
| REST upload | Non-2xx status; body { "error": "…" } |
| GraphQL auth | HTTP 401 / 403 with { "error": "…" } |
reencodeMp4 / sliceFmp4 |
HTTP 200, but data.*.success === false → read
error
|
createDrefMp4 / createTimedMp4 |
HTTP 200 with top-level errors, and/or
success === false
|
Always treat GraphQL HTTP 200 as transport success only. Inspect
success and errors in the body before continuing the
pipeline.
Recommended client logic
- If HTTP status is 401/403 → refresh or request a new JWT
- If REST status is not 2xx → abort; surface
error -
If GraphQL body has
errors→ abort; surfaceerrors[].message -
If
data.<op>.success === false→ abort; surfaceerror; do not advance to the next step
8. End-to-end example
BASE_URL="https://<api-base-url>"
JWT="<provided-jwt>"
# 0. Health
curl -s "$BASE_URL/health-check"
curl -s "$BASE_URL/uploads/health-check"
# 1. Upload
UPLOAD=$(curl -s -X POST "$BASE_URL/uploads/upload-video" -F "file=@./clip.mp4")
UUID=$(echo "$UPLOAD" | jq -r '.file.id')
echo "UUID=$UUID"
# 2. Re-encode
curl -s -X POST "$BASE_URL/graphql" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $JWT" \
-d "{\"query\":\"mutation(\$u:String!){reencodeMp4(uuid:\$u){success error}}\",\"variables\":{\"u\":\"$UUID\"}}"
# 3. Slice
curl -s -X POST "$BASE_URL/graphql" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $JWT" \
-d "{\"query\":\"mutation(\$u:String!){sliceFmp4(uuid:\$u){success error initPath fragmentsPath}}\",\"variables\":{\"u\":\"$UUID\"}}"
# 4. Create DREF virtual MP4 (query)
curl -s -X POST "$BASE_URL/graphql" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $JWT" \
-d "{\"query\":\"query(\$u:String!){createDrefMp4(uuid:\$u){success assetId mediaServerUrl warnings}}\",\"variables\":{\"u\":\"$UUID\"}}"
# 5. Optional — composite (requires additional virtualized UUIDs)
# curl -s -X POST "$BASE_URL/graphql" \
# -H "Content-Type: application/json" \
# -H "Authorization: Bearer $JWT" \
# -d '{"query":"mutation($clips:[VideoClipInput!]!){createTimedMp4(clips:$clips){success uuid assetId mediaServerUrl warnings}}","variables":{"clips":[{"video_id":"'"$UUID"'"}]}}'
9. Integration checklist
Checks persist in this browser via localStorage.
10. Support
| Topic | Contact |
|---|---|
| Credentials, environments, access | ION Team |
| Spec questions / contract changes | Share this guide version (1.0.0) with the ION team |
Credentials and base URL are issued privately by the ION team for your environment.
Appendix — GraphQL response types
type ReencodeMp4Response {
success: Boolean!
uuid: String!
message: String
error: String
meta: JSON
}
type SliceFmp4Response {
success: Boolean!
uuid: String!
initPath: String
fragmentsPath: String
error: String
}
type CreateDrefMp4Response {
success: Boolean!
message: String!
uuid: String!
outputPath: String!
mediaServerUrl: String!
mdatLocation: String!
fileSize: String!
instructions: String!
assetId: String!
warnings: [String!]
}
input VideoClipInput {
video_id: String!
startTime: Float
endTime: Float
}
createTimedMp4 returns the same shape as
CreateDrefMp4Response (including assetId and
warnings).