Skip to content

Publishing apps

Publish any Android app with zsp and your Zapstore pubkey. A release goes live in minutes, once the catalog relay has verified it.

zsp is Zapstore’s command-line publishing tool. It fetches your APK, fills in the listing from your existing metadata, signs it with your pubkey, and publishes it to the catalog.

Any Android app can be published. It doesn’t need to use Nostr, and it can stay on GitHub, Google Play or F-Droid as well.

Install

go install github.com/zapstore/zsp/v2/cmd/zsp@latest

Or download a binary. zsp -v shows the version you have.

Publish with the wizard

In your project folder, run zsp with no arguments. The wizard has three steps:

  1. Add your app. It asks where your APK is: a releases page, F-Droid, a URL or a local file.
  2. Prove it’s yours. It asks for your signing keystore once and signs a claim that links the app’s signing certificate to your pubkey.
  3. Publish. It writes a zapstore.yaml file to your folder. The catalog reads it to write the correct metadata for your app, so commit that file.

The last question is how new releases reach the catalog. You can let the catalog publish them for you from your public release source (recommended), or publish them yourself from CI/CD. You can always publish your own Nostr events instead.

1Your APK

Repository, F-Droid, a URL or a local file

2zsp

Reads it, fills in metadata, signs the listing with your pubkey

3Catalog relay

Checks your pubkey and the file

4The catalog

In the Android app and on the website

The other commands

  • zsp check reads the catalog relay and nothing else. It finds your APK, says whether the app is listed, which claim is active for its certificate, and what to do next. Run it first when something goes wrong.
  • zsp publish publishes the release described in zapstore.yaml. It never prompts, so it works in CI. It needs an active claim that names your pubkey.
  • zsp skill prints instructions that teach a coding agent how to use zsp.

check and publish print JSON when they are not run in a terminal. Flags and advanced use are in the zsp README.

Where the APK comes from

zsp detects the source from the URL.

GitHub, GitLab, Codeberg, Gitea, or Forgejo release assets:

repository: https://github.com/your-org/your-app

F-Droid or IzzyOnDroid, with richer metadata from GitHub or the Play Store:

repository: https://github.com/AntennaPod/AntennaPod
release_source: https://f-droid.org/packages/de.danoeh.antennapod

A local file:

release_source: ./build/outputs/apk/release/app-release.apk
repository: https://github.com/your-org/your-app

Any URL that points to an APK:

release_source: https://example.com/downloads/app.apk

For a private repository, set a token in the environment: GITHUB_TOKEN, GITLAB_TOKEN or GITEA_TOKEN.

Metadata

Fields you set in zapstore.yaml always win. For the rest, the first source in metadata_sources that has a value wins. The name and icon come from the APK unless you set them. Pass --skip-metadata to skip remote metadata.

Source What it reads
fastlane Your fastlane/metadata/android folder
github Name, description, topics, license, website
gitlab Name, description, topics, license
gitea Fastlane files from Gitea, Forgejo and Codeberg
fdroid Name, description, categories, icon, screenshots
playstore Name, description, icon, screenshots

Without metadata_sources, GitHub and GitLab try Fastlane first and then their own data. F-Droid and the Play Store are used only when you list them.

metadata_sources:
  - playstore
  - github

What the catalog relay requires

  • One complete arm64-v8a APK per release. Support for 32-bit ARM (armeabi-v7a) is coming soon.
  • A versionCode higher than the last one you published for that app. zsp stops an equal or lower one.
  • An active claim that names your pubkey, or a delegate of it.
  • The release channel is main unless channel says otherwise.

A release waits on the catalog relay until these checks pass, which usually takes a few minutes. The relay reads the APK itself to check the certificate, the architecture and the version code. Nobody reviews it by hand. The phone checks the file again before it installs.

Claim your app

Zapstore’s indexer may already list your app from its public releases, under the catalog’s pubkey. A claim is what lets you take it over. It is a proof signed with your APK signing key (NIP-C1) that names your Zapstore pubkey, and the catalog relay checks it against the APK. The relay accepts your own listings for the app only with a current claim, so nobody can publish in your name without your signing key.

Once claimed, your listing takes priority over the indexer’s and the app page names you as the developer. Your name, followers and zaps now attach to the certificate, and to any certificate you link to it with a signing lineage. If you skip publishing a release, the indexer can still fill it in from your public source, under your listing.

A claim generally lasts two years. The wizard renews it when you run it within 30 days of the end. You need the keystore (.jks, .p12, or .pem) only for the wizard. zsp publish never reads it.

Your pubkey signs the listing and the claim. It does not replace the APK signing certificate: the pubkey says who published the listing, and the certificate decides what Android accepts as an update.

Claim from the app page

You can also claim an app from its page on this website. Sign in with your pubkey and open the app: an unclaimed app shows Claim this app at the top right. The dialog offers zsp, or two keytool and openssl commands that sign a message with your certificate on your own machine. You paste back two public lines, signature: and cert:, and the page checks them before your pubkey signs the claim. Your keystore, its password and your secret key never reach the website.

