# Signed URLs

> Convert files on demand using URL query parameters.

## Signed URLs

Signed URLs allow converting files on demand only using URL query parameters. As the signed URLs are publicly visible, they don't use your API key and use an encrypted signature instead.

For example, this feature is useful to build "Download as PDF" buttons easily without the need of a complete API integration.

#### Example Signed URL

```bash
https://s.cloudconvert.com/b3d85428-584e-4639-bc11-76b7dee9c109
?job=eyJ0YXNrcyI6eyJpbXBvcnQtaX...&s=69e56b8fead...
```

---

## Create signed URL

First, you need to [create a signed URL base](https://cloudconvert.com/dashboard/api/v2/signed-urls) using your [dashboard](https://cloudconvert.com/dashboard/api/v2/signed-urls). Using this signed URL base you can create dynamic signed URLs using the following URL parameters.

Second, you can use the "Generate Signed URL" button on your [dashboard](https://cloudconvert.com/dashboard/api/v2/signed-urls) to use a simple graphical interface for creating signed URLs. Alternatively, you can build dynamic signed URLs via code as described below.

We recommend using our official SDKs to create the signed URLs, as shown below. All our SDKs have built functions to properly generate the query parameters and signatures of the signed URLs.

<caution>

Do not include any sensitive information in the job payload (such as S3 credentials)! The job payload can be extracted from the URL.

</caution>

<warning>

Each request to a signed URL starts a new job and therefore consumes credits by default. Therefore, make sure you are using the `cache_key` parameter to start a job only once every 24h.

</warning>

#### Query Parameters

<field-group>
<field name="job" type="string" :required="true">
<div>

The Base64-encoded JSON job payload. This is the same payload, you would regularly send as body to the `/jobs` endpoint. The job is required to have an `export/url` task which will automatically used to redirect to.

Please make sure you are using URL-safe Base64 encoding, where the '+' and '/' characters of standard Base64 are respectively replaced by '-' and '_'.

</div>
</field>

<field name="cache_key" type="string">
<div>

By default, the output files of signed URLs will not be cached and each request to the URL starts a new job. When using `cache_key`, the output files will be cached based on the specified key for 24 hours.

This parameter on the one hand speeds up subsequent requests to the same URL and one the other hand it reduces costs by only doing the conversion once for 24h.

Only alphanumeric characters, - and _ are allowed as cache key.

</div>
</field>

<field name="s" type="string" :required="true">
<div>

The signature which is calculated using [HMAC](https://en.wikipedia.org/wiki/Hash-based_message_authentication_code) with [SHA-256](https://en.wikipedia.org/wiki/SHA-2).

Each signed URL base has an unique signing secret to generate the signature. You can show the signing secret of your signed URL base in your [signed URL settings](https://cloudconvert.com/dashboard/api/v2/signed-urls) using the <icon name="lucide-key-round">



</icon>

 button.

The signature is a hash over the URL (signed URL base including query parameters, except `&s`). The generated signature is then appended as `&s=` as the last parameter in your URLs.

As an example, the following PHP function calculates the signature:

```php
$signature = hash_hmac('sha256', $url, $signingSecret);
```

</div>
</field>
</field-group>

#### Signed URL Response

If successful, it redirects to the output file of the conversion (a `302` redirect with the `Location` header pointing to the output file).

If the conversion fails, the job status payload will be returned as JSON.

#### Code Examples

<code-group>

```php [PHP]
<?php

$cloudconvert = new CloudConvert([
    'api_key' => 'API_KEY',
    'sandbox' => false
]);

$job = (new Job())
    ->addTask(
        (new Task('import/url', 'import-my-file'))
            ->set('url', 'https://my.url/file.docx')
    )
    ->addTask(
        (new Task('convert', 'convert-my-file'))
            ->set('input', 'import-my-file')
            ->set('output_format', 'pdf')
    )
    ->addTask(
        (new Task('export/url', 'export-my-file'))
            ->set('input', 'convert-my-file')
    );

$signedUrlBase = 'SIGNED_URL_BASE';
$signingSecret = 'SIGNED_URL_SIGNING_SECRET';
$url = $cloudConvert->signedUrlBuilder()->createFromJob($signedUrlBase, $signingSecret, $job, 'CACHE_KEY');
```

```js [node.js]
const signedUrlBase = 'https://s.cloudconvert.com/...'; // You can find it in your signed URL settings.
const signingSecret = '...'; // You can find it in your signed URL settings.
const cacheKey = 'cache-key'; // Allows caching of the result file for 24h

const job = {
    tasks: {
        'import-it': {
            operation: 'import/url',
            url: 'https://some.url',
            filename: 'logo.png'
        },
        'export-it': {
            operation: 'export/url',
            input: 'import-it',
            inline: true
        }
    }
};

const url = cloudConvert.signedUrls.sign(
    signedUrlBase,
    signingSecret,
    job,
    cacheKey
); // returns the generated URL
```

```python [Python]
base = 'https://s.cloudconvert.com/...'  # You can find it in your signed URL settings.
signing_secret = '...'  # You can find it in your signed URL settings.
cache_key = 'cache-key'  # Allows caching of the result file for 24h

job = {
    "tasks": {
        "import-file": {
            "operation": "import/url",
            "url": "https://github.com/cloudconvert/cloudconvert-php/raw/master/tests/Integration/files/input.pdf"
        },
        "export-file": {
            "operation": "export/url",
            "input": "import-file"
        }
    }
}

url = cloudconvert.SignedUrl.sign(base, signing_secret, job, cache_key);  # returns the URL
```

```ruby [Ruby]
base = 'https://s.cloudconvert.com/...' # You can find it in your signed URL settings.
signing_secret = '...' # You can find it in your signed URL settings.
cache_key = 'cache-key' # Allows caching of the result file for 24h

job = {
  tasks: {
    "import-it": { operation: "import/url", filename: "test.file", url: "http://invalid.url" },
    "convert-it": { input: "import-it", operation: "convert", output_format: "pdf" },
  }
}

url = CloudConvert::SignedUrl.sign(base, signing_secret, job, cache_key)
```

```java [Java]
final String base = "https://s.cloudconvert.com/..."; // You can find it in your signed URL settings.
final String signingSecret = "..."; // You can find it in your signed URL settings.
final String cacheKey = "mykey"; // Allows caching of the result file for 24h

final Map<String, TaskRequest> tasks = ImmutableMap.of(
        "import-my-file", new UrlImportRequest().setUrl("import-url"),
        "convert-my-file", new ConvertFilesTaskRequest()
        .setInput("import-my-file")
        .setOutputFormat("pdf")
        "export-my-file", new UrlExportRequest().setInput("convert-my-file")
        );


final String url = cloudConvertClient.signedUrls().sign(base, signingSecret, tasks, cacheKey);
```

```csharp [.NET]
var signedUrlBase = 'https://s.cloudconvert.com/...'; // You can find it in your signed URL settings.
var signingSecret = '...'; // You can find it in your signed URL settings.
var cacheKey = 'cache-key'; // Allows caching of the result file for 24h

var job = new JobCreateRequest
      {
        Tasks = new
        {
          import_example_1 = new ImportUploadCreateRequest(),
          convert = new ConvertCreateRequest
          {
            Input = "import_example_1",
            Input_Format = "pdf",
            Output_Format = "docx"
          },
          export = new ExportUrlCreateRequest
          {
            Input = "convert"
          }
        },
};

string signedUrl = _cloudConvertAPI.CreateSignedUrl(baseUrl, signingSecret, job, cacheKey)
// returns the signed URL
```

</code-group>
