DevOps Guide
Automate certificate pin management in production environments using the TrustPin CLI. This guide covers the complete workflow for managing certificate pins from installation to automated CI/CD deployments.
Overview
TrustPin uses a simple process for certificate pin updates:
- Clean up (recommended): Remove already-expired pins with
projects cleanupbefore staging new ones - Upsert: Add or update certificate pins in your project configuration
- Sign: Cryptographically sign the configuration and publish it to the CDN
This separation allows you to:
- Stage multiple pin changes before publishing
- Review configuration changes before they go live
- Maintain an audit trail of all pin modifications
- Use different security controls for staging vs. publishing
Quick Start
1. Install the CLI
Homebrew (Recommended):
brew tap trustpin-cloud/trustpin-cli
brew trust trustpin-cloud/trustpin-cli # one-time approval required by Homebrew 6.0+
brew install trustpin-cliDirect Download (Linux x64 for CI/CD):
curl -L https://github.com/trustpin-cloud/homebrew-trustpin/releases/latest/download/trustpin-cli-linux-x64 -o trustpin-cli
chmod +x trustpin-cli
sudo mv trustpin-cli /usr/local/bin/See Installation for platform-specific instructions.
2. Configure Authentication
Set the TRUSTPIN_API_TOKEN environment variable:
export TRUSTPIN_API_TOKEN="tp_your_token_here"Get your Personal Access Token from TrustPin Console .
The CLI automatically uses this environment variable when available.
3. Get Your Project IDs
# List all projects
trustpin-cli projects list
# Get IDs in JSON format
trustpin-cli projects list --output json | jq -r '.data.projects[] | "\(.organization_id) \(.id) \(.name)"'Note your Organization ID and Project ID for subsequent commands.
Certificate Pin Management Workflow
Step 1: Extract Pin from Certificate
Extract the SPKI SHA-256 fingerprint and expiration date from your certificate:
#!/bin/bash
CERT_FILE="cert.pem"
# Extract SPKI SHA-256 (OWASP recommended - pins the public key)
SPKI_SHA256=$(openssl x509 -in "$CERT_FILE" -pubkey -noout | \
openssl pkey -pubin -outform der | \
openssl dgst -sha256 -binary | \
base64)
# Extract expiration date (portable across Linux/macOS)
EXPIRES_EPOCH=$(openssl x509 -in "$CERT_FILE" -noout -enddate | \
cut -d= -f2 | \
xargs -I{} date -jf "%b %d %H:%M:%S %Y %Z" "{}" +%s 2>/dev/null || \
date -d "$(openssl x509 -in "$CERT_FILE" -noout -enddate | cut -d= -f2)" +%s)
EXPIRES=$(date -u -r "$EXPIRES_EPOCH" +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || \
date -u -d "@$EXPIRES_EPOCH" +%Y-%m-%dT%H:%M:%SZ)
echo "SPKI SHA-256: $SPKI_SHA256"
echo "Expires: $EXPIRES"Example output:
SPKI SHA-256: oxVSdCLgthL3M5Vnnzepq8WWlkUfRPYkpjLpm+wn+1o=
Expires: 2026-04-13T08:37:02ZTip: You can skip the manual
opensslpipeline entirely and let the CLI discover pins for you withdomains certificates.
Step 2: Clean Up Expired Pins (recommended)
Before adding the new pin, drop any pins that have already expired. This keeps the stored configuration free of dead pins so you only ever sign live ones. Cleanup updates the stored configuration only — it doesn’t publish, so it’s safe to run ahead of the upsert/sign steps below.
# Preview first (optional)
trustpin-cli projects cleanup <organization-id> <project-id> --dry-run
# Remove expired pins
trustpin-cli projects cleanup <organization-id> <project-id>Cleanup is idempotent — if there’s nothing expired, it’s a no-op and the configuration version is left unchanged.
Step 3: Upsert the Pin
Add or update the certificate pin in your project configuration:
trustpin-cli projects upsert \
<organization-id> \
<project-id> \
--domain api.example.com \
--pin spki-sha256:$SPKI_SHA256 \
--expires $EXPIRES \
--output jsonExample output:
{
"status": "success",
"operation": "upsert",
"domain": "api.example.com",
"action": "added",
"pin": {
"type": "spki-sha256",
"value": "oxVSdCLgthL3M5Vnnzepq8WWlkUfRPYkpjLpm+wn+1o=",
"expires_at": "2026-04-13T08:37:02Z"
},
"config_version": {
"before": 2,
"after": 3
}
}Note: The configuration version increments, but changes are not yet published.
Step 4: Sign and Publish
Sign the configuration and publish it to the CDN:
Cloud-Managed Keys:
trustpin-cli projects sign \
<organization-id> \
<project-id> \
--password <master-password>Bring Your Own Key (BYOK):
trustpin-cli projects sign \
<organization-id> \
<project-id> \
--private-key path/to/private-key.pemExample output:
[1/5] Getting project information
Project: Production API (Managed CDN with Cloud Keys)
[2/5] Loading project configuration
Configuration loaded with 3 domains
[3/5] Preparing private key
Using cloud-managed private key
[4/5] Creating and signing JWT
JWT signed successfully
[5/5] Uploading signed configuration
Configuration published successfully!The signed configuration is now live and available to your mobile apps.
Complete Workflow Script
This script automates the entire process with error handling:
#!/bin/bash
set -e
ORG_ID="fba3418e-b5ae-b273-4bab-6da6ae07ba99"
PROJECT_ID="9caaea0a-80ea-013e-4e7b-cee6bfb52b36"
DOMAIN="api.example.com"
CERT_FILE="certs/api-cert.pem"
echo "═══ Step 1: Extract Pin from Certificate ═══"
SPKI_SHA256=$(openssl x509 -in "$CERT_FILE" -pubkey -noout | \
openssl pkey -pubin -outform der | \
openssl dgst -sha256 -binary | \
base64)
# Extract expiration date (portable across Linux/macOS)
EXPIRES_EPOCH=$(openssl x509 -in "$CERT_FILE" -noout -enddate | \
cut -d= -f2 | \
xargs -I{} date -jf "%b %d %H:%M:%S %Y %Z" "{}" +%s 2>/dev/null || \
date -d "$(openssl x509 -in "$CERT_FILE" -noout -enddate | cut -d= -f2)" +%s)
EXPIRES=$(date -u -r "$EXPIRES_EPOCH" +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || \
date -u -d "@$EXPIRES_EPOCH" +%Y-%m-%dT%H:%M:%SZ)
echo " SPKI SHA-256: $SPKI_SHA256"
echo " Expires: $EXPIRES"
echo ""
echo "═══ Step 2: Clean Up Expired Pins ═══"
trustpin-cli projects cleanup "$ORG_ID" "$PROJECT_ID"
echo ""
echo "═══ Step 3: Upsert Certificate Pin ═══"
RESULT=$(trustpin-cli projects upsert "$ORG_ID" "$PROJECT_ID" \
--domain "$DOMAIN" \
--pin "spki-sha256:$SPKI_SHA256" \
--expires "$EXPIRES" \
--output json)
echo "$RESULT" | jq '.'
# Verify success
STATUS=$(echo "$RESULT" | jq -r '.status')
if [ "$STATUS" != "success" ]; then
echo "❌ Failed to upsert pin"
exit 1
fi
ACTION=$(echo "$RESULT" | jq -r '.action')
echo "✅ Pin ${ACTION} successfully"
echo ""
echo "═══ Step 4: Sign and Publish Configuration ═══"
trustpin-cli projects sign "$ORG_ID" "$PROJECT_ID" \
--password "$MASTER_PASSWORD"
echo ""
echo "✅ Certificate pin updated and published!"Advanced Use Cases
Verifying Published Configuration
After signing and publishing, verify the configuration is correctly deployed to the CDN:
# Verify JWS signature using public key from CDN
trustpin-cli projects jws $ORG_ID $PROJECT_ID --verify
# Decode and inspect published configuration
trustpin-cli projects jws $ORG_ID $PROJECT_ID --decode
# Save published JWS for archival/compliance
trustpin-cli projects jws $ORG_ID $PROJECT_ID \
--output-file "archive/config-$(date +%Y%m%d-%H%M%S).jws"Why validation matters:
- Confirms the
signcommand successfully published to CDN - Catches CDN propagation delays or failures
- Provides audit trail of published configurations
- Enables comparison between database and CDN state
Example validation script:
#!/bin/bash
set -e
ORG_ID="fba3418e-b5ae-b273-4bab-6da6ae07ba99"
PROJECT_ID="9caaea0a-80ea-013e-4e7b-cee6bfb52b36"
echo "Verifying published configuration..."
# Check if JWS is published
JWS=$(trustpin-cli projects jws $ORG_ID $PROJECT_ID 2>/dev/null)
if [ -z "$JWS" ]; then
echo "❌ No JWS published on CDN"
exit 1
fi
# Verify signature
if ! trustpin-cli projects jws $ORG_ID $PROJECT_ID --verify &>/dev/null; then
echo "❌ JWS signature verification failed"
exit 5
fi
echo "✅ JWS signature valid"
# Check version alignment
trustpin-cli projects jws $ORG_ID $PROJECT_ID --decode | grep -E "(Current DB Version|Published Version)"
echo "✅ Published configuration validated"Self-Hosted CDN Deployment
For organizations hosting JWS configurations on their own infrastructure:
Workflow:
- Use TrustPin to manage and sign configurations (upsert + sign)
- Download the signed JWS from TrustPin CDN
- Deploy to your own CDN/servers
- Configure mobile SDKs to fetch from your infrastructure
Deployment script:
#!/bin/bash
set -e
ORG_ID="fba3418e-b5ae-b273-4bab-6da6ae07ba99"
PROJECT_ID="9caaea0a-80ea-013e-4e7b-cee6bfb52b36"
CDN_PATH="/var/www/cdn/trustpin"
echo "═══ Step 1: Upsert Certificate Pins ═══"
# Update your pins...
trustpin-cli projects upsert $ORG_ID $PROJECT_ID \
--domain api.example.com \
--pin spki-sha256:$SPKI_SHA256 \
--expires $EXPIRES
echo ""
echo "═══ Step 2: Sign Configuration ═══"
trustpin-cli projects sign $ORG_ID $PROJECT_ID \
--password "$MASTER_PASSWORD"
echo ""
echo "═══ Step 3: Download Signed JWS ═══"
trustpin-cli projects jws $ORG_ID $PROJECT_ID \
--output-file "$CDN_PATH/config.jws"
echo ""
echo "═══ Step 4: Verify JWS Integrity ═══"
if ! trustpin-cli projects jws $ORG_ID $PROJECT_ID --verify; then
echo "❌ JWS verification failed - aborting deployment"
exit 5
fi
echo ""
echo "═══ Step 5: Deploy to CDN ═══"
# Example: Upload to S3, CloudFront, or your CDN
aws s3 cp "$CDN_PATH/config.jws" s3://your-bucket/trustpin/config.jws \
--cache-control "public, max-age=3600" \
--metadata "version=$(date +%s)"
# Invalidate CDN cache if needed
aws cloudfront create-invalidation \
--distribution-id YOUR_DISTRIBUTION_ID \
--paths "/trustpin/config.jws"
echo "✅ JWS deployed to self-hosted CDN"Benefits:
- Full control over CDN infrastructure and caching
- Compliance with data residency requirements
- Integration with existing CDN/monitoring systems
- Custom cache invalidation strategies
AWS ACM: Automated Certificate Pinning on Renewal
When AWS Certificate Manager (ACM) automatically renews a certificate, this workflow detects the renewal via EventBridge, extracts the new certificate’s pins, upserts them into TrustPin, and signs/publishes the configuration using a BYOK private key stored in AWS Secrets Manager.
Architecture
ACM Certificate Renewal
│
▼
EventBridge Rule ──▶ Lambda Function
(ACM Action event) │
├── 1. Get certificate from ACM (aws sdk)
├── 2. Extract SPKI pin (openssl)
├── 3. Clean up expired pins (trustpin-cli projects cleanup)
├── 4. Upsert pin (trustpin-cli projects upsert)
├── 5. Fetch BYOK key from Secrets Manager
└── 6. Sign & publish (trustpin-cli projects sign --private-key)Prerequisites:
- ACM certificate with automatic renewal enabled
- TrustPin project configured with BYOK (Bring Your Own Key)
- BYOK private key stored in AWS Secrets Manager
- TrustPin API token stored in AWS Secrets Manager
SAM Template (template.yaml)
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
Automated TrustPin certificate pin updates on ACM certificate renewal.
Detects ACM renewals via EventBridge, extracts pins, and publishes
signed configuration using BYOK.
Parameters:
TrustPinOrgId:
Type: String
Description: TrustPin organization ID
TrustPinProjectId:
Type: String
Description: TrustPin project ID
AcmCertificateArn:
Type: String
Description: ARN of the ACM certificate to monitor
Domain:
Type: String
Description: Domain name associated with the certificate (e.g., api.example.com)
TrustPinApiTokenSecretArn:
Type: String
Description: ARN of the Secrets Manager secret containing the TrustPin API token
ByokPrivateKeySecretArn:
Type: String
Description: ARN of the Secrets Manager secret containing the BYOK private key (PEM)
Resources:
# EventBridge rule: triggers on ACM certificate renewal
AcmRenewalRule:
Type: AWS::Events::Rule
Properties:
Description: Triggers on ACM certificate renewal completion
EventPattern:
source:
- aws.acm
detail-type:
- "ACM Certificate Action"
detail:
ActionType:
- RENEWAL
CertificateArn:
- !Ref AcmCertificateArn
State: ENABLED
Targets:
- Id: TrustPinPinUpdater
Arn: !GetAtt PinUpdaterFunction.Arn
# Permission for EventBridge to invoke the Lambda
AcmRenewalPermission:
Type: AWS::Lambda::Permission
Properties:
FunctionName: !Ref PinUpdaterFunction
Action: lambda:InvokeFunction
Principal: events.amazonaws.com
SourceArn: !GetAtt AcmRenewalRule.Arn
# Lambda function: updates TrustPin pins on renewal
PinUpdaterFunction:
Type: AWS::Serverless::Function
Properties:
FunctionName: trustpin-acm-pin-updater
Runtime: provided.al2023
Handler: bootstrap
Timeout: 120
MemorySize: 256
Architectures:
- x86_64
Environment:
Variables:
TRUSTPIN_ORG_ID: !Ref TrustPinOrgId
TRUSTPIN_PROJECT_ID: !Ref TrustPinProjectId
DOMAIN: !Ref Domain
ACM_CERTIFICATE_ARN: !Ref AcmCertificateArn
API_TOKEN_SECRET_ARN: !Ref TrustPinApiTokenSecretArn
BYOK_KEY_SECRET_ARN: !Ref ByokPrivateKeySecretArn
Policies:
- Version: '2012-10-17'
Statement:
# Read the ACM certificate
- Effect: Allow
Action:
- acm:GetCertificate
- acm:DescribeCertificate
Resource: !Ref AcmCertificateArn
# Read secrets (API token + BYOK private key)
- Effect: Allow
Action:
- secretsmanager:GetSecretValue
Resource:
- !Ref TrustPinApiTokenSecretArn
- !Ref ByokPrivateKeySecretArnLambda Handler Script (handler.sh)
The Lambda uses a custom runtime (provided.al2023) to run a shell script with the TrustPin CLI binary bundled in the deployment package.
#!/bin/bash
set -euo pipefail
# --- Custom Lambda Runtime Loop ---
# Polls the Lambda Runtime API for invocation events and processes them.
RUNTIME_API="http://${AWS_LAMBDA_RUNTIME_API}/2018-06-01/runtime"
while true; do
# Get next event
HEADERS=$(mktemp)
EVENT=$(curl -sS -D "$HEADERS" "${RUNTIME_API}/invocation/next")
REQUEST_ID=$(grep -i "Lambda-Runtime-Aws-Request-Id" "$HEADERS" | tr -d '\r' | cut -d' ' -f2)
# Process the event
RESPONSE=$(process_event "$EVENT" 2>&1) && STATUS="success" || STATUS="error"
if [ "$STATUS" = "success" ]; then
curl -sS -X POST "${RUNTIME_API}/invocation/${REQUEST_ID}/response" \
-d "{\"status\": \"success\", \"message\": \"$RESPONSE\"}"
else
curl -sS -X POST "${RUNTIME_API}/invocation/${REQUEST_ID}/error" \
-d "{\"errorType\": \"PinUpdateError\", \"errorMessage\": \"$RESPONSE\"}"
fi
rm -f "$HEADERS"
done
process_event() {
local EVENT="$1"
local CERT_ARN="$ACM_CERTIFICATE_ARN"
echo "Processing ACM renewal for certificate: $CERT_ARN"
# Step 1: Retrieve TrustPin API token from Secrets Manager
export TRUSTPIN_API_TOKEN=$(aws secretsmanager get-secret-value \
--secret-id "$API_TOKEN_SECRET_ARN" \
--query 'SecretString' --output text)
# Step 2: Retrieve BYOK private key from Secrets Manager
PRIVATE_KEY_FILE=$(mktemp /tmp/byok-key-XXXXXX.pem)
aws secretsmanager get-secret-value \
--secret-id "$BYOK_KEY_SECRET_ARN" \
--query 'SecretString' --output text > "$PRIVATE_KEY_FILE"
chmod 600 "$PRIVATE_KEY_FILE"
# Step 3: Export the renewed certificate from ACM
CERT_FILE=$(mktemp /tmp/cert-XXXXXX.pem)
aws acm get-certificate \
--certificate-arn "$CERT_ARN" \
--query 'Certificate' --output text > "$CERT_FILE"
# Step 4: Extract SPKI SHA-256 pin and expiration from the certificate
SPKI_SHA256=$(openssl x509 -in "$CERT_FILE" -pubkey -noout | \
openssl pkey -pubin -outform der | \
openssl dgst -sha256 -binary | \
base64)
# Convert expiration to ISO 8601 format (portable across Linux/macOS)
EXPIRES_EPOCH=$(openssl x509 -in "$CERT_FILE" -noout -enddate | \
cut -d= -f2 | \
xargs -I{} date -jf "%b %d %H:%M:%S %Y %Z" "{}" +%s 2>/dev/null || \
date -d "$(openssl x509 -in "$CERT_FILE" -noout -enddate | cut -d= -f2)" +%s)
EXPIRES=$(date -u -r "$EXPIRES_EPOCH" +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || \
date -u -d "@$EXPIRES_EPOCH" +%Y-%m-%dT%H:%M:%SZ)
echo "Extracted SPKI SHA-256: ${SPKI_SHA256:0:20}..."
echo "Certificate expires: $EXPIRES"
# Step 5: Remove already-expired pins before adding the renewed one
./trustpin-cli projects cleanup \
"$TRUSTPIN_ORG_ID" "$TRUSTPIN_PROJECT_ID"
# Step 6: Upsert the pin into TrustPin
./trustpin-cli projects upsert \
"$TRUSTPIN_ORG_ID" "$TRUSTPIN_PROJECT_ID" \
--domain "$DOMAIN" \
--pin "spki-sha256:${SPKI_SHA256}" \
--expires "$EXPIRES" \
--output json
# Step 7: Sign and publish with BYOK private key
./trustpin-cli projects sign \
"$TRUSTPIN_ORG_ID" "$TRUSTPIN_PROJECT_ID" \
--private-key "$PRIVATE_KEY_FILE"
# Cleanup sensitive files
rm -f "$PRIVATE_KEY_FILE" "$CERT_FILE"
echo "Pin updated and configuration published for $DOMAIN"
}Deployment
# 1. Create the deployment package
mkdir -p build
cp handler.sh build/bootstrap
chmod +x build/bootstrap
# Download the TrustPin CLI Linux binary into the package
curl -L https://github.com/trustpin-cloud/homebrew-trustpin/releases/latest/download/trustpin-cli-linux-x64 \
-o build/trustpin-cli
chmod +x build/trustpin-cli
cd build && zip -r ../function.zip . && cd ..
# 2. Store secrets in AWS Secrets Manager
aws secretsmanager create-secret \
--name trustpin/api-token \
--secret-string "tp_your_token_here"
aws secretsmanager create-secret \
--name trustpin/byok-private-key \
--secret-string file://private-key.pem
# 3. Deploy with SAM
sam deploy \
--template-file template.yaml \
--stack-name trustpin-acm-auto-pin \
--capabilities CAPABILITY_IAM \
--parameter-overrides \
TrustPinOrgId=fba3418e-b5ae-b273-4bab-6da6ae07ba99 \
TrustPinProjectId=9caaea0a-80ea-013e-4e7b-cee6bfb52b36 \
AcmCertificateArn=arn:aws:acm:us-east-1:123456789012:certificate/abc-def-123 \
Domain=api.example.com \
TrustPinApiTokenSecretArn=arn:aws:secretsmanager:us-east-1:123456789012:secret:trustpin/api-token-AbCdEf \
ByokPrivateKeySecretArn=arn:aws:secretsmanager:us-east-1:123456789012:secret:trustpin/byok-private-key-GhIjKl
# 4. Test with a manual invocation
aws lambda invoke \
--function-name trustpin-acm-pin-updater \
--payload '{"detail":{"ActionType":"RENEWAL"}}' \
response.json && cat response.jsonWhat happens on ACM renewal:
- ACM renews the certificate (automatic, managed by AWS)
- EventBridge detects the
ACM Certificate Actionevent withActionType: RENEWAL - Lambda is invoked automatically
- Lambda exports the renewed certificate from ACM via the AWS SDK
- Extracts the SPKI SHA-256 pin and expiration using
openssl - Removes already-expired pins with
trustpin-cli projects cleanup - Upserts the pin into TrustPin with
trustpin-cli projects upsert - Signs and publishes the configuration using the BYOK private key with
trustpin-cli projects sign --private-key - Mobile apps fetch the updated configuration from the CDN on their next refresh
Critical considerations:
⚠️ ACM Key Rotation: ACM generates new key pairs on every renewal (key reuse is not supported). This means the SPKI pin will change on every renewal. To prevent mobile app pinning failures:
- Maintain dual pins: Keep both old and new certificate pins active during the transition period
- Monitor app refresh cycles: Ensure sufficient overlap time for all active app instances to fetch the new configuration
- Set appropriate overlap windows:
- Recommended: 7-14 days overlap between old and new pins for mobile apps
- Minimum: Consider your app’s usage patterns—if users don’t open the app for weeks, a longer overlap is critical
- Automated environments: For server-to-server or CI/CD deployments with guaranteed refresh cycles, shorter overlaps (hours to days) may be acceptable
Recommended dual-pin strategy:
# Before renewal (old cert expires in 30 days)
# Old pin: expires 2026-03-01
# Status: Active
# After renewal detected (new cert issued)
# Old pin: expires 2026-03-01 (keep active)
# New pin: expires 2027-03-01 (newly added)
# Status: Both active for 30 days
# After old cert expires (2026-03-01)
# New pin: expires 2027-03-01
# Status: Old pin automatically ignored by SDKsThis ensures zero downtime during certificate renewals and accounts for mobile apps that haven’t fetched the updated configuration yet.
Troubleshooting
Authentication Issues
Problem: HTTP 401 Unauthorized
Solutions:
# Verify token is set correctly
echo $TRUSTPIN_API_TOKEN
# Test authentication
trustpin-cli projects list --output json
# Reconfigure CLI
trustpin-cli configureIn CI/CD:
- Verify
TRUSTPIN_API_TOKENsecret is set correctly - Ensure token hasn’t expired (create new token if needed)
- Check token has access to the organization/project
Validation Errors
Problem: Invalid pin format or type
❌ Validation failed: invalid pin type: sha255
Valid types: sha256, sha512, spki-sha256, spki-sha512Solution: Fix typo in pin type (e.g., sha255 → sha256)
Problem: Invalid Base64 encoding
❌ Validation failed: invalid base64 encoding in pin valueSolution: Ensure hash is Base64-encoded, not hex:
# Correct: Base64 encoding
openssl dgst -sha256 -binary | base64
# Incorrect: hex encoding
openssl dgst -sha256Problem: Wrong hash length
❌ Validation failed: invalid sha256 hash length: expected 32 bytes, got 16 bytesSolution: Verify you’re hashing the correct data:
# For SPKI pins (correct)
openssl x509 -in cert.pem -pubkey -noout | \
openssl pkey -pubin -outform der | \
openssl dgst -sha256 -binary | base64
# For certificate pins (correct)
openssl x509 -in cert.pem -outform der | \
openssl dgst -sha256 -binary | base64Private Key Issues (BYOK)
Problem: Failed to decrypt private key
❌ Failed to decrypt private key: cipher: message authentication failedSolutions:
- Verify master password is correct for cloud-managed keys
- Verify private key password is correct for BYOK
- Check private key file is in PEM format:
file private-key.pem
Problem: Private key format errors in CI/CD
# Verify key is properly decoded
file private-key.pem
# Should show: "PEM RSA private key" or similar
# Check file permissions
ls -la private-key.pem
# Should be 600 or 400Network Issues
Problem: Connection timeout or network errors
Solutions:
# Test API connectivity
curl -H "Authorization: Bearer $TRUSTPIN_API_TOKEN" \
https://api.trustpin.cloud/users
# Check API base URL
echo $TRUSTPIN_API_BASE_URL
# Use verbose mode for debugging
trustpin-cli projects sign <org-id> <project-id> --verboseUpsert Operation Issues
Problem: Upsert succeeds but nothing changes
Reason: The pin already exists with the same expiration (idempotent operation)
Response:
{
"status": "success",
"operation": "upsert",
"action": "no_change",
"message": "Pin already exists with same expiration"
}Solution: This is expected behavior. No action needed.
Exit Codes
The CLI uses standard exit codes for automation:
| Code | Meaning | Example |
|---|---|---|
| 0 | Success or no-op | Pin added/updated, or already exists |
| 1 | Configuration error | CLI not configured |
| 2 | API error | 401, 404, 500 HTTP errors |
| 3 | Authentication error | Invalid or revoked token |
| 4 | Validation error | Invalid domain, pin format, or type |
| 5 | Resource not found | Wrong organization or project ID |
| 6 | Key error | Bad private key |
| 7 | File error | Cannot read/write a file |
| 8 | Cryptography error | Signature verification or decryption failed |
| 9 | Cancelled by user | Interactive prompt aborted |
| 10 | Permission error | Insufficient access rights |
| 99 | Unexpected error | Network timeout, unknown failure |
Use in scripts:
if ! trustpin-cli projects upsert ... --output json > result.json; then
EXIT_CODE=$?
echo "❌ Upsert failed with exit code $EXIT_CODE"
cat result.json
exit $EXIT_CODE
fiGetting Help
View command help:
trustpin-cli --help
trustpin-cli projects --help
trustpin-cli projects upsert --help
trustpin-cli projects sign --helpEnable verbose output:
trustpin-cli projects upsert ... --verbose
trustpin-cli projects sign ... --verboseCheck CLI version:
trustpin-cli --versionFor additional support, contact support@trustpin.cloud or visit the TrustPin Console .