Skip to main content

Azure Blob Storage

Prerequisites​

  • A PolyDoc account and API key — sign up for free, no credit card required. The free plan includes 150 PDF conversions per month.
  • An Azure Storage account with a Blob container. Create a storage account →
  • Storage account access key or a service principal with Storage Blob Data Contributor role.

Integration​

Generate a SAS URL​

Azure uses Shared Access Signature (SAS) URLs instead of presigned URLs. The result is functionally identical: a URL that grants time-limited write access to a single blob.
import {
generateBlobSASQueryParameters,
BlobSASPermissions,
StorageSharedKeyCredential,
} from "@azure/storage-blob";

// npm install @azure/storage-blob

const account = "mystorageaccount";
const accountKey = process.env.AZURE_STORAGE_KEY;
const credential = new StorageSharedKeyCredential(account, accountKey);

const containerName = "invoices";
const blobName = "inv-001.pdf";

const sasToken = generateBlobSASQueryParameters(
{
containerName,
blobName,
permissions: BlobSASPermissions.parse("cw"), // create + write
expiresOn: new Date(Date.now() + 5 * 60 * 1000),
contentType: "application/pdf",
},
credential
).toString();

const presignedUrl =
`https://${account}.blob.core.windows.net/${containerName}/${blobName}?${sasToken}`;
import os
from datetime import datetime, timedelta, timezone
from azure.storage.blob import (
generate_blob_sas,
BlobSasPermissions,
)

# pip install azure-storage-blob

account = "mystorageaccount"
account_key = os.environ["AZURE_STORAGE_KEY"]
container = "invoices"
blob = "inv-001.pdf"

sas_token = generate_blob_sas(
account_name=account,
container_name=container,
blob_name=blob,
account_key=account_key,
permission=BlobSasPermissions(write=True, create=True),
expiry=datetime.now(timezone.utc) + timedelta(minutes=5),
content_type="application/pdf",
)

presigned_url = (
f"https://{account}.blob.core.windows.net/{container}/{blob}?{sas_token}"
)
# Azure does not publish an official Ruby gem for Blob Storage SAS URLs.
# Use the Azure CLI (see CLI tab) or generate the SAS token via the REST API
# using a plain HMAC-SHA256 signature with your account key.
#
# Alternatively, call the Azure REST API:
# https://docs.microsoft.com/en-us/rest/api/storageservices/create-service-sas

require 'openssl'
require 'base64'
require 'cgi'
require 'time'

account = 'mystorageaccount'
key = Base64.decode64(ENV['AZURE_STORAGE_KEY'])
container = 'invoices'
blob = 'inv-001.pdf'
expiry = (Time.now.utc + 300).iso8601.sub(/\.\d+/, '')

# StringToSign for Blob SAS (simplified version)
string_to_sign = [
'cw', # signedPermissions: create + write
'', # signedStart (omitted)
expiry, # signedExpiry
"/blob/#{account}/#{container}/#{blob}",
'', # signedIdentifier
'', # signedIP
'https', # signedProtocol
'2020-04-08', # signedVersion
'b', # signedResource: blob
'', # signedSnapshotTime
'', # rscc
'application/pdf', # rsct (content-type)
'', # rsce / rscl / rsccd
].join("\n")

sig = Base64.strict_encode64(OpenSSL::HMAC.digest('SHA256', key, string_to_sign))

params = URI.encode_www_form(
sv: '2020-04-08', sr: 'b', st: '', se: expiry,
sp: 'cw', spr: 'https', sig: sig, rsct: 'application/pdf'
)

presigned_url = "https://#{account}.blob.core.windows.net/#{container}/#{blob}?#{params}"
package main

import (
"fmt"
"os"
"time"

"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/sas"
"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/service"
)

// go get github.com/Azure/azure-sdk-for-go/sdk/storage/azblob

