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