> For the documentation index, fetch https://sync.so/docs/llms.txt. Append .md to a page URL for Markdown. Documentation-search MCP: https://sync.so/docs/_mcp/server.

# Create Asset

POST https://api.sync.so/v2/assets
Content-Type: application/json

Register a media URL as a reusable asset and get back its id. Use the id in generation inputs (input[].assetId) or as a voice clone sample. To upload a local file, first request a presigned URL via POST /v2/assets/upload, PUT the file there, then register the returned url here. Registration verifies the upload happened and enforces plan limits on the actual file size.

projectId is optional. When provided it must reference a project in your organization — a stale or foreign id is rejected with a 422 and errorCode project_not_found; omit the field if you don't need the asset attached to a project.

Reference: https://sync.so/docs/api-reference/api/assets-api/create

## Authentication

- `x-api-key` header (required) — API Key authentication via header

## Servers

- `https://api.sync.so` (Default, default)
- `https://dev-api.sync.so` (dev)
- `http://localhost:3001` (local)

## Request

### Body (application/json)

This endpoint expects a CreateAssetRequest.

- `url` (string, required) — The public URL of the media to register — e.g. the url returned by POST /v2/assets/upload. Registration verifies the upload happened and enforces plan limits on the actual file size.
- `type` (enum, required) — The type of asset.
  - Allowed values: `AUDIO`, `VIDEO`, `IMAGE`
- `name` (string, optional) — An optional display name for the asset.
- `visibility` (enum, optional, default: USER) — Who can see the asset. Defaults to USER.
  - Allowed values: `USER`, `ORGANIZATION`
- `projectId` (string, optional) — Optionally attach the asset to a project (created via POST /v2/projects) so it appears in Studio under that project's media. Must reference a project in your organization — otherwise the request is rejected with 422.
- `inputType` (enum, optional) — The source of the asset.
  - Allowed values: `UPLOAD`, `URL`, `RECORD`, `TTS`, `EXTRACT`, `PLATFORM_IMPORT`, `DIALOGUE_EDIT`
- `thumbnailUrl` (string, optional, nullable) — Optional thumbnail URL for the asset.
- `size` (long, optional) — File size in bytes.
- `format` (string, optional) — File format or extension.
- `durationSeconds` (double, optional) — Media duration in seconds.
- `width` (integer, optional) — Width in pixels for video or image assets.
- `height` (integer, optional) — Height in pixels for video or image assets.

## Response

### 201

Asset registered successfully

- `id` (string, required) — A unique identifier for an asset.
- `createdAt` (datetime, required) — The date and time the asset was created.
- `updatedAt` (datetime, required) — The date and time the asset was last updated.
- `type` (enum, required) — The type of asset file.
  - Allowed values: `AUDIO`, `VIDEO`, `IMAGE`
- `visibility` (enum, required) — Visibility scope for the asset.
  - Allowed values: `USER`, `ORGANIZATION`
- `name` (string, optional) — The filename of the asset.
- `url` (string, optional) — The URL to access the asset media.
- `size` (long, optional) — File size in bytes.
- `format` (string, optional) — File format/extension (e.g., "mp4", "wav").
- `inputType` (enum, optional) — The source type of the asset.
  - Allowed values: `UPLOAD`, `URL`, `RECORD`, `TTS`, `EXTRACT`, `PLATFORM_IMPORT`, `DIALOGUE_EDIT`
- `durationSeconds` (double, optional) — Duration of the media in seconds (for audio/video).
- `thumbnailUrl` (string, optional) — URL to the asset's thumbnail image.
- `width` (integer, optional) — Width in pixels (for video/image).
- `height` (integer, optional) — Height in pixels (for video/image).
- `projectId` (string, optional) — The id of the project this asset is attached to, or null when it belongs to no project.
- `proxyPath` (string, optional, nullable) — Stable API proxy path for authenticated media playback.

## Errors

### 400 Bad Request Error

Bad Request - Invalid input or unsupported model

- `message` (GenerationErrorMessage, required) — A message describing the error.
- `statusCode` (double, required) — The type of error that occurred.
- `errorCode` (string, optional) — Stable, machine-readable error code (e.g. project_not_found, voice_not_found, concurrency_limit_reached). Branch your error handling on this, not on the message text. The full catalog of codes with messages and suggested fixes is served unauthenticated at GET /v2/errors.
- `suggestion` (string, optional) — A suggested fix an agent can act on.
- `field` (string, optional) — The request field the error refers to, when the failure is tied to a specific field (e.g. projectId, voiceId, input[].assetId).
- `docsUrl` (string, optional) — Link to the documentation page for the failing operation.
- `requestId` (string, optional) — Present on unexpected 500 responses. Include it when contacting support so the failing request can be located directly.
- `generationId` (string, optional) — Original generation ID when available for an uncertain keyed submission. Poll this ID; do not create another action to bypass IDEMPOTENCY_OUTCOME_UNKNOWN.
- `dialogueEditSection` (DialogueEditRetimeFailureSection, optional) — For dialogue_edit_removal_too_large: the section to change, when it can be identified. Create a new dialogue edit preview before submitting again.

