Skip to main content

Go SDK

The Qpher Go SDK provides an idiomatic Go interface to the Qpher REST API. All cryptographic operations execute server-side. The SDK handles authentication, base64 encoding, retries, and structured error mapping. Every method accepts a context.Context for cancellation and deadline control.

Installation​

go get github.com/qpher/qpher-go

Requirements: Go 1.21 or later. No CGo dependencies.

Client Setup​

package main

import (
"context"
"fmt"
"os"

qpher "github.com/qpher/qpher-go"
)

func main() {
client, err := qpher.NewClient("qph_your_key_here", nil)
if err != nil {
log.Fatal(err)
}

// Or with custom options:
client, err = qpher.NewClient(os.Getenv("QPHER_API_KEY"), &qpher.ClientOptions{
BaseURL: "https://api.qpher.ai", // default
Timeout: 30, // seconds, default: 30
MaxRetries: 3, // default: 3
})
if err != nil {
log.Fatal(err)
}
}
FieldTypeDefaultDescription
apiKeystringrequiredYour Qpher API key (starts with qph_)
BaseURLstringhttps://api.qpher.aiAPI base URL
Timeoutint30Request timeout in seconds
MaxRetriesint3Retries for transient errors (429, 502, 503, 504)

Pass nil as the second argument to NewClient to use all defaults.

Protect your API key

Never hard-code API keys in source files. Load them from environment variables: qpher.NewClient(os.Getenv("QPHER_API_KEY"), nil)


KEM Encryption and Decryption​

Qpher uses Kyber768 (ML-KEM-768) for key encapsulation. The SDK accepts []byte for binary data and returns []byte for ciphertext. Base64 encoding on the wire is handled automatically.

Encrypt​

ctx := context.Background()

result, err := client.KEM.Encrypt(ctx, &qpher.EncryptInput{
Plaintext: []byte("sensitive data to protect"),
KeyVersion: 1,
})
if err != nil {
log.Fatal(err)
}

fmt.Println(result.Ciphertext) // []byte — the encrypted payload
fmt.Println(result.KeyVersion) // int — the key version used
fmt.Println(result.RequestID) // string — UUID for tracing
FieldTypeRequiredDescription
Plaintext[]byteYesThe data to encrypt
KeyVersionintYesKey version to use (must be active)
ModestringNo"standard" (default) or "deterministic"
Salt[]byteNoRequired when Mode is "deterministic"

Returns: (*EncryptResult, error) where EncryptResult has fields Ciphertext ([]byte), KeyVersion (int), and RequestID (string).

Decrypt​

result, err := client.KEM.Decrypt(ctx, &qpher.DecryptInput{
Ciphertext: encryptedCiphertext,
KeyVersion: 1,
})
if err != nil {
log.Fatal(err)
}

fmt.Println(result.Plaintext) // []byte — the original data
fmt.Println(result.RequestID) // string
FieldTypeRequiredDescription
Ciphertext[]byteYesThe encrypted payload to decrypt
KeyVersionintYesKey version used during encryption (active or retired)

Returns: (*DecryptResult, error) where DecryptResult has fields Plaintext ([]byte) and RequestID (string).

Key version is always required

Qpher does not support implicit "latest" key selection. You must specify the KeyVersion for every operation to ensure auditability and prevent silent key-mismatch errors.


Digital Signatures​

Qpher uses Dilithium3 (ML-DSA-65) for post-quantum digital signatures.

Sign​

result, err := client.Signatures.Sign(ctx, &qpher.SignInput{
Message: []byte("document content to sign"),
KeyVersion: 1,
})
if err != nil {
log.Fatal(err)
}

fmt.Println(result.Signature) // []byte — the Dilithium3 signature
fmt.Println(result.KeyVersion) // int
fmt.Println(result.RequestID) // string
FieldTypeRequiredDescription
Message[]byteYesThe message to sign
KeyVersionintYesSigning key version (must be active)

Returns: (*SignResult, error) where SignResult has fields Signature ([]byte), KeyVersion (int), and RequestID (string).

Verify​

result, err := client.Signatures.Verify(ctx, &qpher.VerifyInput{
Message: []byte("document content to sign"),
Signature: signatureBytes,
KeyVersion: 1,
})
if err != nil {
log.Fatal(err)
}

fmt.Println(result.Valid) // bool — true if the signature is valid
fmt.Println(result.KeyVersion) // int
fmt.Println(result.RequestID) // string
FieldTypeRequiredDescription
Message[]byteYesThe original message
Signature[]byteYesThe signature to verify
KeyVersionintYesKey version used during signing (active or retired)

Returns: (*VerifyResult, error) where VerifyResult has fields Valid (bool), KeyVersion (int), Algorithm (string), and RequestID (string).


Key Management​

Manage PQC key lifecycle: generate, list, get, rotate, and retire keys.

Generate Key​