func main() {
account := "mystorageaccount"
accountKey := os.Getenv("AZURE_STORAGE_KEY")
container := "invoices"
blob := "inv-001.pdf"

cred, _ := service.NewSharedKeyCredential(account, accountKey)

sasValues := sas.BlobSignatureValues{
Protocol: sas.ProtocolHTTPS,
ExpiryTime: time.Now().UTC().Add(5 * time.Minute),
ContainerName: container,
BlobName: blob,
Permissions: to.Ptr(sas.BlobPermissions{Write: true, Create: true}).String(),
}

sasQuery, _ := sasValues.SignWithSharedKey(cred)

presignedUrl := fmt.Sprintf(
"https://%s.blob.core.windows.net/%s/%s?%s",
account, container, blob, sasQuery.Encode(),
)
fmt.Println(presignedUrl)
}
<?php
// composer require microsoft/azure-storage-blob

use MicrosoftAzure\Storage\Blob\BlobSharedAccessSignatureHelper;
use MicrosoftAzure\Storage\Common\Internal\Resources;

$account = 'mystorageaccount';
$accountKey = getenv('AZURE_STORAGE_KEY');
$container = 'invoices';
$blob = 'inv-001.pdf';

$helper = new BlobSharedAccessSignatureHelper($account, $accountKey);

$sasToken = $helper->generateBlobServiceSharedAccessSignatureToken(
Resources::RESOURCE_TYPE_BLOB,
$container . '/' . $blob,
'cw', // permissions: create + write
(new \DateTime('+5 minutes'))->format('Y-m-d\TH:i:s\Z'),
'', // signedStart
'', // signedIP
'https',
'', // signedIdentifier
'application/pdf'
);

$presignedUrl =
"https://{$account}.blob.core.windows.net/{$container}/{$blob}?{$sasToken}";
use azure_storage::StorageCredentials;
use azure_storage_blobs::prelude::*;
use time::Duration;

// Cargo.toml:
// azure_storage = "0.20"
// azure_storage_blobs = "0.20"
// time = "0.3"

#[tokio::main]
async fn main() -> azure_core::Result<()> {
let account = "mystorageaccount";
let key = std::env::var("AZURE_STORAGE_KEY").unwrap();
let container = "invoices";
let blob_name = "inv-001.pdf";

let creds = StorageCredentials::access_key(account, key);
let client = BlobServiceClient::new(account, creds)
.container_client(container)
.blob_client(blob_name);

let sas = client
.shared_access_signature(
BlobSasPermissions {
write: true,
create: true,
..Default::default()
},
time::OffsetDateTime::now_utc() + Duration::minutes(5),
)
.await?;

println!("{}", client.generate_signed_blob_url(&sas)?);
Ok(())
}
import com.azure.storage.blob.BlobServiceClientBuilder;
import com.azure.storage.blob.models.BlobSasPermission;
import com.azure.storage.blob.sas.BlobSasQueryParameters;
import com.azure.storage.blob.sas.BlobServiceSasSignatureValues;
import com.azure.storage.common.StorageSharedKeyCredential;
import java.time.OffsetDateTime;

// implementation 'com.azure:azure-storage-blob:12.x.x' in build.gradle

public class Main {
public static void main(String[] args) {
String account = "mystorageaccount";
String key = System.getenv("AZURE_STORAGE_KEY");
String container = "invoices";
String blob = "inv-001.pdf";

StorageSharedKeyCredential cred = new StorageSharedKeyCredential(account, key);

BlobSasPermission perm = new BlobSasPermission()
.setWritePermission(true)
.setCreatePermission(true);

BlobServiceSasSignatureValues values = new BlobServiceSasSignatureValues(
OffsetDateTime.now().plusMinutes(5), perm)
.setContentType("application/pdf");

BlobSasQueryParameters params = values.generateSasQueryParameters(cred);

String presignedUrl = String.format(
"https://%s.blob.core.windows.net/%s/%s?%s",
account, container, blob, params.encode()
);
System.out.println(presignedUrl);
}
}
using Azure.Storage.Blobs;
using Azure.Storage.Blobs.Specialized;
using Azure.Storage.Sas;
using Azure.Storage;
using System;

// NuGet: Azure.Storage.Blobs

var account = "mystorageaccount";
var key = Environment.GetEnvironmentVariable("AZURE_STORAGE_KEY");
var container = "invoices";
var blobName = "inv-001.pdf";

