Commands
Complete reference for TrustPin CLI commands.
Command Structure
trustpin-cli
├── configure Configure API credentials
├── user
│ └── info Show current user and organizations
├── projects
│ ├── list List projects
│ ├── get Show project details
│ ├── config Get configuration as JWS payload (JSON)
│ ├── upsert Add or update a certificate pin
│ ├── cleanup Remove expired certificate pins
│ ├── refresh-certs Refresh a domain's pins from current certificates
│ ├── sign Sign and publish configuration as JWS
│ └── jws Fetch the published JWS from the CDN
└── domains
└── certificates Look up certificates and pins for a domainNew in CLI v6.0.0:
projects refresh-certsdiscovers and applies a domain’s pins for you, with noopensslpipelines and no manual--pinvalues, andprojects signgained a--dry-runrehearsal mode that verifies your key and password without publishing.
Global Flags
Available on every command:
| Flag | Description |
|---|---|
--verbose | Verbose output: config file location, API URLs, HTTP status codes |
--debug | Debug output |
--log-http | Log HTTP requests and responses |
--version | Print the CLI version |
Most commands also accept --output json for machine-readable output.
Authentication
configure
Configure the CLI with your Personal Access Token.
trustpin-cli configure [--api-base-url <url>] [--api-token <token>]Interactive prompt:
$ trustpin-cli configure
API Base URL (https://api.trustpin.cloud): https://api.trustpin.cloud
API Token: tp_your_personal_access_token_here
Configuration saved successfully!Non-interactive (CI/CD):
Pass --api-base-url and --api-token to skip the prompts, useful in pipelines where you inject the token from a secret:
trustpin-cli configure \
--api-base-url https://api.trustpin.cloud \
--api-token "$TRUSTPIN_API_TOKEN"Get your Personal Access Token:
- Visit https://app.trustpin.cloud/account/access-tokens
- Click Create Token
- Copy the token (it starts with
tp_) - Use it with
trustpin-cli configure
Token permissions:
- User-scoped: Access all projects you have permission to view
- Can expire: Create a new token if yours expires
Environment variables (CI/CD):
You can skip configure entirely by exporting credentials, ideal for CI runners and containers:
export TRUSTPIN_API_BASE_URL=https://api.trustpin.cloud
export TRUSTPIN_API_TOKEN=tp_your_token_hereOtherwise, credentials are stored in ~/.trustpin/cli/config.properties.
Reset configuration:
If you need to reconfigure or clear your credentials:
# Remove configuration
rm -rf ~/.trustpin/
# Configure again
trustpin-cli configureUser
user info
Show the authenticated user and the organizations you belong to. Useful as a first call after configure to verify your token and to discover the organization IDs needed by the projects commands.
trustpin-cli user info [--output json]Examples:
# Human-readable format (default)
$ trustpin-cli user info
═══ User Information ═══
ID: 7bb1bbbd-b7fc-4e1a-893a-026d92c6356f
Name: TrustPin
Email: info@trustpin.cloud
── Organizations ──
• Personal Organization (fba3418e-b5ae-b273-4bab-6da6ae07ba99)# JSON format for automation
$ trustpin-cli user info --output json
{
"status": "success",
"operation": "user-info",
"data": {
"user": {
"id": "7bb1bbbd-b7fc-4e1a-893a-026d92c6356f",
"name": "TrustPin",
"email": "info@trustpin.cloud"
},
"organizations": [
{
"id": "fba3418e-b5ae-b273-4bab-6da6ae07ba99",
"name": "Personal Organization"
}
]
}
}Get your organization ID with jq:
trustpin-cli user info --output json | jq -r '.data.organizations[].id'Project Management
projects list
List all projects you have access to.
trustpin-cli projects list [--output json]Examples:
# Human-readable format (default)
$ trustpin-cli projects list
═══ Projects (2) ═══
── TrustPin Mobile App ──
ID: df9964a9-66bf-4673-9743-adee9ce6213e
Type: Managed CDN with Cloud Keys
Organization: Personal Organization
Domains: 4
── API Gateway ──
ID: 99acf096-79b9-4fa6-a115-14b35b224839
Type: Managed CDN with Bring Your Own Keys
Organization: Personal Organization
Domains: 2# JSON format for automation
$ trustpin-cli projects list --output json
{
"status": "success",
"operation": "projects-list",
"data": {
"projects": [
{
"id": "df9964a9-66bf-4673-9743-adee9ce6213e",
"name": "TrustPin Mobile App",
"organization_id": "fba3418e-b5ae-b273-4bab-6da6ae07ba99",
"organization_name": "Personal Organization",
"type": "Managed CDN with Cloud Keys",
"domains_count": 4
}
]
}
}Use with jq:
# Get all project IDs
trustpin-cli projects list --output json | jq -r '.data.projects[].id'
# Get project names
trustpin-cli projects list --output json | jq -r '.data.projects[].name'projects get
Show detailed information about a single project, including its type, public key, configuration version vs. published version, and per-domain pin counts.
trustpin-cli projects get <organization-id> <project-id> [--output json]Example:
$ trustpin-cli projects get fba3418e-b5ae-b273-4bab-6da6ae07ba99 df9964a9-66bf-4673-9743-adee9ce6213e
═══ Project Details ═══
Name: TrustPin Mobile App
ID: df9964a9-66bf-4673-9743-adee9ce6213e
Type: Managed CDN with Cloud Keys
Organization: Personal Organization (fba3418e-b5ae-b273-4bab-6da6ae07ba99)
Created: 2025-07-10T16:58:11.093900Z
Updated: 2025-07-18T08:43:11.140066Z
Configuration Version: 3
Published Version: 2
Warning: Latest configuration (v3) is not yet published (v2)
Public Key: MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEyQ04pTptzBAqo8q6mhwvvwdJSnoxDpvhwir9SVNAscNTNAApbuaKaEA6Ua5zTfknWPjMONc9XJeDOb4ExUj8dQ==
Domains: 4
• Domain: trustpin.cloud
Updated: 2025-07-18T08:43:11.140066Z
Certificate Pins: 1
• Domain: api.trustpin.cloud
Updated: 2025-07-18T08:43:11.140066Z
Certificate Pins: 1The Configuration Version / Published Version lines tell you whether you have unpublished changes. Run projects sign to publish them.
projects config
Fetch the current project configuration as the JWS payload (always JSON). This is the unsigned configuration that projects sign turns into a published JWS, handy for inspecting exactly which pins and expiry dates are stored before signing.
trustpin-cli projects config <organization-id> <project-id>Example:
$ trustpin-cli projects config fba3418e-b5ae-b273-4bab-6da6ae07ba99 df9964a9-66bf-4673-9743-adee9ce6213e
{
"status": "success",
"operation": "projects-config",
"data": {
"project": {
"id": "df9964a9-66bf-4673-9743-adee9ce6213e",
"organization_id": "fba3418e-b5ae-b273-4bab-6da6ae07ba99"
},
"config": {
"version": 3,
"domains": [
{
"domain": "trustpin.cloud",
"last_updated": "2025-07-18T08:43:11Z",
"pins": [
{
"algorithm": "sha256",
"pin": "heXXXV6YUWtMPE/dUyZ6ESBpkOibPSeHseRAnp4dQJg=",
"expires_at": "2025-09-02T05:43:19Z"
}
]
}
]
}
}
}projects upsert
Add or update a certificate pin for a domain in your project configuration.
trustpin-cli projects upsert <organization-id> <project-id> \
--domain <domain-name> \
--pin <type>:<value> \
[--expires <ISO8601-datetime>] \
[--dry-run] \
[--verbose] \
[--output json]Tip: Before adding fresh pins during a certificate rotation, run
projects cleanupto drop already-expired pins. This keeps the stored configuration tidy and avoids carrying dead pins into the next signed version.
Prefer
refresh-certsfor rotations. If the certificate you want to pin is already live or visible in Certificate Transparency,projects refresh-certsdiscovers and applies its pin for you, with noopensslpipeline and no hand-copied digest. Reach forupsertwhen you need a specific digest type, a custom expiry, or a certificate TrustPin cannot observe.
Parameters:
| Parameter | Required | Description |
|---|---|---|
<organization-id> | Yes | Organization UUID |
<project-id> | Yes | Project UUID |
--domain | Yes | Domain name (e.g., api.example.com) |
--pin | Yes | Pin in format <type>:<value> |
--expires | No | Expiration date in ISO 8601 format |
--dry-run | No | Preview changes without applying them |
--verbose | No | Print extra detail about the request and the resulting change |
--output | No | Output format: human (default) or json |
Pin Types:
| Type | Description | Use Case |
|---|---|---|
sha256 | Certificate SHA-256 fingerprint | Must update pin on every certificate renewal |
sha512 | Certificate SHA-512 fingerprint | Must update pin on every certificate renewal |
spki-sha256 | SPKI SHA-256 fingerprint | Recommended - OWASP best practice |
spki-sha512 | SPKI SHA-512 fingerprint | Maximum security + OWASP best practice |
Why SPKI is recommended: SPKI (Subject Public Key Info) pins the public key, not the certificate. This is the OWASP-recommended approach for certificate pinning. When combined with TrustPin’s dynamic pin updates, it enables zero-downtime certificate rotation regardless of whether keys change during renewal.
Key Reuse (Optional): If you reuse the same private key during certificate renewal (e.g., certbot renew --reuse-key), the SPKI pin remains unchanged. However, most managed certificate services (AWS ACM, Let’s Encrypt default, Cloudflare) generate new key pairs on renewal, which changes the SPKI pin, but TrustPin’s dynamic updates handle this seamlessly.
Upsert Behavior:
The command is idempotent and follows these rules:
| Scenario | Action | Response |
|---|---|---|
| Domain exists, pin exists, same expiration | No-op | "action": "no_change" |
| Domain exists, pin exists, different expiration | Update | "action": "updated" |
| Domain exists, pin doesn’t exist | Add | "action": "added" |
| Domain doesn’t exist | Create + Add | "action": "added" |
Pin matching: Pins are matched by type + value. Expiration is not part of the match key.
Examples:
Extract and add SPKI SHA-256 pin:
# Extract SPKI SHA-256 from certificate
SPKI_SHA256=$(openssl x509 -in cert.pem -pubkey -noout | \
openssl pkey -pubin -outform der | \
openssl dgst -sha256 -binary | \
base64)
# Extract expiration date
EXPIRES=$(openssl x509 -in cert.pem -noout -enddate | \
cut -d= -f2 | \
xargs -I{} date -d "{}" -u +%Y-%m-%dT%H:%M:%SZ)
# Add pin to project
trustpin-cli projects upsert \
fba3418e-b5ae-b273-4bab-6da6ae07ba99 \
9caaea0a-80ea-013e-4e7b-cee6bfb52b36 \
--domain api.example.com \
--pin spki-sha256:$SPKI_SHA256 \
--expires $EXPIRES \
--output jsonOutput:
{
"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
}
}Dry run to preview changes:
trustpin-cli projects upsert \
fba3418e-b5ae-b273-4bab-6da6ae07ba99 \
9caaea0a-80ea-013e-4e7b-cee6bfb52b36 \
--domain api.example.com \
--pin spki-sha256:$SPKI_SHA256 \
--expires 2026-04-13T08:37:02Z \
--dry-runOutput (dry run):
Dry run mode enabled - no changes will be made
Would send PATCH request to:
/organizations/fba3418e-b5ae-b273-4bab-6da6ae07ba99/projects/9caaea0a-80ea-013e-4e7b-cee6bfb52b36/config
Request body:
{
"domain": "api.example.com",
"pin": {
"type": "spki-sha256",
"value": "oxVSdCLgthL3M5Vnnzepq8WWlkUfRPYkpjLpm+wn+1o=",
"expires_at": "2026-04-13T08:37:02Z"
}
}Update expiration date only:
# Pin already exists, just update the expiration
trustpin-cli projects upsert \
fba3418e-b5ae-b273-4bab-6da6ae07ba99 \
9caaea0a-80ea-013e-4e7b-cee6bfb52b36 \
--domain api.example.com \
--pin spki-sha256:oxVSdCLgthL3M5Vnnzepq8WWlkUfRPYkpjLpm+wn+1o= \
--expires 2027-06-01T00:00:00ZOutput:
{
"status": "success",
"operation": "upsert",
"domain": "api.example.com",
"action": "updated",
"pin": {
"type": "spki-sha256",
"value": "oxVSdCLgthL3M5Vnnzepq8WWlkUfRPYkpjLpm+wn+1o=",
"expires_at": "2027-06-01T00:00:00Z"
},
"config_version": {
"before": 3,
"after": 4
}
}Add certificate SHA-256 pin:
# Extract certificate SHA-256
CERT_SHA256=$(openssl x509 -in cert.pem -outform der | \
openssl dgst -sha256 -binary | \
base64)
trustpin-cli projects upsert \
fba3418e-b5ae-b273-4bab-6da6ae07ba99 \
9caaea0a-80ea-013e-4e7b-cee6bfb52b36 \
--domain cdn.example.com \
--pin sha256:$CERT_SHA256 \
--expires 2026-04-13T08:37:02ZImportant Notes:
- Configuration is not published yet - After upserting, you must run
projects signto publish changes to the CDN - Multiple upserts before signing - You can upsert multiple domains/pins before signing once
- Idempotent operation - Safe to run multiple times; won’t duplicate pins
- Version tracking - Each upsert increments the configuration version
- No waiting period - Upsert is instantaneous; only signing publishes to CDN
Common Use Cases:
1. Add backup pin before certificate rotation:
# Add new certificate's pin while old cert is still active
trustpin-cli projects upsert $ORG_ID $PROJECT_ID \
--domain api.example.com \
--pin spki-sha256:$NEW_SPKI \
--expires 2027-06-01T00:00:00Z
# Sign and publish
trustpin-cli projects sign $ORG_ID $PROJECT_ID --password $MASTER_PASSWORD
# Wait 24-48 hours for mobile apps to fetch new config
# Then deploy new certificate to servers2. Update multiple domains with wildcard certificate:
# Same pin for multiple domains
for DOMAIN in api.example.com cdn.example.com www.example.com; do
trustpin-cli projects upsert $ORG_ID $PROJECT_ID \
--domain $DOMAIN \
--pin spki-sha256:$SPKI \
--expires $EXPIRES
done
# Sign once after all updates
trustpin-cli projects sign $ORG_ID $PROJECT_ID --password $MASTER_PASSWORD3. Certificate renewal with same key (SPKI unchanged):
# Only expiration changes, SPKI remains the same
trustpin-cli projects upsert $ORG_ID $PROJECT_ID \
--domain api.example.com \
--pin spki-sha256:$EXISTING_SPKI \
--expires $NEW_EXPIRES
# Sign and deploy immediately (no waiting needed - pin unchanged)
trustpin-cli projects sign $ORG_ID $PROJECT_ID --password $MASTER_PASSWORDExit Codes:
| Code | Meaning | Example |
|---|---|---|
| 0 | Success or no-op | Pin added, updated, or unchanged |
| 2 | API error | HTTP 401, 404, 500 |
| 4 | Validation error | Invalid domain, pin format, or type |
| 99 | Unexpected error | Network timeout, file I/O error |
See Also:
- DevOps Guide - Complete workflow with examples
- Projects Refresh-Certs - Let the CLI discover and apply pins for you
- Projects Cleanup - Removing expired pins before upserting
- Projects Sign - Publishing configurations
projects cleanup
Remove already-expired certificate pins from every domain in a project. This is the recommended first step of a certificate-rotation workflow. Run it before projects upsert so you only ever sign a configuration that still contains live pins.
trustpin-cli projects cleanup <organization-id> <project-id> [--dry-run] [--verbose] [--output json|text]What it does:
- Removes only pins whose expiration is non-null and in the past.
- Pins without an expiry are always kept.
- Domains are never removed. A domain left with zero pins still exists.
- The configuration version is incremented only when at least one pin is removed, so the operation is idempotent.
- It updates the stored configuration only. It does not re-sign or publish. Run
projects signafterwards to publish the cleaned configuration. - It is project-wide. To prune expired pins for a single domain, typically while refreshing it, use
projects refresh-certs --remove-expiredinstead.
Parameters:
| Parameter | Required | Description |
|---|---|---|
<organization-id> | Yes | Organization UUID |
<project-id> | Yes | Project UUID |
--dry-run | No | Ask the server how many pins would be removed without changing anything |
--verbose | No | Print the per-domain breakdown of removed and kept pins |
--output | No | Output format: text (default) or json |
Examples:
Preview what would be removed (safe):
$ trustpin-cli projects cleanup \
fba3418e-b5ae-b273-4bab-6da6ae07ba99 \
361049fd-418c-f5b8-ab11-ea4686c2cea3 \
--dry-run --output json
{
"status": "success",
"operation": "projects-cleanup",
"data": {
"dry_run": true,
"removed_pins": 2,
"resource_name": "tprn::project::fba3418e-b5ae-b273-4bab-6da6ae07ba99::361049fd-418c-f5b8-ab11-ea4686c2cea3"
},
"execution_time_ms": 41
}Remove expired pins, then sign:
# 1. Drop expired pins from the stored configuration
trustpin-cli projects cleanup $ORG_ID $PROJECT_ID
# 2. Publish the cleaned configuration
trustpin-cli projects sign $ORG_ID $PROJECT_IDJSON output for automation:
$ trustpin-cli projects cleanup \
fba3418e-b5ae-b273-4bab-6da6ae07ba99 \
361049fd-418c-f5b8-ab11-ea4686c2cea3 \
--output json
{
"status": "success",
"operation": "projects-cleanup",
"data": {
"dry_run": false,
"removed_pins": 0,
"resource_name": "tprn::project::fba3418e-b5ae-b273-4bab-6da6ae07ba99::361049fd-418c-f5b8-ab11-ea4686c2cea3"
},
"execution_time_ms": 664
}The data.removed_pins count is 0 when nothing was expired (a no-op), and data.resource_name is the project’s full TrustPin resource name (tprn::project::<org-id>::<project-id>).
Exit Codes:
| Code | Meaning | Example |
|---|---|---|
| 0 | Success | Expired pins removed, or none to remove (no-op) |
| 2 | API error | HTTP 401, 404, 500 |
| 99 | Unexpected error | Network timeout, file I/O error |
See Also:
- Projects Upsert - Adding/updating pins
- Projects Refresh-Certs - Per-domain refresh, with optional
--remove-expired - DevOps Guide - Certificate Rotation - End-to-end rotation workflow
projects refresh-certs
Refresh the certificate pins for one domain from the certificates TrustPin can currently see: the live certificate presented in a TLS handshake, plus every unexpired issuance found in the public Certificate Transparency logs.
Where projects upsert requires you to supply a pin by hand, refresh-certs looks the certificates up for you. It is the shortest path from “my certificate rotated” to “my project is up to date”: no openssl pipeline, no domains certificates + jq plumbing, no chance of pasting the wrong digest.
trustpin-cli projects refresh-certs <organization-id> <project-id> \
--domain <fqdn> \
[--remove-expired] \
[--dry-run] \
[--verbose] \
[--output json]Parameters:
| Parameter | Required | Description |
|---|---|---|
<organization-id> | Yes | Organization UUID |
<project-id> | Yes | Project UUID |
--domain | Yes | Domain name (FQDN) to refresh |
--remove-expired | No | Also drop pins for this domain whose expiry is in the past |
--dry-run | No | Show the PATCH request that would be sent, without submitting it |
--verbose | No | Print extra detail about the lookup and the resulting change |
--output | No | Output format: human (default) or json |
If the domain is not yet in the project, it is added.
Pins are written as SPKI SHA-256. That’s the digest that survives a renewal reusing the same key pair, and the only one available for CT entries recorded as precertificates. See pin types for why SPKI is the recommended format.
Safety guarantees
refresh-certs is deliberately conservative. It is designed to be safe to run unattended, on a schedule, against production projects:
- Existing pins are never discarded. A certificate that is already pinned has its expiry refreshed rather than being pinned a second time, including when the existing pin was added by
upsertusing a different digest type. A pin the lookup did not return is left alone. - A failed lookup changes nothing. If the lookup fails or returns no certificates, nothing is written at all, including expired-pin removal, so a transient DNS or TLS failure can never strip a domain’s pins.
- No-op runs send no request. Re-running the command when nothing has changed sends no request, so the configuration version is not bumped for no reason.
--remove-expiredis scoped to the named domain. To sweep expired pins across every domain in the project, useprojects cleanupinstead.
Examples:
Refresh a domain’s pins:
$ trustpin-cli projects refresh-certs $ORG_ID $PROJECT_ID --domain api.example.com
Domain: api.example.com
7 certificate(s) returned by TrustPin
+ Added spki-sha256:heXXXV6YUWtMPE... (expires 2027-01-14T00:00:00Z)
~ Updated spki-sha256:Ab12Cd34Ef56Gh... (expiry 2026-07-01T00:00:00Z -> 2027-03-02T00:00:00Z)
= 4 pin(s) already up to date
Project updated successfully
Configuration version: 12 -> 13
Remember to sign and publish: trustpin-cli projects sign ...Preview without changing anything:
trustpin-cli projects refresh-certs $ORG_ID $PROJECT_ID \
--domain api.example.com \
--dry-runRefresh and prune this domain’s expired pins in one step:
trustpin-cli projects refresh-certs $ORG_ID $PROJECT_ID \
--domain api.example.com \
--remove-expiredRefresh every domain in a project, then publish once:
DOMAINS=$(trustpin-cli projects config $ORG_ID $PROJECT_ID | \
jq -r '.data.config.domains[].domain')
for DOMAIN in $DOMAINS; do
trustpin-cli projects refresh-certs $ORG_ID $PROJECT_ID \
--domain "$DOMAIN" --remove-expired --output json
done
trustpin-cli projects sign $ORG_ID $PROJECT_ID --password "$MASTER_PASSWORD"Note: Like
upsertandcleanup,refresh-certsupdates the stored configuration only. Changes are not live until you runprojects sign.
When to use refresh-certs vs. upsert
| Situation | Use |
|---|---|
| A certificate rotated and you want the project to catch up | refresh-certs |
| Routine, scheduled pin maintenance in CI | refresh-certs |
| Onboarding a new domain whose certificate is already live | refresh-certs |
| Pinning a certificate TrustPin cannot observe (internal-only host, not in CT) | upsert |
Pinning a specific digest type (sha256, sha512, spki-sha512) | upsert |
| Setting a custom expiry that differs from the certificate’s | upsert |
See Also:
domains certificates- Inspect what the lookup returns before applying itprojects cleanup- Prune expired pins across all domains- DevOps Guide - End-to-end rotation workflow
Configuration Signing
projects sign
Sign the project’s current configuration as a JWS and publish it to the TrustPin CDN. This is what makes staged changes from upsert, cleanup, and refresh-certs live for your apps.
trustpin-cli projects sign <organization-id> <project-id> \
[--private-key <path>] \
[--password <password>] \
[--dry-run] \
[--output json|text]Flags:
| Flag | Description |
|---|---|
-k, --private-key | Path to a PEM private key file. Required for BYOK projects |
-p, --password | Master password (cloud keys) or private key password (BYOK). Prompted interactively if omitted |
--dry-run | Sign and verify locally without publishing. Nothing is uploaded |
--verbose | Also print the signed JWS |
--output | Output format: text (default) or json |
Cloud-managed keys
For projects of type Managed CDN with Cloud Keys, no key file is needed. You’re prompted for your master password.
$ trustpin-cli projects sign fba3418e-b5ae-b273-4bab-6da6ae07ba99 df9964a9-66bf-4673-9743-adee9ce6213e
Master password: ****
[1/5] Getting project information
Project: TrustPin Mobile App (Managed CDN with Cloud Keys)
[2/5] Loading project configuration
Configuration loaded with 4 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 configuration is now live and SDKs will use it immediately.
In CI/CD, pass the password non-interactively with --password / -p, injected from a secret:
trustpin-cli projects sign $ORG_ID $PROJECT_ID --password "$MASTER_PASSWORD"Bring Your Own Key (BYOK)
For BYOK projects, supply your own private key.
Requirements:
- Private key must be in PEM format (
--private-key/-k) - Private key can be password-protected (you’ll be prompted, or pass
--password/-p)
$ trustpin-cli projects sign fba3418e-b5ae-b273-4bab-6da6ae07ba99 99acf096-79b9-4fa6-a115-14b35b224839 --private-key ./my-private-key.pem
Private key password: ****
[1/5] Getting project information
Project: API Gateway (Managed CDN with Bring Your Own Keys)
[2/5] Loading project configuration
Configuration loaded with 2 domains
[3/5] Preparing private key
Reading private key from file: ./my-private-key.pem
[4/5] Creating and signing JWT
JWT signed successfully
[5/5] Uploading signed configuration
Configuration published successfully!Rehearsing with --dry-run
--dry-run performs every step of a real run: it loads the configuration, reads and decrypts the private key, checks it against the project’s public key, signs the payload, and verifies the resulting signature. It then stops before the upload. Nothing about the project changes.
That makes it the way to confirm a key file and master password are correct, and that they belong to this project, before committing to a publish. A mismatched key still fails with INVALID_KEY_PAIR; a wrong password still fails with INCORRECT_PASSWORD.
$ trustpin-cli projects sign $ORG_ID $PROJECT_ID --private-key key.pem --dry-run
[1/4] Getting project information
[2/4] Loading project configuration
[3/4] Preparing private key
Key pair validation successful
[4/4] Creating and signing JWT
JWS signature verified successfully
Dry run: signature verified locally, nothing was uploaded
Dry run successful - nothing was published
═══ Dry Run Summary ═══
Project: my-app
Signature Valid: Yes
Key Source: BYOK
Published: No (dry run)
Domains: 2A dry run reports four steps rather than five, because the upload step does not happen.
This is particularly valuable in pipelines: rehearse the signature early, right after your secrets are wired up, so a bad key or password fails the build before you’ve staged configuration changes that you then can’t publish.
Seeing the signed JWS
Both --dry-run and --verbose print the signed token, so you can inspect or verify it yourself:
═══ Signed JWS ═══
Header: eyJhbGciOiJFUzI1NiIsImtpZCI6...
Payload: eyJkb21haW5zIjpbeyJkb21haW4i (354 chars)
Signature: uwgnUpsq42OXoTnS2ZfY9fmeDI0y...
Length: 533 chars
eyJhbGciOiJFUzI1NiIs....eyJkb21haW5zIjpb....uwgnUpsq42OXoTnS2ZfThe full compact serialization is printed on its own line so it can be copied or piped into a verifier. With --output json the same value appears as data.jws.
It’s only included when asked for, because it’s long, not because it’s secret: the identical token is served from the public CDN once published.
# Capture the JWS a dry run would publish, without publishing it
trustpin-cli projects sign $ORG_ID $PROJECT_ID \
--private-key key.pem --dry-run --output json | jq -r '.data.jws'What happens on failure:
If signing fails, the previous configuration remains active. Your users are not affected. You can retry signing at any time.
See Also:
projects jws- Verify what actually got publishedprojects refresh-certs- Stage up-to-date pins before signing- DevOps Guide - Automated signing in CI/CD
projects jws
Fetch the published JWS (JSON Web Signature) configuration from the CDN.
This command retrieves the currently published certificate pinning configuration that mobile applications are fetching. The JWS is the signed configuration that was published by the sign command.
trustpin-cli projects jws <organization-id> <project-id> [flags]Flags:
| Flag | Description |
|---|---|
--decode | Decode and display JWS header and payload |
--verify | Verify JWS signature using public key from CDN |
--output-file <path> | Save JWS to file |
Use cases:
- Verify what configuration is currently published
- Debug mobile app pinning issues
- Validate that
signsuccessfully published the configuration - Download the JWS for testing, archival, or self-hosted CDN deployment
Examples:
Fetch raw JWS (default):
$ trustpin-cli projects jws fba3418e-b5ae-b273-4bab-6da6ae07ba99 df9964a9-66bf-4673-9743-adee9ce6213e
eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJkb21haW5zIjp7ImFwaS5leGFtcGxlLmNvbSI6W3sidHlwZSI6InNwa2ktc2hhMjU2IiwidmFsdWUiOiJveFZTZENMZ3RoTDNNNVZubnplcHE4V1dsa1VmUlBZa3BqTHBtK3duKzFvPSIsImV4cGlyZXNfYXQiOiIyMDI2LTA0LTEzVDA4OjM3OjAyWiJ9XX0sImlhdCI6MTczODk4NzIwMH0.kG7z2x3nF4mB8qW9pL5jD3rC6vT8sY1eN4hK2mO9iU7lP4aQ6wX8bV9nE5cR3fJ2dT7gH1sM4kL6pO8jU9qW3eVerify JWS signature:
$ trustpin-cli projects jws fba3418e-b5ae-b273-4bab-6da6ae07ba99 df9964a9-66bf-4673-9743-adee9ce6213e --verify
JWS signature valid
Signed with algorithm: ES256 (ECDSA P-256)
Public key verified from CDNDecode and display payload:
$ trustpin-cli projects jws fba3418e-b5ae-b273-4bab-6da6ae07ba99 df9964a9-66bf-4673-9743-adee9ce6213e --decode
JWS Header:
Algorithm: ES256
Type: JWT
Payload:
Spec: v1.0.0
Version: 3
Issued At: 2026-06-11T13:24:44Z
Domains: 1
- api.example.com (2 pins)
Current DB Version: 3
Published Version: 3
Published version is up to dateSave JWS to file:
$ trustpin-cli projects jws fba3418e-b5ae-b273-4bab-6da6ae07ba99 df9964a9-66bf-4673-9743-adee9ce6213e --output-file config.jws
JWS saved to: config.jwsPipe to mobile app simulator:
$ trustpin-cli projects jws fba3418e-b5ae-b273-4bab-6da6ae07ba99 df9964a9-66bf-4673-9743-adee9ce6213e | ./test-mobile-app
Testing certificate pinning with config...
All domains validated successfullyCommon workflows:
1. Verify deployment after signing:
# Sign configuration
trustpin-cli projects sign $ORG_ID $PROJECT_ID --password $MASTER_PASSWORD
# Verify it's published and valid
trustpin-cli projects jws $ORG_ID $PROJECT_ID --verify2. Archive configurations for compliance:
# Save with timestamp
trustpin-cli projects jws $ORG_ID $PROJECT_ID \
--output-file "archive/config-$(date +%Y%m%d-%H%M%S).jws"3. Deploy to self-hosted CDN:
# Download signed JWS
trustpin-cli projects jws $ORG_ID $PROJECT_ID --output-file config.jws
# Verify integrity
trustpin-cli projects jws $ORG_ID $PROJECT_ID --verify
# Upload to your CDN
aws s3 cp config.jws s3://your-bucket/trustpin/config.jwsExit Codes:
| Code | Meaning |
|---|---|
| 0 | Success (empty output when no JWS is published yet) |
| 2 | API error |
| 7 | Failed to write the JWS to --output-file |
| 8 | JWS signature verification failed |
| 99 | Unexpected error |
See Also:
- DevOps Guide - Advanced Use Cases - Self-hosted CDN deployment
- Projects Sign - Publishing configurations
Domain Lookup
domains certificates
Look up all known SSL/TLS certificates for a domain: the live certificate plus any certificates discovered via Certificate Transparency logs. Every certificate is reported with all four pin formats, its validity window, subject alternative names, key and signature details, CAA issuer, and CT log information.
Use it to inspect what TrustPin can see for a domain, ready to use with projects upsert, or applied automatically by projects refresh-certs.
trustpin-cli domains certificates <domain-name> [--output json]Each entry is labelled with where it was observed: (live) for the certificate the server actually presents in a TLS handshake, (Certificate Transparency) for one found in the public logs. A summary line counts each.
Example:
$ trustpin-cli domains certificates api.example.com
═══ Certificates for api.example.com ═══
Total certificates: 2
Sources: 1 live, 1 from Certificate Transparency
── Certificate 1 (live) ──
Common Name: api.example.com
Issuer: CN=R11,O=Let's Encrypt,C=US
Subject Alt Names: api.example.com, www.example.com
Valid From: 2026-03-01T00:00:00Z
Expires At: 2026-12-31T23:59:59Z
Public Key: EC 256 bits
Signature Algorithm: SHA256-RSA
SHA-256: heXXXV6YUWtMPE/dUyZ6ESBpkOibPSeHseRAnp4dQJg=
SPKI SHA-256: oxVSdCLgthL3M5Vnnzepq8WWlkUfRPYkpjLpm+wn+1o=
CAA Issuer: letsencrypt.org
CT SCT Logs: 2
── Certificate 2 (Certificate Transparency) ──
Common Name: api.example.com
Issuer: CN=R10,O=Let's Encrypt,C=US
Expires At: 2027-01-31T23:59:59Z
SHA-256: (not available for this record)
SHA-512: (not available for this record)
SPKI SHA-256: fFq2hLnpGYtMuP8wR3vKdN7xZaQcEj5TbVm1oXsY9Uk=Two things worth knowing when reading the output:
(not available for this record)is expected, not an error. A CT log entry that holds only the precertificate has no usable certificate digest, because a precertificate’s hash never matches what a server sends, so the API withholds it rather than offering an unpinnable value. The SPKI digest is shared between a certificate and its precertificate, which is why it is always present, and why it is the pin TrustPin recommends.Valid Frommay be flagged(not yet valid). A certificate issued ahead of its deployment can be pinned before the server starts serving it, so it is shown rather than hidden.
--output json returns every field of the API response verbatim, including source, subjectAlternativeNames, notBefore, keyAlgorithm, keySize and signatureAlgorithm.
Pin a discovered certificate (no manual openssl):
# Pull the SPKI SHA-256 of the live certificate and upsert it
SPKI=$(trustpin-cli domains certificates api.example.com --output json | \
jq -r '.data.certificates[0].spkiSha256')
trustpin-cli projects upsert $ORG_ID $PROJECT_ID \
--domain api.example.com \
--pin spki-sha256:$SPKI \
--expires 2026-04-13T08:37:02ZTip: If you just want the project to match what’s live, you don’t need this pipeline at all:
projects refresh-certsperforms the same lookup and applies every certificate it finds in a single command.
Output Formats
All commands support two output formats:
Human-Readable (Default)
Formatted output with colors and progress indicators, perfect for interactive use.
trustpin-cli projects listJSON
Machine-readable output for automation and CI/CD pipelines.
trustpin-cli projects list --output jsonJSON response structure:
{
"status": "success",
"operation": "command-name",
"data": {
// Command-specific data
}
}Exit Codes
Exit codes are stable across commands and safe to script against:
| Code | Meaning |
|---|---|
0 | Success |
1 | Configuration error (CLI not configured) |
2 | API error |
3 | Authentication error |
4 | Validation error (bad arguments or input) |
5 | Resource not found |
6 | Key error |
7 | File error |
8 | Cryptography error |
9 | Cancelled by user |
10 | Permission error |
99 | Unexpected error |
Getting Help
View help for any command:
# General help
trustpin-cli --help
# Command-specific help
trustpin-cli projects --help
trustpin-cli projects sign --help