Skip to Content
CLI & DevOpsDevOps Guide

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:

  1. Clean up (recommended): Remove already-expired pins with projects cleanup before staging new ones
  2. Upsert: Add or update certificate pins in your project configuration
  3. 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-cli

Direct 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:02Z

Tip: You can skip the manual openssl pipeline entirely and let the CLI discover pins for you with domains certificates.

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 json

Example 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.pem

Example 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 sign command 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:

  1. Use TrustPin to manage and sign configurations (upsert + sign)
  2. Download the signed JWS from TrustPin CDN
  3. Deploy to your own CDN/servers
  4. 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 ByokPrivateKeySecretArn

Lambda 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.json

What happens on ACM renewal:

  1. ACM renews the certificate (automatic, managed by AWS)
  2. EventBridge detects the ACM Certificate Action event with ActionType: RENEWAL
  3. Lambda is invoked automatically
  4. Lambda exports the renewed certificate from ACM via the AWS SDK
  5. Extracts the SPKI SHA-256 pin and expiration using openssl
  6. Removes already-expired pins with trustpin-cli projects cleanup
  7. Upserts the pin into TrustPin with trustpin-cli projects upsert
  8. Signs and publishes the configuration using the BYOK private key with trustpin-cli projects sign --private-key
  9. 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:

  1. Maintain dual pins: Keep both old and new certificate pins active during the transition period
  2. Monitor app refresh cycles: Ensure sufficient overlap time for all active app instances to fetch the new configuration
  3. 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 SDKs

This 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 configure

In CI/CD:

  • Verify TRUSTPIN_API_TOKEN secret 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-sha512

Solution: Fix typo in pin type (e.g., sha255sha256)

Problem: Invalid Base64 encoding

❌ Validation failed: invalid base64 encoding in pin value

Solution: Ensure hash is Base64-encoded, not hex:

# Correct: Base64 encoding openssl dgst -sha256 -binary | base64 # Incorrect: hex encoding openssl dgst -sha256

Problem: Wrong hash length

❌ Validation failed: invalid sha256 hash length: expected 32 bytes, got 16 bytes

Solution: 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 | base64

Private Key Issues (BYOK)

Problem: Failed to decrypt private key

❌ Failed to decrypt private key: cipher: message authentication failed

Solutions:

  • 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 400

Network 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> --verbose

Upsert 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:

CodeMeaningExample
0Success or no-opPin added/updated, or already exists
1Configuration errorCLI not configured
2API error401, 404, 500 HTTP errors
3Authentication errorInvalid or revoked token
4Validation errorInvalid domain, pin format, or type
5Resource not foundWrong organization or project ID
6Key errorBad private key
7File errorCannot read/write a file
8Cryptography errorSignature verification or decryption failed
9Cancelled by userInteractive prompt aborted
10Permission errorInsufficient access rights
99Unexpected errorNetwork 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 fi

Getting Help

View command help:

trustpin-cli --help trustpin-cli projects --help trustpin-cli projects upsert --help trustpin-cli projects sign --help

Enable verbose output:

trustpin-cli projects upsert ... --verbose trustpin-cli projects sign ... --verbose

Check CLI version:

trustpin-cli --version

For additional support, contact support@trustpin.cloud or visit the TrustPin Console .