### 401 Unauthorized Error

Unauthorized - Invalid or missing authentication

- `message` (GenerationErrorMessage, required) — A message describing the error.
- `statusCode` (double, required) — The type of error that occurred.
- `errorCode` (string, optional) — Stable, machine-readable error code (e.g. project_not_found, voice_not_found, concurrency_limit_reached). Branch your error handling on this, not on the message text. The full catalog of codes with messages and suggested fixes is served unauthenticated at GET /v2/errors.
- `suggestion` (string, optional) — A suggested fix an agent can act on.
- `field` (string, optional) — The request field the error refers to, when the failure is tied to a specific field (e.g. projectId, voiceId, input[].assetId).
- `docsUrl` (string, optional) — Link to the documentation page for the failing operation.
- `requestId` (string, optional) — Present on unexpected 500 responses. Include it when contacting support so the failing request can be located directly.
- `generationId` (string, optional) — Original generation ID when available for an uncertain keyed submission. Poll this ID; do not create another action to bypass IDEMPOTENCY_OUTCOME_UNKNOWN.
- `dialogueEditSection` (DialogueEditRetimeFailureSection, optional) — For dialogue_edit_removal_too_large: the section to change, when it can be identified. Create a new dialogue edit preview before submitting again.

### 422 Unprocessable Entity Error

Unprocessable Entity - The requested generation is not downloadable (generation_not_downloadable), or submit-time validation failed (e.g. inaccessible media, invalid segments, a projectId/voiceId/assetId that does not resolve in your organization, or a file exceeding the plan size limit). The body carries a stable errorCode and, where applicable, the failing field.

- `message` (GenerationErrorMessage, required) — A message describing the error.
- `statusCode` (double, required) — The type of error that occurred.
- `errorCode` (string, optional) — Stable, machine-readable error code (e.g. project_not_found, voice_not_found, concurrency_limit_reached). Branch your error handling on this, not on the message text. The full catalog of codes with messages and suggested fixes is served unauthenticated at GET /v2/errors.
- `suggestion` (string, optional) — A suggested fix an agent can act on.
- `field` (string, optional) — The request field the error refers to, when the failure is tied to a specific field (e.g. projectId, voiceId, input[].assetId).
- `docsUrl` (string, optional) — Link to the documentation page for the failing operation.
- `requestId` (string, optional) — Present on unexpected 500 responses. Include it when contacting support so the failing request can be located directly.
- `generationId` (string, optional) — Original generation ID when available for an uncertain keyed submission. Poll this ID; do not create another action to bypass IDEMPOTENCY_OUTCOME_UNKNOWN.
- `dialogueEditSection` (DialogueEditRetimeFailureSection, optional) — For dialogue_edit_removal_too_large: the section to change, when it can be identified. Create a new dialogue edit preview before submitting again.

### 500 Internal Server Error

Internal Server Error - An unexpected failure on our side. The body carries errorCode internal_error and a requestId; include the requestId when contacting support.

- `message` (GenerationErrorMessage, required) — A message describing the error.
- `statusCode` (double, required) — The type of error that occurred.
- `errorCode` (string, optional) — Stable, machine-readable error code (e.g. project_not_found, voice_not_found, concurrency_limit_reached). Branch your error handling on this, not on the message text. The full catalog of codes with messages and suggested fixes is served unauthenticated at GET /v2/errors.
- `suggestion` (string, optional) — A suggested fix an agent can act on.
- `field` (string, optional) — The request field the error refers to, when the failure is tied to a specific field (e.g. projectId, voiceId, input[].assetId).
- `docsUrl` (string, optional) — Link to the documentation page for the failing operation.
- `requestId` (string, optional) — Present on unexpected 500 responses. Include it when contacting support so the failing request can be located directly.
- `generationId` (string, optional) — Original generation ID when available for an uncertain keyed submission. Poll this ID; do not create another action to bypass IDEMPOTENCY_OUTCOME_UNKNOWN.
- `dialogueEditSection` (DialogueEditRetimeFailureSection, optional) — For dialogue_edit_removal_too_large: the section to change, when it can be identified. Create a new dialogue edit preview before submitting again.

## Types

### GenerationErrorMessage

A message describing the error.

### DialogueEditRetimeFailureSection

The dialogue-edit section that removes too much speech to stay in sync.

- `slotIndex` (integer, required) — Zero-based index of the section in the preview's `resultSlots`.
- `sourceStartMs` (integer, optional) — Start of the section in the source video, in absolute milliseconds.
- `sourceDurationMs` (integer, optional) — Length of the section in the source video, in milliseconds.

## Examples

**Request**