var credential = new StorageSharedKeyCredential(account, key);
var serviceUri = new Uri($"https://{account}.blob.core.windows.net");
var blobClient = new BlobServiceClient(serviceUri, credential)
.GetBlobContainerClient(container)
.GetBlobClient(blobName);

var sasBuilder = new BlobSasBuilder
{
BlobContainerName = container,
BlobName = blobName,
Resource = "b",
ExpiresOn = DateTimeOffset.UtcNow.AddMinutes(5),
ContentType = "application/pdf",
};
sasBuilder.SetPermissions(BlobSasPermissions.Write | BlobSasPermissions.Create);

string presignedUrl = blobClient.GenerateSasUri(sasBuilder).ToString();
Console.WriteLine(presignedUrl);
# Requires Azure CLI authenticated to your subscription
ACCOUNT="mystorageaccount"
CONTAINER="invoices"
BLOB="inv-001.pdf"
EXPIRY=$(date -u -d "+5 minutes" '+%Y-%m-%dT%H:%MZ')

SAS=$(az storage blob generate-sas \
--account-name "$ACCOUNT" \
--container-name "$CONTAINER" \
--name "$BLOB" \
--permissions cw \
--expiry "$EXPIRY" \
--https-only \
--output tsv)

echo "https://${ACCOUNT}.blob.core.windows.net/${CONTAINER}/${BLOB}?${SAS}"

PolyDoc API request​

Pass the SAS URL to PolyDoc. The converted document is uploaded directly to your Blob Storage container. The response is JSON containing the permanent blob URL.
const response = await fetch('https://api.polydoc.tech/pdf/convert', {
method: 'POST',
headers: {
'Authorization': 'Bearer <YOUR_API_KEY>',
'Content-Type': 'application/json',
'X-Sandbox': 'true',
},
body: JSON.stringify({
source: '<html><body><h1>Hello</h1></body></html>',
cloudStorage: { presignedUrl },
}),
});

const result = await response.json();
console.log(result.data.url);
// → https://mystorageaccount.blob.core.windows.net/invoices/inv-001.pdf
import requests

headers = {
'Authorization': 'Bearer <YOUR_API_KEY>',
'Content-Type': 'application/json',
'X-Sandbox': 'true',
}

data = {
'source': '<html><body><h1>Hello</h1></body></html>',
'cloudStorage': {'presignedUrl': presigned_url},
}

response = requests.post(
'https://api.polydoc.tech/pdf/convert',
headers=headers,
json=data,
)

result = response.json()
print(result['data']['url'])
# → https://mystorageaccount.blob.core.windows.net/invoices/inv-001.pdf
require 'net/http'
require 'uri'
require 'json'

uri = URI.parse('https://api.polydoc.tech/pdf/convert')
request = Net::HTTP::Post.new(uri)
request['Authorization'] = 'Bearer <YOUR_API_KEY>'
request['Content-Type'] = 'application/json'
request['X-Sandbox'] = 'true'
request.body = JSON.dump({
'source' => '<html><body><h1>Hello</h1></body></html>',
'cloudStorage' => { 'presignedUrl' => presigned_url },
})

response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
http.request(request)
end

result = JSON.parse(response.body)
puts result['data']['url']
# → https://mystorageaccount.blob.core.windows.net/invoices/inv-001.pdf
package main

import (
"bytes"
"encoding/json"
"fmt"
"net/http"
)

func main() {
payload := map[string]interface{}{
"source": "<html><body><h1>Hello</h1></body></html>",
"cloudStorage": map[string]string{
"presignedUrl": presignedUrl,
},
}
body, _ := json.Marshal(payload)

req, _ := http.NewRequest("POST", "https://api.polydoc.tech/pdf/convert", bytes.NewBuffer(body))
req.Header.Set("Authorization", "Bearer <YOUR_API_KEY>")
req.Header.Set("Content-Type", "application/json")
req.Header.Set("X-Sandbox", "true")

client := &http.Client{}
resp, _ := client.Do(req)
defer resp.Body.Close()

var result map[string]interface{}
json.NewDecoder(resp.Body).Decode(&result)
fmt.Println(result["data"].(map[string]interface{})["url"])
// → https://mystorageaccount.blob.core.windows.net/invoices/inv-001.pdf
}
<?php
$ch = curl_init();

