> ## Documentation Index
> Fetch the complete documentation index at: https://www.getsoundlink.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Import video(s) from public HTTPS MP4 URL(s)

> Accepts a single `url` or a `videos` array (1–5 items). Creates one upload
session, enqueues async download/staging, and returns `202` with `sessionId`.

Requires the `videos:write` scope. Subject to a stricter per-credential rate
limit than generic public API routes (default 20 imports per hour per API key
or OAuth grant). This caps how many import requests are accepted per hour,
not how long ingestion takes.

Poll `GET /v1/videos/import/{sessionId}` until `status` is `ready`, `partial`,
or `failed`. For single-URL imports, use `videoId` when ready. For batches
(`count > 1`), use `videoIds`.




## OpenAPI

````yaml /openapi/soundlink-public-api-v1.yaml post /v1/videos/import
openapi: 3.0.3
info:
  title: Soundlink Public API
  version: 0.0.1-draft
  description: >
    Campaign listing, metrics, and write operations for Soundlink partner
    organizations.


    **Base URL:** `https://api.getsoundlink.com`


    ## Authentication


    Send `Authorization: Bearer` with one of:


    1. **API key (recommended)** — a `sk_*` key from the Soundlink app
       (Settings → Developer → API keys):
       ```
       Authorization: Bearer sk_abc123_<token>
       ```

    2. **OAuth Bearer** — an access token from Sign in with Soundlink
       (authorization code or client credentials). Token scopes use the
       same strings as Public API key scopes (for example `campaigns:read`,
       `metrics:read`):
       ```
       Authorization: Bearer <oauth_access_token>
       ```

    `x-api-key` is no longer accepted and is ignored if sent. Requests

    without `Authorization: Bearer` return `401 invalid_api_key` (or

    `401 invalid_token` when a Bearer value is present but neither a

    recognized `sk_*` key nor a valid OAuth access token).


    ### Which operations accept OAuth?


    | Operations | Required scope | Auth |

    | --- | --- | --- |

    | `GET /v1/ping` | any granted Public API scope | API key (Bearer) or OAuth
    Bearer |

    | Campaign reads (`GET /v1/strategies`, `GET /v1/campaigns`, campaign
    detail, tiers) | `campaigns:read` | API key (Bearer) or OAuth Bearer |

    | Metrics reads (overview, breakdown, exports) | `metrics:read` | API key
    (Bearer) or OAuth Bearer |

    | Campaign writes (create, stop, budget, tier updates) | `campaigns:write` |
    API key (Bearer) or OAuth Bearer |

    | Video import / import status | `videos:write` | API key (Bearer) or OAuth
    Bearer |

    | Soundlink reads (list, detail, metrics overview/breakdown/engagement,
    JSONL exports) | `soundlinks:read` | API key (Bearer) or OAuth Bearer |

    | Soundlink writes (create, archive) | `soundlinks:write` | API key (Bearer)
    or OAuth Bearer |


    Per-operation scope requirements are also marked with `x-required-scope`

    (except `GET /v1/ping`, which accepts any valid credential with at least

    one Public API scope granted to the key/token).


    All responses are wrapped in a standard envelope:

    ```json

    { "data": <payload>, "meta": { "requestId": "<uuid>" } }

    ```


    Errors follow the same envelope shape with an `error` field instead of
    `data`.
servers:
  - url: https://api.getsoundlink.com
    description: Production
security:
  - apiKeyBearerAuth: []
  - oauthBearer: []
tags:
  - name: System
    description: Connectivity and auth verification
  - name: Campaigns
    description: >-
      Campaign listing, detail, creation, and wallet management (budget, stop,
      tiers)
  - name: Metrics
    description: Campaign performance metrics
  - name: Videos
    description: Partner video library import for Full Control campaigns
  - name: Soundlinks
    description: Soundlink listing, detail, creation, archive, and performance metrics
