ION API Docs
Partner package · v1.0.0

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.

Audience Partners integrating upload & virtualization Scope Health · Upload · Virtualize

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
Out of scope Search, catalog management, playback UI, and other product APIs are not covered. This document covers health, upload, and virtualization only.

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-data and JSON
  • Ability to store the source video UUID between pipeline steps
  • A JWT from ION for all GraphQL calls

Quick connectivity check

bash
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

Order matters Skipping or reordering steps will fail. Do not call 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

POST /uploads/upload-video

Source video UUID (file.id)

mutation reencodeMp4(uuid)

Fragmented MP4 suitable for slicing

mutation sliceFmp4(uuid)

Init segment + media fragments

query createDrefMp4(uuid)

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:

json
{ "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):

json
{
  "message": "Video uploaded successfully",
  "file": {
    "id": "018f3a2b-7c4e-7a1d-8f2b-1a2b3c4d5e6f",
    "original_name": "clip.mp4",
    "mime_type": "video/mp4",
    "size": 1234567,
    "duration": 10.5
  }
}
Persist this Save file.id. That UUID is required for all later steps.
Status Body
400 { "error": "No file uploaded" }
500 { "error": "<message>" }
bash
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.

HTTP 200 ≠ operation success GraphQL transport can succeed while the operation fails. Always inspect 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 === false and error set
  • Prerequisite: Upload completed for that UUID
graphql
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: reencodeMp4 succeeded for the same uuid
graphql
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: reencodeMp4 and sliceFmp4 succeeded for the same uuid
graphql
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.

graphql
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:

json
{
  "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_id must exist and should already be virtualized
  • startTime may be adjusted to a prior keyframe for reliable decode; check warnings
  • On thrown errors, expect top-level GraphQL errors (e.g. missing source videos)
  • On operation-level failure, inspect data.createTimedMp4.success where 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

  1. If HTTP status is 401/403 → refresh or request a new JWT
  2. If REST status is not 2xx → abort; surface error
  3. If GraphQL body has errors → abort; surface errors[].message
  4. If data.<op>.success === false → abort; surface error; do not advance to the next step

8. End-to-end example

bash
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

graphql
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).