Once you hold the claim, the same page shows Renew proof and Edit listing.

Claim with your best pubkey

Claim with the pubkey people already know you by: the one with your follows, your reputation and your Lightning address. Don’t create a new one just for publishing.

  • A pubkey with a history usually passes the catalog’s web of trust check. A new one usually doesn’t.
  • Zaps for your app go to the pubkey on the claim, at the Lightning address (lud16) on its profile. A listing has no zap field, and a delegate receives nothing.
  • People who follow you see your app under the name they recognise.

For CI or a build machine, keep that pubkey’s secret key off the machine and use a delegate instead (see CI/CD).

How you sign

Set SIGN_WITH before zsp publish, or put it in a .env file in the folder you run zsp from.

Method Value
Remote signer (recommended) SIGN_WITH=bunker://pubkey?relay=...&secret=...
Secret key SIGN_WITH=nsec1...

zsp refuses a hex key and has no browser signing. It also refuses a .env containing SIGN_WITH that other users on the machine can read, so keep it owner-only and out of Git.

CI/CD

If your release source is public, you may not need CI. Zapstore’s indexer follows it and publishes each new release, shown as developed by you.

Otherwise, the wizard can add a second pubkey to your claim as a delegate. A delegate can publish releases but cannot change who owns the app. Give CI the delegate’s nsec, or a bunker:// URL for it, as SIGN_WITH, stored as a secret. Trust and zaps still go to your own pubkey. To rotate the delegate, run zsp again with a new one.

If your signing key changes

Android accepts an update signed with a new certificate only when the new certificate carries a signing lineage (APK Signature Scheme v3) back to the old one. Rotate that way, and updates keep working. The catalog relay rejects a release whose certificate is neither the app’s current one nor linked to it by lineage.

After a rotation, run zsp again to claim the app with the new certificate. Without a lineage, people who have the app see a certificate mismatch and cannot update without reinstalling.

After you publish

A new listing appears once the catalog relay has read the APK and checked your claim, which takes a few minutes. Link to it at /apps/ followed by your package name, from your website or release notes.

Your app’s page shows its zaps and comments, and your profile’s Activity lists what people sent you. Download counts are not available to developers yet.

Troubleshooting

Run zsp check first. It says why the app is not accepted and what to do next. The error zsp publish prints also names the check that failed.

  • The catalog relay refused your first publish. Usually no claim for the app’s signing certificate names your pubkey. Run zsp to create one. A new app may also need to pass the catalog’s admission check.
  • The catalog relay rejected a release. Check the list in What the catalog relay requires.
  • rate-limited: retry in …. A pubkey can add at most 3 new apps a day, and one IP address can send at most 10 releases an hour. Wait and try again.
  • The APK was not found. The release needs an .apk asset. If several APKs match, narrow them with match in zapstore.yaml. Check the repository URL, and the token for a private repository.
  • The app is not in search. Confirm zsp publish succeeded, then wait a few minutes. zsp check says whether the app is listed.
  • The metadata is wrong. Fix zapstore.yaml and publish again. Your file overrides metadata from GitHub, F-Droid or the Play Store.
  • You published from the wrong pubkey. Delete the event from that pubkey, then publish from the right one.

Get Zapstore for Android

  1. Tap Download APK below.Scan the code with your Android phone’s camera.
  2. If Android asks, allow your browser to install unknown apps.
  3. Open Zapstore and finish the onboarding.
Download APK

v2.0.4 · 71.2 MB · Android 10+

Verify the download

On a computer, run shasum -a 256 <the file you downloaded>. It should print: b13e01319fecf0b3ba1fd60132758bd0fa08a235836c52d44fddb412cf1b7137

If you have zsp, Zapstore’s publishing tool, zsp utils extract-apk <the file you downloaded> works too. Its hash should be the same SHA-256: b13e01319fecf0b3ba1fd60132758bd0fa08a235836c52d44fddb412cf1b7137

On the phone, AppVerifier should show this signing certificate: 99e33b0c2d07e75fcd9df7e40e886646ff667e3aa6648e1a1160b036cf2b9320

Source code All releases and notes

Get zsp

  1. Download the file for your computer.
  2. Make it executable and put it on your PATH.
  3. In your project folder, run zsp.

Have Go? This does the same:

go install github.com/zapstore/zsp/v2/cmd/zsp@latest
macOS, Apple silicon Linux, x86-64 Linux, arm64

v2.0.1 · macOS and Linux

Verify the download

On your computer, run shasum -a 256 <the file you downloaded>. It should print the SHA-256 for the one you chose.

macOS, Apple silicon · 18.6 MB 99018fb9744e5ace2b44557550dc333496239912c0e9b1137266d4922c803346

Linux, x86-64 · 23.8 MB 1ef5373f8ecd9ba63d2c3fab98abb0d31b2244a2aac31b3749fb205c7d5f5b38

Linux, arm64 · 22.1 MB e421005ee47cba58293dce4277bb784e4cfe706dbbcb8617d671629d0238be18

Source code All releases and notes