{
  "openapi": "3.0.3",
  "info": {
    "title": "Trint Upload API - Ingest",
    "description": "The Ingest API enables programmatic media ingestion through a URL-based workflow. Submit a source URL from which Trint retrieves a media file, receive real-time progress updates via webhook callbacks, and track operations through unique operation tokens.",
    "version": "1.0.0"
  },
  "servers": [
    {
      "url": "https://upload.trint.com",
      "description": "Production"
    }
  ],
  "security": [
    {
      "basicAuth": []
    }
  ],
  "paths": {
    "/ingest": {
      "post": {
        "operationId": "submitIngest",
        "summary": "Submit an ingest request",
        "description": "Accepts a source URL and begins asynchronous media ingestion. Returns an `operationToken` that can be used to correlate webhook callbacks and track the operation.\n\n## Workflow\n\n1. **Callback verification** — Trint sends an `ACTIVITY_STARTED` event to the `callbackUrl` to verify the endpoint responds with `200 OK`. If the callback fails, the request is rejected.\n2. **202 Accepted** — The API returns the `operationToken`.\n3. **Asynchronous processing** — Media is downloaded, transcoded, and transcribed. Progress events are sent to the `callbackUrl`.\n\n## Webhook Events\n\nThe following events are sent to the `callbackUrl` as `POST` requests:\n\n| Event Type | Description |\n|---|---|\n| `ACTIVITY_STARTED` | Ingest operation has begun |\n| `MEDIA_TRANSFER_COMPLETE` | Media successfully retrieved and stored |\n| `MEDIA_TRANSFER_FAILED` | Media retrieval failed |\n| `TRANSCRIPT_COMPLETE` | Transcription completed successfully |\n| `TRANSCRIPT_FAILED` | Transcription processing failed |\n\nEach callback payload includes `operationToken`, `eventType`, and the `metadata` you provided in the original request.",
        "tags": ["Ingest"],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/IngestRequest"
              },
              "examples": {
                "minimal": {
                  "summary": "Minimal request",
                  "value": {
                    "sourceUrl": "https://example.com/media/interview.mp4",
                    "callbackUrl": "https://example.com/webhooks/trint"
                  }
                },
                "withMetadata": {
                  "summary": "With metadata and language",
                  "value": {
                    "sourceUrl": "https://example.com/media/interview.mp4",
                    "filename": "Interview-2024.mp4",
                    "callbackUrl": "https://example.com/webhooks/trint",
                    "metadata": "internal-ref-12345",
                    "language": "en-US"
                  }
                },
                "withFolder": {
                  "summary": "Upload to a specific folder in a workspace",
                  "value": {
                    "sourceUrl": "https://example.com/media/meeting.wav",
                    "filename": "Team-Meeting.wav",
                    "callbackUrl": "https://example.com/webhooks/trint",
                    "language": "en-GB",
                    "folderId": "abc123",
                    "sharedDriveId": "drive456"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Accepted — the ingest operation has been queued for processing.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IngestResponse"
                },
                "example": {
                  "operationToken": "550e8400-e29b-41d4-a716-446655440000"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request — invalid or missing parameters.",
            "content": {
              "text/plain": {
                "schema": {
                  "type": "string"
                },
                "examples": {
                  "missingSourceUrl": {
                    "summary": "Missing sourceUrl",
                    "value": "Missing required field: sourceUrl is required"
                  },
                  "invalidCallbackUrl": {
                    "summary": "Invalid callback URL",
                    "value": "Invalid callbackUrl: must be a valid URL"
                  },
                  "callbackTimeout": {
                    "summary": "Callback URL timed out",
                    "value": "Invalid callbackUrl: request timed out"
                  },
                  "callbackNonOk": {
                    "summary": "Callback URL returned non-200",
                    "value": "Invalid callbackUrl: endpoint did not return OK status"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized — missing or invalid credentials.",
            "content": {
              "text/plain": {
                "schema": {
                  "type": "string"
                },
                "example": "Authorization required"
              }
            }
          },
          "403": {
            "description": "Forbidden — credentials are valid but not permitted. Only API Key v2 and Bearer tokens are accepted.",
            "content": {
              "text/plain": {
                "schema": {
                  "type": "string"
                },
                "example": "invalid credentials"
              }
            }
          },
          "429": {
            "description": "Too Many Requests — rate limit exceeded.",
            "content": {
              "text/plain": {
                "schema": {
                  "type": "string"
                },
                "example": "rate limiting exceeded, please try again later"
              }
            }
          },
          "503": {
            "description": "Service Unavailable — the ingest service is temporarily disabled.",
            "content": {
              "text/plain": {
                "schema": {
                  "type": "string"
                },
                "example": "Service temporarily unavailable"
              }
            }
          }
        },
        "callbacks": {
          "ingestProgress": {
            "{$request.body#/callbackUrl}": {
              "post": {
                "summary": "Ingest progress notification",
                "description": "Trint POSTs one of these to your `callbackUrl` as the ingest progresses. Your endpoint must respond `200 OK` to the initial `ACTIVITY_STARTED` verification event, otherwise the ingest request is rejected.",
                "operationId": "ingestProgressCallback",
                "requestBody": {
                  "required": true,
                  "content": {
                    "application/json": {
                      "schema": {
                        "$ref": "#/components/schemas/WebhookPayload"
                      }
                    }
                  }
                },
                "responses": {
                  "200": {
                    "description": "Acknowledged. Return this promptly, then process the event asynchronously."
                  }
                }
              }
            }
          }
        }
      },
      "get": {
        "operationId": "getIngestStatus",
        "summary": "Get upload status",
        "description": "Returns a paginated list of uploads with their current status, or an aggregate summary of upload counts per status group.\n\n## Views\n\n- **List view** (default) — Returns individual upload records with pagination.\n- **Summary view** (`?view=summary`) — Returns aggregate counts per status: `PENDING`, `IMPORTING`, `PROCESSING`, `READY`, `FAILED`.\n\n## Rate Limiting\n\nWhen the rate limit is exceeded, the endpoint returns a **cached response** from the most recent successful request with the same query parameters, rather than a `429` error. This ensures clients always receive data during high-frequency polling.",
        "tags": ["Ingest"],
        "parameters": [
          {
            "name": "view",
            "in": "query",
            "description": "Set to `summary` to receive aggregate counts instead of the full list.",
            "required": false,
            "schema": {
              "type": "string",
              "enum": ["summary"]
            }
          },
          {
            "name": "operationToken",
            "in": "query",
            "description": "Filter by a specific operation token.",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "status",
            "in": "query",
            "description": "Comma-separated list of statuses to filter by. Valid values: `PENDING`, `IMPORTING`, `PROCESSING`, `READY`, `FAILED`.",
            "required": false,
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "enum": ["PENDING", "IMPORTING", "PROCESSING", "READY", "FAILED"]
              }
            },
            "example": ["PENDING", "PROCESSING"],
            "style": "form",
            "explode": false
          },
          {
            "name": "userId",
            "in": "query",
            "description": "Filter uploads by a specific user. Only available to admin or workspace owner roles.",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "description": "Number of results per page.",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 50
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "description": "Base64-encoded cursor for fetching the next page. Use the `nextCursor` value from a previous response.",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "createdAfter",
            "in": "query",
            "description": "Return only uploads created after this date (ISO 8601).",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "example": "2024-01-01T00:00:00Z"
          },
          {
            "name": "createdBefore",
            "in": "query",
            "description": "Return only uploads created before this date (ISO 8601).",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "example": "2024-12-31T23:59:59Z"
          }
        ],
        "responses": {
          "200": {
            "description": "Success — returns either a list of uploads or a summary, depending on the `view` parameter.",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/IngestListResponse"
                    },
                    {
                      "$ref": "#/components/schemas/IngestSummaryResponse"
                    }
                  ]
                },
                "examples": {
                  "list": {
                    "summary": "List view (default)",
                    "value": {
                      "uploads": [
                        {
                          "operationToken": "550e8400-e29b-41d4-a716-446655440000",
                          "trintId": "abc123def456",
                          "status": "READY",
                          "filename": "Interview-2024.mp4",
                          "language": "en-US",
                          "createdAt": "2024-06-15T10:30:00.000Z",
                          "updatedAt": "2024-06-15T10:35:00.000Z",
                          "completedAt": "2024-06-15T10:35:00.000Z",
                          "errorCode": null,
                          "errorMessage": null
                        },
                        {
                          "operationToken": "661f9511-f3ac-52e5-b827-557766551111",
                          "status": "PROCESSING",
                          "filename": "Meeting.wav",
                          "language": "en-GB",
                          "createdAt": "2024-06-15T11:00:00.000Z",
                          "updatedAt": "2024-06-15T11:01:00.000Z",
                          "completedAt": null,
                          "errorCode": null,
                          "errorMessage": null
                        }
                      ],
                      "pagination": {
                        "nextCursor": "eyJjcmVhdGVkIjoiMjAyNC0wNi0xNVQxMTowMDowMC4wMDBaIiwiaWQiOiI2NjFmOTUxMSJ9",
                        "hasMore": true
                      }
                    }
                  },
                  "summary": {
                    "summary": "Summary view (?view=summary)",
                    "value": {
                      "summary": {
                        "PENDING": 3,
                        "IMPORTING": 1,
                        "PROCESSING": 5,
                        "READY": 42,
                        "FAILED": 2
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Bad Request — invalid query parameters.",
            "content": {
              "text/plain": {
                "schema": {
                  "type": "string"
                },
                "examples": {
                  "invalidView": {
                    "summary": "Invalid view parameter",
                    "value": "Invalid view parameter. Valid values: summary"
                  },
                  "invalidLimit": {
                    "summary": "Invalid limit",
                    "value": "Invalid limit parameter: must be an integer between 1 and 100"
                  },
                  "invalidStatus": {
                    "summary": "Invalid status filter",
                    "value": "Invalid status values: UNKNOWN. Valid values: PENDING, IMPORTING, PROCESSING, READY, FAILED"
                  },
                  "invalidDate": {
                    "summary": "Invalid date filter",
                    "value": "Invalid createdAfter parameter: must be a valid ISO 8601 date"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized — missing or invalid credentials.",
            "content": {
              "text/plain": {
                "schema": {
                  "type": "string"
                },
                "example": "Authorization required"
              }
            }
          },
          "403": {
            "description": "Forbidden — attempt to filter by another user without admin privileges.",
            "content": {
              "text/plain": {
                "schema": {
                  "type": "string"
                },
                "example": "Cannot filter by other users"
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "basicAuth": {
        "type": "http",
        "scheme": "basic",
        "description": "Base64-encoded API Key credentials in the format `KEY_ID:KEY_SECRET`. Only API Key v2 is supported."
      }
    },
    "schemas": {
      "IngestRequest": {
        "type": "object",
        "required": ["sourceUrl", "callbackUrl"],
        "properties": {
          "sourceUrl": {
            "type": "string",
            "format": "uri",
            "description": "Source URL from which Trint will retrieve the media file."
          },
          "filename": {
            "type": "string",
            "description": "Display name for the file in the Trint webapp. If omitted, an auto-generated UUID is used."
          },
          "callbackUrl": {
            "type": "string",
            "format": "uri",
            "description": "Required. Webhook URL for receiving progress notifications. Must use `http` or `https` protocol. The endpoint must respond with `200 OK` to the initial `ACTIVITY_STARTED` verification event, or the ingest request will be rejected."
          },
          "metadata": {
            "type": "string",
            "maxLength": 1024,
            "description": "Custom metadata string (max 1KB). Returned in all webhook callback payloads."
          },
          "language": {
            "type": "string",
            "default": "en-US",
            "description": "Language code for transcription (e.g., `en-US`, `en-GB`, `fr`, `de`)."
          },
          "folderId": {
            "type": "string",
            "description": "ID of the folder to upload to. If the folder belongs to a workspace, `sharedDriveId` must also be provided."
          },
          "sharedDriveId": {
            "type": "string",
            "description": "ID of the workspace (shared drive) to upload to. Cannot be an archived workspace."
          }
        }
      },
      "IngestResponse": {
        "type": "object",
        "properties": {
          "operationToken": {
            "type": "string",
            "description": "Unique identifier for this ingest operation. Use this to correlate webhook callbacks and for support/debugging."
          }
        },
        "required": ["operationToken"]
      },
      "IngestListResponse": {
        "type": "object",
        "properties": {
          "uploads": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Upload"
            }
          },
          "pagination": {
            "$ref": "#/components/schemas/Pagination"
          }
        },
        "required": ["uploads", "pagination"]
      },
      "IngestSummaryResponse": {
        "type": "object",
        "properties": {
          "summary": {
            "$ref": "#/components/schemas/Summary"
          }
        },
        "required": ["summary"]
      },
      "Upload": {
        "type": "object",
        "properties": {
          "operationToken": {
            "type": "string",
            "description": "Unique identifier for this upload operation."
          },
          "trintId": {
            "type": "string",
            "description": "The Trint transcript ID, available once the media has been successfully imported."
          },
          "status": {
            "type": "string",
            "enum": ["PENDING", "IMPORTING", "PROCESSING", "READY", "FAILED"],
            "description": "Current status of the upload."
          },
          "filename": {
            "type": "string",
            "description": "Display name of the uploaded file."
          },
          "language": {
            "type": "string",
            "description": "Language code used for transcription."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time",
            "description": "Timestamp when the upload was created."
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time",
            "description": "Timestamp when the upload was last updated."
          },
          "completedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "Timestamp when the upload completed (either successfully or with failure). Null if still in progress."
          },
          "errorCode": {
            "type": "string",
            "nullable": true,
            "description": "Error code if the upload failed. Null on success or while in progress."
          },
          "errorMessage": {
            "type": "string",
            "nullable": true,
            "description": "Human-readable error message if the upload failed. Null on success or while in progress."
          }
        },
        "required": ["operationToken", "status", "filename", "createdAt", "updatedAt"]
      },
      "Pagination": {
        "type": "object",
        "properties": {
          "nextCursor": {
            "type": "string",
            "nullable": true,
            "description": "Cursor to pass in the `cursor` query parameter to fetch the next page. Null when there are no more results."
          },
          "hasMore": {
            "type": "boolean",
            "description": "Whether more results are available beyond this page."
          }
        },
        "required": ["nextCursor", "hasMore"]
      },
      "Summary": {
        "type": "object",
        "properties": {
          "PENDING": {
            "type": "integer",
            "description": "Number of uploads waiting to be processed."
          },
          "IMPORTING": {
            "type": "integer",
            "description": "Number of uploads currently downloading media."
          },
          "PROCESSING": {
            "type": "integer",
            "description": "Number of uploads currently being transcribed."
          },
          "READY": {
            "type": "integer",
            "description": "Number of uploads that completed successfully."
          },
          "FAILED": {
            "type": "integer",
            "description": "Number of uploads that failed."
          }
        },
        "required": ["PENDING", "IMPORTING", "PROCESSING", "READY", "FAILED"]
      },
      "WebhookPayload": {
        "type": "object",
        "description": "Payload sent to your `callbackUrl` for each event.",
        "properties": {
          "operationToken": {
            "type": "string",
            "description": "The operation token from the original ingest request."
          },
          "eventType": {
            "type": "string",
            "enum": [
              "ACTIVITY_STARTED",
              "MEDIA_TRANSFER_COMPLETE",
              "MEDIA_TRANSFER_FAILED",
              "TRANSCRIPT_COMPLETE",
              "TRANSCRIPT_FAILED"
            ],
            "description": "The type of event."
          },
          "metadata": {
            "type": "string",
            "description": "The custom metadata string provided in the original ingest request."
          }
        },
        "required": ["operationToken", "eventType"]
      }
    }
  }
}