curl_setopt($ch, CURLOPT_URL, 'https://api.polydoc.tech/pdf/convert');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
'source' => '<html><body><h1>Hello</h1></body></html>',
'cloudStorage' => ['presignedUrl' => $presignedUrl],
]));
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Authorization: Bearer <YOUR_API_KEY>',
'Content-Type: application/json',
'X-Sandbox: true',
]);

$response = curl_exec($ch);
curl_close($ch);

$result = json_decode($response, true);
echo $result['data']['url'];
// → https://mystorageaccount.blob.core.windows.net/invoices/inv-001.pdf
use reqwest::Client;
use serde_json::json;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = Client::new();

let res = client
.post("https://api.polydoc.tech/pdf/convert")
.header("Authorization", "Bearer <YOUR_API_KEY>")
.header("X-Sandbox", "true")
.json(&json!({
"source": "<html><body><h1>Hello</h1></body></html>",
"cloudStorage": { "presignedUrl": presigned_url }
}))
.send()
.await?;

let result: serde_json::Value = res.json().await?;
println!("{}", result["data"]["url"]);
// → https://mystorageaccount.blob.core.windows.net/invoices/inv-001.pdf

Ok(())
}
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.net.http.HttpRequest.BodyPublishers;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.Map;

public class Main {
public static void main(String[] args) throws Exception {
HttpClient client = HttpClient.newHttpClient();
ObjectMapper mapper = new ObjectMapper();

String body = mapper.writeValueAsString(Map.of(
"source", "<html><body><h1>Hello</h1></body></html>",
"cloudStorage", Map.of("presignedUrl", presignedUrl)
));

HttpRequest request = HttpRequest.newBuilder()
.uri(new URI("https://api.polydoc.tech/pdf/convert"))
.header("Authorization", "Bearer <YOUR_API_KEY>")
.header("Content-Type", "application/json")
.header("X-Sandbox", "true")
.POST(BodyPublishers.ofString(body))
.build();

HttpResponse<String> response = client.send(
request, HttpResponse.BodyHandlers.ofString()
);

var result = mapper.readValue(response.body(), Map.class);
System.out.println(((Map) result.get("data")).get("url"));
// → https://mystorageaccount.blob.core.windows.net/invoices/inv-001.pdf
}
}
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;

using var client = new HttpClient();
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", "<YOUR_API_KEY>");
client.DefaultRequestHeaders.Add("X-Sandbox", "true");

var payload = JsonSerializer.Serialize(new {
source = "<html><body><h1>Hello</h1></body></html>",
cloudStorage = new { presignedUrl }
});

var content = new StringContent(payload, Encoding.UTF8, "application/json");
var response = await client.PostAsync("https://api.polydoc.tech/pdf/convert", content);
var json = await response.Content.ReadAsStringAsync();

using var doc = JsonDocument.Parse(json);
Console.WriteLine(doc.RootElement.GetProperty("data").GetProperty("url").GetString());
// → https://mystorageaccount.blob.core.windows.net/invoices/inv-001.pdf
curl --location \
--request POST 'https://api.polydoc.tech/pdf/convert' \
--header 'Authorization: Bearer <YOUR_API_KEY>' \
--header 'Content-Type: application/json' \
--header 'X-Sandbox: true' \
--data-raw '{
"source": "<html><body><h1>Hello</h1></body></html>",
"cloudStorage": {
"presignedUrl": "<PRESIGNED_URL>"
}
}'

# Response:
# {
# "success": true,
# "data": {
# "url": "https://my-bucket.s3.eu-central-1.amazonaws.com/invoices/inv-001.pdf"
# }
# }

Response​

On success, PolyDoc returns the permanent blob URL (SAS query parameters stripped).
{
"success": true,
"message": "PDF uploaded to cloud storage.",
"data": {
"url": "https://mystorageaccount.blob.core.windows.net/invoices/inv-001.pdf"
}
}

Tips​