result, err := client.Keys.Generate(ctx, &qpher.GenerateInput{
Algorithm: "Kyber768",
})
if err != nil {
log.Fatal(err)
}

fmt.Println(result.KeyVersion) // int — the new key version
fmt.Println(result.Algorithm) // string — "Kyber768"
fmt.Println(result.Status) // string — "active"
fmt.Println(result.PublicKey) // []byte — the public key (private key stays in Qpher)
fmt.Println(result.CreatedAt) // string — ISO-8601 timestamp
fmt.Println(result.RequestID) // string — UUID for tracing
FieldTypeRequiredDescription
AlgorithmstringYes"Kyber768" or "Dilithium3"

Returns: (*GenerateResult, error) where GenerateResult has fields KeyVersion (int), Algorithm (string), Status (string), PublicKey ([]byte), CreatedAt (string), and RequestID (string).

List Keys​

result, err := client.Keys.List(ctx, &qpher.ListKeysInput{
Algorithm: "Kyber768",
})
if err != nil {
log.Fatal(err)
}

fmt.Printf("Total keys: %d\n", result.Total)
for _, key := range result.Keys {
fmt.Printf("Version %d: %s\n", key.KeyVersion, key.Status)
// Version 1: retired
// Version 2: active
}
FieldTypeRequiredDescription
AlgorithmstringNoFilter by "Kyber768" or "Dilithium3"
StatusstringNoFilter by "active", "retired", or "archived"

Returns: (*KeyListResult, error) where KeyListResult has fields Keys ([]KeyInfo), Total (int), and RequestID (string). Each KeyInfo has KeyVersion (int), Algorithm (string), Status (string), CreatedAt (string), and PublicKey ([]byte).

Get Active Key​

key, err := client.Keys.GetActive(ctx, &qpher.GetActiveInput{
Algorithm: "Kyber768",
})
if err != nil {
log.Fatal(err)
}

fmt.Println(key.KeyVersion) // int — the currently active version
fmt.Println(key.Algorithm) // string — "Kyber768"
fmt.Println(key.PublicKey) // []byte — the public key

Get Key by Version​

key, err := client.Keys.Get(ctx, &qpher.GetKeyInput{
Algorithm: "Kyber768",
KeyVersion: 1,
})
if err != nil {
log.Fatal(err)
}

fmt.Println(key.KeyVersion) // int — 1
fmt.Println(key.Status) // string — "active", "retired", or "archived"
fmt.Println(key.PublicKey) // []byte — the public key
fmt.Println(key.CreatedAt) // string — ISO-8601 timestamp

Rotate Key​

newKey, err := client.Keys.Rotate(ctx, &qpher.RotateInput{
Algorithm: "Kyber768",
})
if err != nil {
log.Fatal(err)
}

fmt.Println(newKey.KeyVersion) // int — the new active version
fmt.Println(newKey.OldKeyVersion) // int — the previous version (now retired)
fmt.Println(newKey.PublicKey) // []byte — the new public key
fmt.Println(newKey.RequestID) // string — UUID for tracing

Returns: (*RotateResult, error) where RotateResult has fields KeyVersion (int), Algorithm (string), PublicKey ([]byte), OldKeyVersion (int), and RequestID (string).

Retire Key​

result, err := client.Keys.Retire(ctx, &qpher.RetireInput{
Algorithm: "Kyber768",
KeyVersion: 1,
})
if err != nil {
log.Fatal(err)
}

fmt.Println(result.Status) // string — "retired"
// Key version 1 is now retired — decrypt/verify still work, encrypt/sign do not

Returns: (*RetireResult, error) where RetireResult has fields KeyVersion (int), Algorithm (string), Status (string), PublicKey ([]byte), CreatedAt (string), and RequestID (string).

Key lifecycle

Retired keys can still decrypt and verify, but cannot encrypt or sign. Archived keys cannot perform any operations. Plan your rotation strategy accordingly.


Error Handling​

API errors are returned as *qpher.Error, which implements the error interface. Use errors.As to extract structured fields.

import "errors"

result, err := client.KEM.Encrypt(ctx, &qpher.EncryptInput{
Plaintext: []byte("data"),
KeyVersion: 999,
})
if err != nil {
var qErr *qpher.Error
if errors.As(err, &qErr) {
fmt.Println(qErr.Code) // string — e.g., "ERR_KEM_005"
fmt.Println(qErr.Message) // string — human-readable description
fmt.Println(qErr.StatusCode) // int — HTTP status code, e.g., 404
fmt.Println(qErr.RequestID) // string — UUID for support inquiries
} else {
fmt.Println("Unexpected error:", err)
}
}

Error Type Helpers​

The *qpher.Error type provides convenience methods for checking error categories:

