Quickstart Tutorial
In this guide, you'll protect your first image with SASHA Signature technology. By the end, you'll have a protected image and a SignatureID that uniquely identifies the protection.
Time to complete: ~10 minutes
What you'll do
- Get an access token from the Partner API
- Submit an image for protection
- Poll for job completion
- Download your protected image
What you'll need
- Client ID and Client Secret from SASHA (see Prerequisites)
- An image URL or local image file (JPEG or PNG)
- Basic command-line knowledge
This guide uses polling to check job status. For production use, we recommend setting up callbacks to receive automatic notifications when jobs complete.
If you're using gRPC, you'll need nice-grpc (or your preferred gRPC client) and generated client code from SASHA .proto files.
Step 1: Get an Access Token
First, authenticate with the Partner API to get an access token.
- REST
- gRPC
Replace YOUR_CLIENT_ID
and YOUR_CLIENT_SECRET
with your actual credentials:
curl -X POST https://partner.api.sasha.eu/oauth/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-u "YOUR_CLIENT_ID:YOUR_CLIENT_SECRET" \
-d "grant_type=client_credentials"
Response:
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "Bearer",
"expires_in": 3600
}
Save the access_token
value - you'll use it in the next steps. Tokens expire after the time specified in expires_in
(in seconds).
import { createChannel, createClient } from "nice-grpc";
import { PartnerAPIClient } from "./generated/partner_api_grpc_pb.js";
const channel = createChannel("partner.api.sasha.eu:443");
const client = createClient(PartnerAPIClient, channel);
const authResponse = await client.authenticateWithClientCredential({
clientId: "YOUR_CLIENT_ID",
clientSecret: "YOUR_CLIENT_SECRET",
});
const accessToken = authResponse.accessToken;
console.log("Access token:", accessToken);
Save the accessToken
value - you'll use it in the next steps.
Step 2: Submit Your Image for Protection
Now submit your image to the PartnerAPI for protection. We'll use a publicly accessible image URL.
- REST
- gRPC
curl -X POST https://partner.api.sasha.eu/signature/embed \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"media_url": "/img/sasha-logo-wall.jpeg",
"media_mime_type": "image/jpeg"
}'
Response:
{
"job": {
"job_id": "job_abc123xyz",
"type": "embed-signature",
"status": "pending",
"created_at": "2025-10-07T12:00:00Z",
"updated_at": "2025-10-07T12:00:00Z"
}
}
Save the job_id
value - you'll use it to check the job status.
You can also upload a local file using multipart/form-data
(REST) or the embedSignatureFromData
streaming method (gRPC). See the API Reference for details.
import { createChannel, createClient, Metadata } from "nice-grpc";
import { PartnerServiceClient } from "./generated/partner_grpc_pb.js";
const partnerChannel = createChannel("partner.api.sasha.eu:443");
const partnerClient = createClient(PartnerServiceClient, partnerChannel);
// Create metadata with authorization token
const metadata = new Metadata();
metadata.set("authorization", `Bearer ${accessToken}`);
// Submit image for protection
const embedResponse = await partnerClient.embedSignatureFromURL(
{
// todo: fix this for prod url
mediaUrl: "/img/sasha-logo-wall.jpeg",
mediaMimeType: "image/jpeg",
},
{ metadata }
);
const jobId = embedResponse.job.jobId;
console.log("Job ID:", jobId);
console.log("Job status:", embedResponse.job.status);
For local files, use the embedSignatureFromData
streaming method. See the gRPC API Reference for details.
Step 3: Poll for Job Completion
The embedding process takes a few seconds. Poll the job status until it's complete.
- REST
- gRPC
curl -X GET https://partner.api.sasha.eu/jobs/job_abc123xyz \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
Response (while processing):
{
"job_id": "job_abc123xyz",
"type": "embed-signature",
"status": "in_progress",
"created_at": "2025-10-07T12:00:00Z",
"updated_at": "2025-10-07T12:00:01Z"
}
Response (when complete):
{
"job_id": "job_abc123xyz",
"type": "embed-signature",
"status": "completed",
"output_url": "https://storage.sasha.eu/protected/abc123.jpg",
"output_url_expires_at": "2025-10-07T13:00:00Z",
"signature_id": "12345678901234567890",
"created_at": "2025-10-07T12:00:00Z",
"updated_at": "2025-10-07T12:00:05Z"
}
Check the status every 1-2 seconds. Most jobs complete within 5-10 seconds.
const waitForCompletion = async (jobId) => {
while (true) {
const jobResponse = await partnerClient.getJob(
{ jobId },
{ metadata }
);
console.log("Job status:", jobResponse.status);
if (jobResponse.status === "completed") {
return jobResponse;
}
if (jobResponse.status === "failed") {
throw new Error(`Job failed: ${jobResponse.error.message}`);
}
// Wait 1 second before checking again
await new Promise((resolve) => setTimeout(resolve, 1000));
}
};
const completedJob = await waitForCompletion(jobId);
console.log("Protected image URL:", completedJob.outputUrl);
console.log("Signature ID:", completedJob.signatureId);
Step 4: Download Your Protected Image
Once the job is complete, download the protected image.
- REST
- gRPC
curl -o protected-image.jpg "https://storage.sasha.eu/protected/abc123.jpg"
The output_url
expires after the time specified in output_url_expires_at
. Download your image before it expires!
import { writeFileSync } from "fs";
const imageResponse = await fetch(completedJob.outputUrl);
const imageBuffer = await imageResponse.arrayBuffer();
writeFileSync("protected-image.jpg", Buffer.from(imageBuffer));
console.log("Protected image saved!");
The output URL expires after the time specified in outputUrlExpiresAt
. Download your image before it expires!
✅ Success!
Congratulations! You've successfully protected your first image with SASHA. You should now have:
- ✅ A
job_id
that tracked the embedding process - ✅ A
signature_id
(e.g.,12345678901234567890
) that uniquely identifies this protection - ✅ A protected image that looks identical to the original but contains the embedded SASHA Signature
What you learned
- How to authenticate with the Partner API using client credentials
- How to submit an image for protection
- How to poll for job completion
- How to download the protected image
Look up the protection
If you want to verify the Signature was embedded, use the Lookup API:
- REST
- gRPC
curl -X POST https://partner.api.sasha.eu/signature/lookup \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"media_url": "[USE_OUTPUT_URL_FROM_STEP_3]",
"media_mime_type": "image/jpeg"
}'
Replace [USE_OUTPUT_URL_FROM_STEP_3]
with the output_url
you received in Step 3.
const lookupResponse = await partnerClient.lookupSignatureFromURL(
{
mediaUrl: completedJob.outputUrl,
mediaMimeType: "image/jpeg",
},
{ metadata }
);
// Wait for lookup job to complete (same as embed)
const lookupResult = await waitForCompletion(lookupResponse.job.jobId);
console.log("Found signature ID:", lookupResult.signatureId);
// Should match the signature_id from the embed job!
The returned signature_id
should match the one from your embed job!
Common Issues
Issue | Solution |
---|---|
401 Unauthorized | Check your Client ID and Secret, or your access token may have expired |
400 Bad Request with "media_url is required" | Ensure you're sending the correct request format |
Job status is failed with unsupported_format | Check that your image is JPEG or PNG format |
404 Not Found when downloading output_url | The URL may have expired - check the expiration timestamp in the job response |
Next Steps
Now that you've protected your first image, explore these topics:
- Authentication Concepts - Learn about token lifecycle and refresh strategies
- Signature Concepts - Deep dive into how signatures work
- Callback Setup - Get notified automatically when jobs complete (recommended for production)
- Error Handling - Handle rate limits and errors gracefully
- API Reference - Complete REST API documentation
- gRPC API Reference - Complete gRPC API documentation
For production applications, we recommend:
- Using callbacks instead of polling for better performance
- Implementing token caching and proactive renewal
- Adding proper error handling and retry logic