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

# Migrate Media URL

> Convert a legacy Google Cloud Storage media URL to the new Periskope media URL format

This endpoint converts a legacy `storage.googleapis.com` attachment URL to the new Periskope media URL format (`https://api.periskope.app/app/media/...`).

You can also pass an existing Periskope media URL to refresh its access token — useful when a private link has expired and you need a fresh one.

<Note>
  The media file must belong to the organization of the API key used. URLs from other organizations or non-Periskope buckets will return an error.
</Note>

### Headers

<ParamField header="Authorization" type="string" required>
  Bearer token with your API key e.g. `Bearer <api_key>`
</ParamField>

### Body

<ParamField body="url" type="string" required>
  The media URL to migrate. Either a legacy `storage.googleapis.com` attachment URL or an existing Periskope media URL.
</ParamField>

<ParamField body="access" type="string">
  Access mode for the returned URL. One of `private` or `public`.

  * `private` — returns a tokenized link that expires
  * `public` — returns a bare link without a token

  If omitted, follows your organization's [Media Privacy](/features/media-privacy) setting.
</ParamField>

<ParamField body="expires_in_seconds" type="number">
  Validity of the returned link in seconds. Only valid for private links — returns an error if passed for a public link. Clamped between `60` (1 minute) and `604800` (7 days). Defaults to your organization's configured link expiry (24 hours unless changed).
</ParamField>

### Response

<ResponseField name="url" type="string">
  The migrated Periskope media URL. Includes an access token (`?jwt=...`) when access is private.
</ResponseField>

<ResponseField name="access" type="string">
  The access mode applied to the returned URL — `private` or `public`.
</ResponseField>

<ResponseField name="expires_in_seconds" type="number">
  Validity of the returned link in seconds. Only present for private links.
</ResponseField>

<RequestExample>
  ```bash cURL theme={null}
  curl --request POST \
    --url https://api.periskope.app/v1/media/migrate-url \
    --header 'Authorization: Bearer <api_key>' \
    --header 'Content-Type: application/json' \
    --data '{
      "url": "https://storage.googleapis.com/periskope-attachments/2997dd64-89bf-48d3-9a22-b314fca017e5/919876543210/msg_ABCDEF123/file.jpg",
      "access": "private",
      "expires_in_seconds": 86400
    }'
  ```
</RequestExample>

<ResponseExample>
  ```json 200 OK theme={null}
  {
    "url": "https://api.periskope.app/app/media/periskope-attachments/2997dd64-89bf-48d3-9a22-b314fca017e5%2F919876543210%2Fmsg_ABCDEF123%2Ffile.jpg?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
    "access": "private",
    "expires_in_seconds": 86400
  }
  ```

  ```json 400 Error theme={null}
  {
    "error": "URL belongs to a different organization"
  }
  ```
</ResponseExample>
