basePath: / definitions: api.CategoriesResponse: properties: categories: items: $ref: '#/definitions/api.Category' type: array type: object api.Category: properties: id: example: 1 type: integer name: example: documentary type: string type: object api.CompleteRequest: properties: categoryIds: example: - 1 - 2 items: type: integer type: array description: example: A behind-the-scenes look at the evening bulletin. type: string fileName: example: interview-cut.mov type: string key: example: videos/a1b2c3d4e5f6.mov type: string tags: example: media, press, riyadh type: string title: example: Inside the Newsroom type: string type: object api.CompleteResponse: properties: categoryIds: example: - 1 - 2 items: type: integer type: array createdAt: type: string description: example: A behind-the-scenes look at the evening bulletin. type: string fileName: example: interview-cut.mov type: string id: example: 0199f3a1-7c2e-7b21-9f0d-1a2b3c4d5e6f type: string mediaConvertJobId: example: 1755300000000-abcdef type: string sizeBytes: example: 60 type: integer status: enum: - processing - ready - failed example: processing type: string storageKey: example: videos/a1b2c3d4e5f6.mov type: string tags: example: media, press, riyadh type: string title: example: Inside the Newsroom type: string updatedAt: type: string type: object api.HealthResponse: properties: status: example: ok type: string type: object api.PresignRequest: properties: contentType: enum: - video/mp4 - video/quicktime example: video/quicktime type: string fileName: example: interview-cut.mov type: string type: object api.PresignResponse: properties: key: example: videos/a1b2c3d4e5f6.mov type: string uploadUrl: example: https://raw-uploads-bucket.s3.amazonaws.com/videos/a1b2....mov?X-Amz-Signature=... type: string type: object api.ProblemDetails: properties: detail: example: The 'title' field was absent or contained only whitespace. Every video needs a non-empty title. type: string status: example: 422 type: integer title: example: Title is required. type: string type: example: about:blank type: string type: object api.VideoResponse: properties: categoryIds: example: - 1 - 2 items: type: integer type: array createdAt: type: string description: example: A behind-the-scenes look at the evening bulletin. type: string fileName: example: interview-cut.mov type: string id: example: 0199f3a1-7c2e-7b21-9f0d-1a2b3c4d5e6f type: string mediaConvertJobId: example: 1755300000000-abcdef type: string sizeBytes: example: 60 type: integer status: enum: - processing - ready - failed example: processing type: string storageKey: example: videos/a1b2c3d4e5f6.mov type: string tags: example: media, press, riyadh type: string title: example: Inside the Newsroom type: string updatedAt: type: string type: object info: contact: {} description: 'JSON API for ingesting videos into the Thamanyah catalogue. Uploads are two-step: presign, PUT the file straight to S3, then register the video to queue transcoding.' title: Thamanyah CMS API version: "1.0" paths: /api/categories: get: description: Returns the fixed lookup set of categories a video can belong to, each with the id used to reference it. The `id` values from this list are the only ones accepted in the `categoryIds` field of POST /api/videos. produces: - application/json responses: "200": description: OK schema: $ref: '#/definitions/api.CategoriesResponse' "500": description: Categories could not be read from the database schema: $ref: '#/definitions/api.ProblemDetails' summary: List video categories tags: - categories /api/videos: post: consumes: - application/json description: Step 2 of the upload flow. Takes the storage key returned by POST /api/videos/presign — after the file has been PUT to the presigned URL — submits a MediaConvert transcoding job, and persists the video with its category links. The returned record has status "processing" — one of the states "processing", "ready" or "failed" — though nothing currently moves it on once transcoding finishes. parameters: - description: Video metadata plus the storage key from the presign step. title, key and categoryIds are required; categoryIds must hold at least one id from GET /api/categories. in: body name: request required: true schema: $ref: '#/definitions/api.CompleteRequest' produces: - application/json responses: "201": description: Created schema: $ref: '#/definitions/api.CompleteResponse' "400": description: Request body was not valid JSON schema: $ref: '#/definitions/api.ProblemDetails' "404": description: categoryIds referenced a category that does not exist schema: $ref: '#/definitions/api.ProblemDetails' "409": description: key has already been registered by an earlier request schema: $ref: '#/definitions/api.ProblemDetails' "422": description: title was empty, key was missing or not a key issued by this API, or categoryIds was empty schema: $ref: '#/definitions/api.ProblemDetails' "500": description: Categories could not be read, the key could not be checked, the transcoding job could not be queued, or the video record could not be saved schema: $ref: '#/definitions/api.ProblemDetails' summary: Register an uploaded video tags: - videos /api/videos/{id}: get: description: 'Returns the catalogue record for one video, including the status its transcoding job has reached. This is how a client follows an upload after POST /api/videos: the record starts "processing" and moves to "ready" or "failed" once MediaConvert reports the job finished.' parameters: - description: The video's id, as returned by POST /api/videos in: path name: id required: true type: string produces: - application/json responses: "200": description: OK schema: $ref: '#/definitions/api.VideoResponse' "404": description: No video has that id schema: $ref: '#/definitions/api.ProblemDetails' "500": description: The video could not be read from the database schema: $ref: '#/definitions/api.ProblemDetails' summary: Read a video tags: - videos /api/videos/presign: post: consumes: - application/json description: Step 1 of the upload flow. Returns a short-lived presigned S3 URL that the client PUTs the video file to directly, plus the storage key identifying it. The submitted contentType is signed into that URL, so the PUT must carry an identical Content-Type header or S3 rejects it as a signature mismatch. Once the PUT succeeds, pass the key to POST /api/videos to register the video and start transcoding. The file itself never passes through this API. parameters: - description: Name and media type of the file to be uploaded. contentType must be exactly 'video/mp4' or 'video/quicktime'. in: body name: request required: true schema: $ref: '#/definitions/api.PresignRequest' produces: - application/json responses: "200": description: OK schema: $ref: '#/definitions/api.PresignResponse' "400": description: Request body was not valid JSON schema: $ref: '#/definitions/api.ProblemDetails' "422": description: contentType was not 'video/mp4' or 'video/quicktime' schema: $ref: '#/definitions/api.ProblemDetails' "500": description: Upload URL could not be issued schema: $ref: '#/definitions/api.ProblemDetails' summary: Create a presigned upload URL tags: - videos /health: get: description: Reports that the service is up and serving. Used as the ALB target group health check; it does not verify the database or S3 connections. produces: - application/json responses: "200": description: OK schema: $ref: '#/definitions/api.HealthResponse' summary: Health check tags: - system swagger: "2.0"