```json
{
  "url": "https://assets.sync.so/org-123/my-video.mp4",
  "type": "VIDEO",
  "name": "my-video.mp4"
}
```

**Response**

```json
{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "createdAt": "2024-01-15T09:30:00Z",
  "updatedAt": "2024-01-15T09:30:00Z",
  "type": "VIDEO",
  "visibility": "USER",
  "name": "my-video.mp4",
  "url": "https://assets.sync.so/org-123/my-video.mp4",
  "size": 10485760,
  "format": "mp4",
  "inputType": "UPLOAD"
}
```

**SDK Code**

```python
import requests

url = "https://api.sync.so/v2/assets"

payload = {
    "url": "https://assets.sync.so/org-123/my-video.mp4",
    "type": "VIDEO",
    "name": "my-video.mp4"
}
headers = {
    "x-api-key": "<apiKey>",
    "Content-Type": "application/json"
}

response = requests.post(url, json=payload, headers=headers)

print(response.json())
```

```javascript
const url = 'https://api.sync.so/v2/assets';
const options = {
  method: 'POST',
  headers: {'x-api-key': '<apiKey>', 'Content-Type': 'application/json'},
  body: '{"url":"https://assets.sync.so/org-123/my-video.mp4","type":"VIDEO","name":"my-video.mp4"}'
};

try {
  const response = await fetch(url, options);
  const data = await response.json();
  console.log(data);
} catch (error) {
  console.error(error);
}
```

```go
package main

import (
	"fmt"
	"strings"
	"net/http"
	"io"
)

func main() {

	url := "https://api.sync.so/v2/assets"

	payload := strings.NewReader("{\n  \"url\": \"https://assets.sync.so/org-123/my-video.mp4\",\n  \"type\": \"VIDEO\",\n  \"name\": \"my-video.mp4\"\n}")

	req, _ := http.NewRequest("POST", url, payload)

	req.Header.Add("x-api-key", "<apiKey>")
	req.Header.Add("Content-Type", "application/json")

	res, _ := http.DefaultClient.Do(req)

	defer res.Body.Close()
	body, _ := io.ReadAll(res.Body)

	fmt.Println(res)
	fmt.Println(string(body))

}
```

```ruby
require 'uri'
require 'net/http'

url = URI("https://api.sync.so/v2/assets")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true

request = Net::HTTP::Post.new(url)
request["x-api-key"] = '<apiKey>'
request["Content-Type"] = 'application/json'
request.body = "{\n  \"url\": \"https://assets.sync.so/org-123/my-video.mp4\",\n  \"type\": \"VIDEO\",\n  \"name\": \"my-video.mp4\"\n}"

response = http.request(request)
puts response.read_body
```

```java
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.Unirest;

HttpResponse<String> response = Unirest.post("https://api.sync.so/v2/assets")
  .header("x-api-key", "<apiKey>")
  .header("Content-Type", "application/json")
  .body("{\n  \"url\": \"https://assets.sync.so/org-123/my-video.mp4\",\n  \"type\": \"VIDEO\",\n  \"name\": \"my-video.mp4\"\n}")
  .asString();
```

```php
<?php
require_once('vendor/autoload.php');

$client = new \GuzzleHttp\Client();

$response = $client->request('POST', 'https://api.sync.so/v2/assets', [
  'body' => '{
  "url": "https://assets.sync.so/org-123/my-video.mp4",
  "type": "VIDEO",
  "name": "my-video.mp4"
}',
  'headers' => [
    'Content-Type' => 'application/json',
    'x-api-key' => '<apiKey>',
  ],
]);

echo $response->getBody();
```

```csharp
using RestSharp;

var client = new RestClient("https://api.sync.so/v2/assets");
var request = new RestRequest(Method.POST);
request.AddHeader("x-api-key", "<apiKey>");
request.AddHeader("Content-Type", "application/json");
request.AddParameter("application/json", "{\n  \"url\": \"https://assets.sync.so/org-123/my-video.mp4\",\n  \"type\": \"VIDEO\",\n  \"name\": \"my-video.mp4\"\n}", ParameterType.RequestBody);
IRestResponse response = client.Execute(request);
```

```swift
import Foundation

let headers = [
  "x-api-key": "<apiKey>",
  "Content-Type": "application/json"
]
let parameters = [
  "url": "https://assets.sync.so/org-123/my-video.mp4",
  "type": "VIDEO",
  "name": "my-video.mp4"
] as [String : Any]

let postData = JSONSerialization.data(withJSONObject: parameters, options: [])

let request = NSMutableURLRequest(url: NSURL(string: "https://api.sync.so/v2/assets")! as URL,
                                        cachePolicy: .useProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.httpMethod = "POST"
request.allHTTPHeaderFields = headers
request.httpBody = postData as Data

let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
  if (error != nil) {
    print(error as Any)
  } else {
    let httpResponse = response as? HTTPURLResponse
    print(httpResponse)
  }
})

dataTask.resume()
```