Skip to Content
CLI & DevOpsCommands

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 domain

New in CLI v6.0.0: projects refresh-certs discovers and applies a domain’s pins for you, with no openssl pipelines and no manual --pin values, and projects sign gained a --dry-run rehearsal mode that verifies your key and password without publishing.


Global Flags

Available on every command:

FlagDescription
--verboseVerbose output: config file location, API URLs, HTTP status codes
--debugDebug output
--log-httpLog HTTP requests and responses
--versionPrint 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:

  1. Visit https://app.trustpin.cloud/account/access-tokens 
  2. Click Create Token
  3. Copy the token (it starts with tp_)
  4. 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_here

Otherwise, 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 configure

User

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

The 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 cleanup to drop already-expired pins. This keeps the stored configuration tidy and avoids carrying dead pins into the next signed version.

Prefer refresh-certs for rotations. If the certificate you want to pin is already live or visible in Certificate Transparency, projects refresh-certs discovers and applies its pin for you, with no openssl pipeline and no hand-copied digest. Reach for upsert when you need a specific digest type, a custom expiry, or a certificate TrustPin cannot observe.

Parameters:

ParameterRequiredDescription
<organization-id>YesOrganization UUID
<project-id>YesProject UUID
--domainYesDomain name (e.g., api.example.com)
--pinYesPin in format <type>:<value>
--expiresNoExpiration date in ISO 8601 format
--dry-runNoPreview changes without applying them
--verboseNoPrint extra detail about the request and the resulting change
--outputNoOutput format: human (default) or json

Pin Types:

TypeDescriptionUse Case
sha256Certificate SHA-256 fingerprintMust update pin on every certificate renewal
sha512Certificate SHA-512 fingerprintMust update pin on every certificate renewal
spki-sha256SPKI SHA-256 fingerprintRecommended - OWASP best practice
spki-sha512SPKI SHA-512 fingerprintMaximum 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:

ScenarioActionResponse
Domain exists, pin exists, same expirationNo-op"action": "no_change"
Domain exists, pin exists, different expirationUpdate"action": "updated"
Domain exists, pin doesn’t existAdd"action": "added"
Domain doesn’t existCreate + 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 json

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 } }

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-run

Output (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:00Z

Output:

{ "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:02Z

Important Notes:

  1. Configuration is not published yet - After upserting, you must run projects sign to publish changes to the CDN
  2. Multiple upserts before signing - You can upsert multiple domains/pins before signing once
  3. Idempotent operation - Safe to run multiple times; won’t duplicate pins
  4. Version tracking - Each upsert increments the configuration version
  5. 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 servers

2. 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_PASSWORD

3. 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_PASSWORD

Exit Codes:

CodeMeaningExample
0Success or no-opPin added, updated, or unchanged
2API errorHTTP 401, 404, 500
4Validation errorInvalid domain, pin format, or type
99Unexpected errorNetwork timeout, file I/O error

See Also:


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 sign afterwards 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-expired instead.

Parameters:

ParameterRequiredDescription
<organization-id>YesOrganization UUID
<project-id>YesProject UUID
--dry-runNoAsk the server how many pins would be removed without changing anything
--verboseNoPrint the per-domain breakdown of removed and kept pins
--outputNoOutput 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_ID

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

CodeMeaningExample
0SuccessExpired pins removed, or none to remove (no-op)
2API errorHTTP 401, 404, 500
99Unexpected errorNetwork timeout, file I/O error

See Also:


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:

ParameterRequiredDescription
<organization-id>YesOrganization UUID
<project-id>YesProject UUID
--domainYesDomain name (FQDN) to refresh
--remove-expiredNoAlso drop pins for this domain whose expiry is in the past
--dry-runNoShow the PATCH request that would be sent, without submitting it
--verboseNoPrint extra detail about the lookup and the resulting change
--outputNoOutput 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 upsert using 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-expired is scoped to the named domain. To sweep expired pins across every domain in the project, use projects cleanup instead.

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-run

Refresh and prune this domain’s expired pins in one step:

trustpin-cli projects refresh-certs $ORG_ID $PROJECT_ID \ --domain api.example.com \ --remove-expired

Refresh 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 upsert and cleanup, refresh-certs updates the stored configuration only. Changes are not live until you run projects sign.

When to use refresh-certs vs. upsert

SituationUse
A certificate rotated and you want the project to catch uprefresh-certs
Routine, scheduled pin maintenance in CIrefresh-certs
Onboarding a new domain whose certificate is already liverefresh-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’supsert

See Also:


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:

FlagDescription
-k, --private-keyPath to a PEM private key file. Required for BYOK projects
-p, --passwordMaster password (cloud keys) or private key password (BYOK). Prompted interactively if omitted
--dry-runSign and verify locally without publishing. Nothing is uploaded
--verboseAlso print the signed JWS
--outputOutput 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: 2

A 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....uwgnUpsq42OXoTnS2Zf

The 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

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:

FlagDescription
--decodeDecode and display JWS header and payload
--verifyVerify 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 sign successfully 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.kG7z2x3nF4mB8qW9pL5jD3rC6vT8sY1eN4hK2mO9iU7lP4aQ6wX8bV9nE5cR3fJ2dT7gH1sM4kL6pO8jU9qW3e

Verify 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 CDN

Decode 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 date

Save 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.jws

Pipe 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 successfully

Common 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 --verify

2. 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.jws

Exit Codes:

CodeMeaning
0Success (empty output when no JWS is published yet)
2API error
7Failed to write the JWS to --output-file
8JWS signature verification failed
99Unexpected error

See Also:


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 From may 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:02Z

Tip: If you just want the project to match what’s live, you don’t need this pipeline at all: projects refresh-certs performs 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 list

JSON

Machine-readable output for automation and CI/CD pipelines.

trustpin-cli projects list --output json

JSON response structure:

{ "status": "success", "operation": "command-name", "data": { // Command-specific data } }

Exit Codes

Exit codes are stable across commands and safe to script against:

CodeMeaning
0Success
1Configuration error (CLI not configured)
2API error
3Authentication error
4Validation error (bad arguments or input)
5Resource not found
6Key error
7File error
8Cryptography error
9Cancelled by user
10Permission error
99Unexpected 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