paths:
  /v1/videos/import:
    post:
      tags:
        - Videos
      summary: Import video(s) from public HTTPS MP4 URL(s)
      description: >
        Accepts a single `url` or a `videos` array (1–5 items). Creates one
        upload

        session, enqueues async download/staging, and returns `202` with
        `sessionId`.


        Requires the `videos:write` scope. Subject to a stricter per-credential
        rate

        limit than generic public API routes (default 20 imports per hour per
        API key

        or OAuth grant). This caps how many import requests are accepted per
        hour,

        not how long ingestion takes.


        Poll `GET /v1/videos/import/{sessionId}` until `status` is `ready`,
        `partial`,

        or `failed`. For single-URL imports, use `videoId` when ready. For
        batches

        (`count > 1`), use `videoIds`.
      operationId: importVideo
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/VideoImportRequest'
            examples:
              single:
                summary: Single URL (legacy)
                value:
                  url: https://cdn.example.com/promo.mp4
              batch:
                summary: Batch of URLs
                value:
                  videos:
                    - url: https://cdn.example.com/a.mp4
                    - url: https://cdn.example.com/b.mp4
      responses:
        '200':
          description: >-
            Duplicate video already in the organization's library (sync legacy
            path only)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/VideoImportStartResponse'
        '202':
          description: Import accepted; ingestion in progress
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/VideoImportStartResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/InternalError'
      security:
        - apiKeyBearerAuth: []
        - oauthBearer: []
