gRPC API
SASHA offers a gRPC-based API for signature operations that provides an alternative to our REST API. The gRPC API is particularly useful for high-performance applications and systems that require efficient binary communication, especially when working with large image files.
Overview
The PartnerAPI gRPC service provides functionality for:
- Authentication using client credentials to obtain access tokens
- Embedding signatures into images to protect them with SASHA technology
- Looking up signatures from images to detect if they're already protected
- Managing jobs for asynchronous processing of signature operations
- Streaming support for efficient handling of large image files
Service Definition
The PartnerAPI service is defined in our protocol buffer specifications. You can find the complete service definition and message types in our GitHub repository: github.com/Sasha-ApS/apis
Protobuf | Description |
---|---|
partner_api.proto | Defines the PartnerAPI service |
job.proto | Defines job-related message types |
signature_id.proto | Defines SignatureID message type |
Connection Details
Service Endpoint
The gRPC PartnerAPI service is available at:
partner.api.sasha.eu:443
(TLS enabled)
Authentication
Overview
Partners must authenticate with the PartnerAPI.AuthenticateWithClientCredential
RPC using the provided client_id
and client_secret
credentials, and then pass the resulting access token in the "Authorization" metadata with the format Bearer <token>
for all subsequent API calls.
AuthenticateWithClientCredential
This RPC method grants your application access to SASHA services using the OAuth 2.0 client credentials flow.
Method Signature:
rpc AuthenticateWithClientCredential(AuthenticateWithClientCredentialRequest)
returns (AuthenticateWithClientCredentialResponse) {
option idempotency_level = IDEMPOTENT;
}
Request Message:
message AuthenticateWithClientCredentialRequest {
string client_id = 1; // Required: Your application's Client ID
string client_secret = 2; // Required: Your application's Client Secret
}
Response Message:
message AuthenticateWithClientCredentialResponse {
string access_token = 1; // The access token for API calls
uint32 expires_in = 2; // Token expiration time in seconds
}
Using the Access Token
Once you obtain an access token from AuthenticateWithClientCredential
, you must include it in the metadata of all subsequent requests to PartnerAPI methods.
gRPC Metadata:
Authorization: Bearer <access-token>
Error Handling
The PartnerAPI RPCs may return the following authentication errors:
-
Token expired If the token is expired, the service returns an "UNAUTHENTICATED" status code with the message starting with "Token expired".
-
Token missing If the token is missing, the service returns an "UNAUTHENTICATED" status code with the message starting with "Token not provided".
-
Token invalid If the token is invalid, the service returns an "UNAUTHENTICATED" status code with the message starting with "Invalid token".
-
Other authorization issues The service may return "UNAUTHENTICATED" status code for other authorization issues.
Embed Signature Operations
EmbedSignatureFromURL
Protects an image by embedding a SASHA Signature using an image URL.
Method Signature:
rpc EmbedSignatureFromURL(EmbedSignatureFromURLRequest)
returns (EmbedSignatureFromURLResponse);
Request Message:
message EmbedSignatureFromURLRequest {
string media_url = 2; // Required: URL of the image to protect
string media_mime_type = 3; // Required: MIME type (image/jpeg, image/png)
}
Response Message:
message EmbedSignatureFromURLResponse {
Job job = 1; // The created job for async processing
}
EmbedSignatureFromData
Protects an image by embedding a SASHA Signature using streaming image data.
Method Signature:
rpc EmbedSignatureFromData(stream EmbedSignatureFromDataRequest)
returns (EmbedSignatureFromDataResponse);
Request Message (Streaming):
message EmbedSignatureFromDataRequest {
bytes media_data_chunk = 1; // Image data chunk (can be streamed)
string media_mime_type = 2; // Required: MIME type (image/jpeg, image/png)
}
Response Message:
message EmbedSignatureFromDataResponse {
Job job = 1; // The created job for async processing
}
Lookup Signature Operations
LookupSignatureFromURL
Checks if an image is already protected with SASHA Signature using an image URL.
Method Signature:
rpc LookupSignatureFromURL(LookupSignatureFromURLRequest)
returns (LookupSignatureFromURLResponse);
Request Message:
message LookupSignatureFromURLRequest {
string media_url = 2; // Required: URL of the image to check
string media_mime_type = 3; // Required: MIME type (image/jpeg, image/png)
}
LookupSignatureFromData
Checks if an image is already protected with SASHA Signature using streaming image data.
Method Signature:
rpc LookupSignatureFromData(stream LookupSignatureFromDataRequest)
returns (LookupSignatureFromDataResponse);
Request Message (Streaming):
message LookupSignatureFromDataRequest {
bytes media_data_chunk = 1; // Image data chunk (can be streamed)
string media_mime_type = 2; // Required: MIME type (image/jpeg, image/png)
}
Job Management Operations
All signature operations are asynchronous and return a Job
object. Use these methods to manage and monitor job progress.
GetJob
Retrieves the status and results of a specific job.
Method Signature:
rpc GetJob(GetJobRequest) returns (Job);
Request Message:
message GetJobRequest {
string job_id = 1; // Required: The job ID to retrieve
}
ListJobs
Lists all jobs for the authenticated partner.
Method Signature:
rpc ListJobs(ListJobsRequest) returns (ListJobsResponse);
WatchJob
Watches a job for completion using server-side streaming (alternative to polling).
Method Signature:
rpc WatchJob(WatchJobRequest) returns (stream WatchJobResponse);
Request Message:
message WatchJobRequest {
string job_id = 1; // Required: The job ID to watch
}
Response Stream:
message WatchJobResponse {
Job job = 1; // Updated job status
}
Code Examples
Go Example - Authentication
package main
import (
"context"
"crypto/tls"
"log"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
partnerapiv1 "github.com/Sasha-ApS/apis/sasha/partner-api/v1"
)
func authenticate(clientID, clientSecret string) string {
// Create TLS credentials
creds := credentials.NewTLS(&tls.Config{})
// Connect to the PartnerAPI service
conn, err := grpc.Dial("partner.api.sasha.eu:443", grpc.WithTransportCredentials(creds))
if err != nil {
log.Fatalf("Failed to connect: %v", err)
}
defer conn.Close()
// Create PartnerAPI service client
client := partnerapiv1.NewPartnerAPIClient(conn)
// Authenticate with client credentials
req := &partnerapiv1.AuthenticateWithClientCredentialRequest{
ClientId: clientID,
ClientSecret: clientSecret,
}
resp, err := client.AuthenticateWithClientCredential(context.Background(), req)
if err != nil {
log.Fatalf("Authentication failed: %v", err)
}
log.Printf("Access token: %s", resp.AccessToken)
log.Printf("Expires in: %d seconds", resp.ExpiresIn)
return resp.AccessToken
}
Python Example - Authentication
import grpc
from sasha.partner_api.v1 import partner_api_pb2, partner_api_pb2_grpc
def authenticate(client_id: str, client_secret: str) -> str:
# Create secure channel
credentials = grpc.ssl_channel_credentials()
channel = grpc.secure_channel('partner.api.sasha.eu:443', credentials)
# Create PartnerAPI service stub
stub = partner_api_pb2_grpc.PartnerAPIStub(channel)
# Create authentication request
request = partner_api_pb2.AuthenticateWithClientCredentialRequest(
client_id=client_id,
client_secret=client_secret
)
# Call the service
response = stub.AuthenticateWithClientCredential(request)
print(f"Access token: {response.access_token}")
print(f"Expires in: {response.expires_in} seconds")
return response.access_token
Go Example - Embed Signature from URL
func embedSignatureFromURL(client partnerapiv1.PartnerAPIClient, accessToken, imageURL string) {
// Create context with authorization
ctx := metadata.AppendToOutgoingContext(context.Background(),
"authorization", "Bearer "+accessToken)
// Create embed request
req := &partnerapiv1.EmbedSignatureFromURLRequest{
MediaUrl: imageURL,
MediaMimeType: "image/jpeg",
}
// Call the service
resp, err := client.EmbedSignatureFromURL(ctx, req)
if err != nil {
log.Fatalf("Embed signature failed: %v", err)
}
log.Printf("Job created: %s", resp.Job.JobId)
log.Printf("Job status: %s", resp.Job.Status)
}
Python Example - Embed Signature from URL
def embed_signature_from_url(stub, access_token: str, image_url: str):
# Create metadata with authorization
metadata = [('authorization', f'Bearer {access_token}')]
# Create embed request
request = signature_api_pb2.EmbedSignatureFromURLRequest(
media_url=image_url,
media_mime_type='image/jpeg'
)
# Call the service
response = stub.EmbedSignatureFromURL(request, metadata=metadata)
print(f"Job created: {response.job.job_id}")
print(f"Job status: {response.job.status}")
return response.job.job_id
Go Example - Complete Flow
package main
import (
"context"
"crypto/tls"
"log"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/metadata"
partnerapiv1 "github.com/Sasha-ApS/apis/sasha/partner-api/v1"
)
func main() {
// Create TLS credentials
creds := credentials.NewTLS(&tls.Config{})
// Connect to the PartnerAPI service
conn, err := grpc.Dial("partner.api.sasha.eu:443", grpc.WithTransportCredentials(creds))
if err != nil {
log.Fatalf("Failed to connect: %v", err)
}
defer conn.Close()
// Create PartnerAPI service client
client := partnerapiv1.NewPartnerAPIClient(conn)
// Step 1: Authenticate
authReq := &partnerapiv1.AuthenticateWithClientCredentialRequest{
ClientId: "your-client-id",
ClientSecret: "your-client-secret",
}
authResp, err := client.AuthenticateWithClientCredential(context.Background(), authReq)
if err != nil {
log.Fatalf("Authentication failed: %v", err)
}
accessToken := authResp.AccessToken
log.Printf("Authenticated successfully. Token expires in: %d seconds", authResp.ExpiresIn)
// Step 2: Embed signature
ctx := metadata.AppendToOutgoingContext(context.Background(),
"authorization", "Bearer "+accessToken)
embedReq := &partnerapiv1.EmbedSignatureFromURLRequest{
MediaUrl: "https://example.com/image.jpg",
MediaMimeType: "image/jpeg",
}
embedResp, err := client.EmbedSignatureFromURL(ctx, embedReq)
if err != nil {
log.Fatalf("Embed signature failed: %v", err)
}
jobID := embedResp.Job.JobId
log.Printf("Job created: %s", jobID)
// Step 3: Watch job for completion
watchReq := &partnerapiv1.WatchJobRequest{
JobId: jobID,
}
stream, err := client.WatchJob(ctx, watchReq)
if err != nil {
log.Fatalf("Failed to watch job: %v", err)
}
for {
resp, err := stream.Recv()
if err != nil {
log.Fatalf("Stream error: %v", err)
}
job := resp.Job
log.Printf("Job %s status: %s", job.JobId, job.Status)
if job.Status == "COMPLETED" {
log.Printf("Job completed successfully!")
if job.OutputUrl != "" {
log.Printf("Protected image URL: %s", job.OutputUrl)
}
break
} else if job.Status == "FAILED" {
log.Printf("Job failed: %s", job.ErrorMessage)
break
}
}
}
Best Practices
Authentication
- Store access tokens securely and reuse them until they expire
- Implement token refresh logic before expiration to avoid service interruptions
- Handle authentication errors gracefully with retry logic
Streaming Large Files
- Use
EmbedSignatureFromData
andLookupSignatureFromData
for large images - Stream data in reasonable chunks (1MB recommended)
Job Management
- Use
WatchJob
for real-time updates instead of pollingGetJob
- Implement proper error handling for stream disconnections
- Store job IDs for later reference and status checking
Performance Optimization
- Reuse gRPC connections when possible
- Use connection pooling for high-throughput applications
- Consider parallel processing for multiple images
For more information about authentication concepts and signature operations, see our Authentication Guide and Signature Concepts.