var qErr *qpher.Error
if errors.As(err, &qErr) {
if qErr.IsNotFoundError() {
fmt.Println("Key version does not exist")
} else if qErr.IsRateLimitError() {
fmt.Println("Rate limit exceeded — SDK already retried")
} else if qErr.IsAuthenticationError() {
fmt.Println("Invalid API key")
}
}
MethodStatus CodeDescription
IsAuthenticationError()401Invalid or missing API key
IsValidationError()400Invalid request parameters
IsNotFoundError()404Resource not found
IsForbiddenError()403Operation not allowed by policy
IsRateLimitError()429Rate limit exceeded
IsServerError()500+Internal server error
IsTimeoutError()504Request timed out
IsConnectionError()503Service unavailable

Common Error Codes​

Error CodeStatusMeaning
ERR_AUTH_001401Invalid or missing API key
ERR_RATE_001429Rate limit exceeded (auto-retried)
ERR_KEM_005404Key version not found
ERR_INVALID_001400Invalid request parameters
ERR_FORBIDDEN_001403Operation not allowed by policy
ERR_SERVICE_001503Service temporarily unavailable

Retry Behavior​

The SDK automatically retries on transient errors (HTTP 429, 502, 503, 504) with exponential backoff (0.5s, 1s, 2s, capped at 10s). After MaxRetries attempts, the final *qpher.Error is returned.


Full Round-Trip Example​

This example demonstrates a complete encrypt-then-decrypt workflow with proper error handling.

package main

import (
"context"
"fmt"
"log"
"os"
qpher "github.com/qpher/qpher-go"
)

func main() {
ctx := context.Background()
client, err := qpher.NewClient(os.Getenv("QPHER_API_KEY"), nil)
if err != nil {
log.Fatal(err)
}

// 1. Get the active Kyber768 key
activeKey, err := client.Keys.GetActive(ctx, &qpher.GetActiveInput{
Algorithm: "Kyber768",
})
if err != nil {
log.Fatal(err)
}

// 2. Encrypt sensitive data
plaintext := []byte("Patient record: John Doe, DOB 1990-01-15")
enc, err := client.KEM.Encrypt(ctx, &qpher.EncryptInput{
Plaintext: plaintext,
KeyVersion: activeKey.KeyVersion,
})
if err != nil {
log.Fatal(err)
}
fmt.Printf("Encrypted %d bytes, request: %s\n", len(enc.Ciphertext), enc.RequestID)

// 3. Decrypt to recover the original data
dec, err := client.KEM.Decrypt(ctx, &qpher.DecryptInput{
Ciphertext: enc.Ciphertext,
KeyVersion: enc.KeyVersion,
})
if err != nil {
log.Fatal(err)
}

// 4. Verify round-trip integrity
if string(dec.Plaintext) == string(plaintext) {
fmt.Println("Round-trip successful: plaintext matches")
}
}

Sign and Verify Example​

package main

import (
"context"
"fmt"
"log"
"os"
qpher "github.com/qpher/qpher-go"
)

func main() {
ctx := context.Background()
client, err := qpher.NewClient(os.Getenv("QPHER_API_KEY"), nil)
if err != nil {
log.Fatal(err)
}

// Get the active Dilithium3 signing key
signingKey, err := client.Keys.GetActive(ctx, &qpher.GetActiveInput{
Algorithm: "Dilithium3",
})
if err != nil {
log.Fatal(err)
}

// Sign a document
document := []byte("Invoice #1234: $5,000.00 due 2026-03-01")
signResult, err := client.Signatures.Sign(ctx, &qpher.SignInput{
Message: document,
KeyVersion: signingKey.KeyVersion,
})
if err != nil {
log.Fatal(err)
}
fmt.Printf("Signature: %d bytes\n", len(signResult.Signature))

// Verify the signature
verifyResult, err := client.Signatures.Verify(ctx, &qpher.VerifyInput{
Message: document,
Signature: signResult.Signature,
KeyVersion: signResult.KeyVersion,
})
if err != nil {
log.Fatal(err)
}
fmt.Printf("Valid: %t\n", verifyResult.Valid) // true
}

Context and Cancellation​

Every SDK method accepts a context.Context as its first argument. Use this for timeouts and cancellation:

// With a 5-second timeout
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()

result, err := client.KEM.Encrypt(ctx, &qpher.EncryptInput{
Plaintext: []byte("data"),
KeyVersion: 1,
})
// If the request takes more than 5 seconds, err will be a context.DeadlineExceeded

Configuration Reference​

Environment Variables​

The SDK respects these environment variables as fallbacks:

VariablePurpose
QPHER_API_KEYAPI key (used if the apiKey argument is empty)
QPHER_BASE_URLBase URL (used if BaseURL option is empty)
Security

The SDK never logs your API key or response bodies containing ciphertext. Sensitive fields are redacted in debug output.

Next Steps​

  • Python SDK — if your backend uses Python
  • Node.js SDK — if your backend uses JavaScript or TypeScript
  • REST API — for raw HTTP access from any language
  • API Reference — full OpenAPI specification