components:
  schemas:
    VideoImportRequest:
      type: object
      description: >
        Provide either `url` (single) or `videos` (batch of 1–5). Do not send
        both.
      properties:
        url:
          type: string
          format: uri
          description: >-
            Public HTTPS URL to an MP4 file (max 70 MB). Mutually exclusive with
            `videos`.
        videos:
          type: array
          minItems: 1
          maxItems: 5
          description: Batch of video URLs. Mutually exclusive with `url`.
          items:
            type: object
            required:
              - url
            properties:
              url:
                type: string
                format: uri
                description: Public HTTPS URL to an MP4 file (max 70 MB).
    VideoImportStartResponse:
      type: object
      required:
        - data
        - meta
      properties:
        data:
          $ref: '#/components/schemas/VideoImportStartData'
        meta:
          $ref: '#/components/schemas/Meta'
    VideoImportStartData:
      type: object
      required:
        - sessionId
        - status
      properties:
        sessionId:
          type: string
          format: uuid
          description: Upload session ID for status polling
        status:
          $ref: '#/components/schemas/VideoImportStatus'
        count:
          type: integer
          description: Number of videos accepted for import
        videoId:
          type: string
          format: uuid
          nullable: true
          description: Present when `status` is `ready` (new or duplicate video)
    Meta:
      type: object
      required:
        - requestId
      properties:
        requestId:
          type: string
          format: uuid
          description: Server-generated request ID. Include when contacting support.
    ErrorResponse:
      type: object
      required:
        - error
        - meta
      properties:
        error:
          $ref: '#/components/schemas/Error'
        meta:
          $ref: '#/components/schemas/Meta'
    VideoImportStatus:
      type: string
      enum:
        - processing
        - ready
        - failed
        - partial
    Error:
      type: object
      required:
        - code
        - message
      properties:
        code:
          type: string
          description: Stable snake_case error code.
          enum:
            - invalid_api_key
            - api_key_revoked
            - api_key_expired
            - insufficient_scope
            - not_found
            - campaign_not_found
            - invalid_query_parameter
            - invalid_date_range
            - page_size_exceeded
            - invalid_request
            - invalid_genre
            - invalid_duration_or_budget
            - invalid_daily_budget
            - invalid_duration_days
            - invalid_spotify_url
            - invalid_tier_budget_allocation
            - invalid_tier_status
            - tier_update_cooldown
            - wallet_not_enabled
            - insufficient_credit
            - idempotency_key_conflict
            - rate_limit_exceeded
            - invalid_video_url
            - video_import_limit_exceeded
            - video_import_session_not_found
            - internal_error
        message:
          type: string
          description: Human-readable error description. Do not parse programmatically.
        details:
          type: object
          nullable: true
          description: |
            Additive, error-code-specific context (e.g. `available`/`required`
            on `insufficient_credit`). Absent or `null` for error codes that
            carry no extra data.
          additionalProperties: true
  responses:
    BadRequest:
      description: Invalid query parameter or date range
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          examples:
            invalid_query_parameter:
              value:
                error:
                  code: invalid_query_parameter
                  message: pageSize must be an integer between 1 and 500.
                meta:
                  requestId: 550e8400-e29b-41d4-a716-446655440000
            invalid_date_range:
              value:
                error:
                  code: invalid_date_range
                  message: endDate must not be before startDate.
                meta:
                  requestId: 550e8400-e29b-41d4-a716-446655440000
    Unauthorized:
      description: |
        Missing or invalid credentials. Covers missing/malformed API keys,
        invalid or expired OAuth access tokens, and revoked API keys.
        `x-api-key` is not accepted and is ignored if sent.
      headers:
        Retry-After:
          schema:
            type: integer
          description: Present only on `rate_limit_exceeded`
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          examples:
            invalid_api_key:
              value:
                error:
                  code: invalid_api_key
                  message: 'Missing credentials. Send Authorization: Bearer.'
                meta:
                  requestId: 550e8400-e29b-41d4-a716-446655440000
            invalid_token:
              value:
                error:
                  code: invalid_token
                  message: OAuth access token is invalid or expired.
                meta:
                  requestId: 550e8400-e29b-41d4-a716-446655440000
            api_key_revoked:
              value:
                error:
                  code: api_key_revoked
                  message: >-
                    This API key has been revoked. Create a new key in Settings
                    → Developer → API keys.
                meta:
                  requestId: 550e8400-e29b-41d4-a716-446655440000
    Forbidden:
      description: |
        Authenticated but not allowed: missing required scope, or OAuth access
        disabled for the organization (`access_denied`).
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          examples:
            insufficient_scope:
              value:
                error:
                  code: insufficient_scope
                  message: This token does not have the metrics:read scope.
                meta:
                  requestId: 550e8400-e29b-41d4-a716-446655440000
            access_denied:
              value:
                error:
                  code: access_denied
                  message: OAuth access is not enabled for this organization.
                meta:
                  requestId: 550e8400-e29b-41d4-a716-446655440000
    RateLimited:
      description: Per-key rate limit exceeded (60/min, 600/hr)
      headers:
        Retry-After:
          required: true
          schema:
            type: integer
          description: Seconds until the rate limit resets
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            error:
              code: rate_limit_exceeded
              message: >-
                Rate limit exceeded. Retry after the number of seconds indicated
                in the Retry-After header.
            meta:
              requestId: 550e8400-e29b-41d4-a716-446655440000
    InternalError:
      description: Unexpected server error
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            error:
              code: internal_error
              message: >-
                An unexpected error occurred. Contact support with the
                requestId.
            meta:
              requestId: 550e8400-e29b-41d4-a716-446655440000
  securitySchemes:
    apiKeyBearerAuth:
      type: http
      scheme: bearer
      description: |
        Pass your Soundlink `sk_*` API key as a Bearer token:
        `Authorization: Bearer sk_<prefix>_<token>` (not an OAuth token; see
        `oauthBearer` for that). Accepted on all Public API operations. Send
        only one `Authorization` header per request.
    oauthBearer:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: |
        OAuth 2.0 access token from Sign in with Soundlink
        (`Authorization: Bearer <oauth_access_token>`).
        Token scopes must include the operation's required scope
        (see `x-required-scope` and the Authentication section).
        Accepted on all Public API operations (reads and writes).

````