# Sign and publish

Source: https://opvs.ai/docs/guides/vendors/publish

> Publish a signed package to market.opvs.ai with opvs-skills, check it is live, and fix the common refusals.

How your package gets from your machine to `market.opvs.ai`.

## What `publish` does

```
 your machine                                market.opvs.ai
 ────────────                                ──────────────
 1 validate manifest (same rules as server)
 2 load your ed25519 key → fingerprint
 3 pack → SHA-256 → sign the digest
 4 ─────────────────── POST /api/v1/bundles/upload-url ──▶ upload URL
 5 ─────────────────── PUT the tarball ─────────────────▶ bundle storage
 6 ─────────────────── POST /api/v1/registry/packages ──▶ verify signature
                                                          check certificate is active
                                                          validate manifest
                                                          run the security review
                                                          → published, or 422 + findings
```

## You need

- A vendor scope and an active [signing certificate](https://opvs.ai/docs/guides/vendors/signing-certificate.md).
- Your [partner key](https://opvs.ai/docs/guides/vendors/partner-keys.md).
- The CLI, from public npm: `npm install -g @opvs-ai/skills`.
- A package folder with a valid `package.yaml`.

## Step 1: dry run

```bash
export OPVS_SIGNING_KEY="<your base64url private key>"   # from your CI secret
opvs-skills publish ./my-package --dry-run --private-key @env:OPVS_SIGNING_KEY
```

No network call, no partner key. Green means the manifest is right and the bundle signs.

## Step 2: publish

```bash
export OPVS_MARKETPLACE_ADMIN_KEY="<your partner key>"
opvs-skills publish ./my-package \
  --private-key @env:OPVS_SIGNING_KEY \
  --api-url https://market.opvs.ai
```

Flag

What

Notes

`--private-key`

your ed25519 signing key

a file path, an inline base64url string, or `@env:NAME`

`--api-url`

the registry

**always pass **`https://market.opvs.ai`**.** The CLI's own default points at a local development address.

`--admin-key`

your partner key

or set `OPVS_MARKETPLACE_ADMIN_KEY`

`--dry-run`

preflight only

validate and sign, no upload

On success the CLI prints the version, `bundle_sha256`, the bundle URL and your certificate fingerprint.

## Step 3: check it is live

```bash
curl -s "https://market.opvs.ai/api/v1/registry/packages/@acme/leads" \
  | python3 -c "import sys,json; d=json.load(sys.stdin); print(d['version'], d['status'], d['tier'], d['security_score'])"
# expect: your new version, published, the tier you declared, a score near 100
```

## In CI

```yaml
# GitHub Actions example
env:
  OPVS_MARKETPLACE_ADMIN_KEY: ${{ secrets.OPVS_PARTNER_KEY }}
  OPVS_SIGNING_KEY: ${{ secrets.OPVS_SIGNING_KEY }}
steps:
  - uses: actions/checkout@v4
  - run: npm install -g @opvs-ai/skills
  - run: opvs-skills publish ./my-package --private-key @env:OPVS_SIGNING_KEY --api-url https://market.opvs.ai
```

## Things to know before you publish

- **A published version cannot be replaced.** `(vendor, name, version)` is permanent. To change anything, bump the version. See [Versions and trust tiers](https://opvs.ai/docs/guides/vendors/versions-and-tiers.md).
- **Publishing delivers.** Workspaces that installed your package on a `latest` or range pin receive the new version automatically, within a day. Treat every publish as a release.
- **The tier is in the manifest.** `beta` (the default) is visible to customers immediately; `internal` keeps a version hidden while you test.

## Common refusals

Message (shortened)

Cause

Fix

`extra fields not permitted`

an unknown top-level key

remove or correct it

scope must match `"@"+vendor`

`name` and `vendor` disagree

make them agree

version must be valid SemVer

`v1`, `0.1`

use `0.1.0`

`409` version … is already published

you republished a version

bump the version

must declare skills / inbound_events / peer_dependencies

empty package

declare at least one

`provider_config` requires `base_url_env` or `upstream`

missing

add one, even for guidance-only skills

`entitlement.unknown`

an unknown entitlement

use a known name or `vendor.<you>.<surface>.<verb>`

`403`

partner key missing, revoked or for another vendor

check the key and the scope

`422` with findings

a blocking security-review finding

see [Security review](https://opvs.ai/docs/guides/vendors/security-review.md)

## Next

[Security review](https://opvs.ai/docs/guides/vendors/security-review.md).
