JPOS Marketplace

Sign in

Your first published app

  1. Sign in. The portal sends a magic link; click it, and you're in.
  2. Generate an ed25519 keypair locally:
    openssl genpkey -algorithm Ed25519 -out priv.pem
    openssl pkey -in priv.pem -pubout -outform DER | tail -c 32 | xxd -p -c 64
    The hex string is your public key — paste it into the dashboard's Signing key form. Keep priv.pem safe; the marketplace never sees it.
  3. Claim an app id on the Submit app page. Use reverse-DNS: com.example.todo.
  4. Build a bundle. A bundle is a .zip containing manifest.json at the root and any index.html/*.js/*.css your app needs. The manifest declares app_id, name, version (SemVer), publisher, and (optional) capabilities.
  5. Sign it. Compute SHA-256 of bundle.zip and sign the string app_id|version|sha256_hex with your private key. The signature is base64-encoded.
    SHA=$(openssl dgst -sha256 -binary bundle.zip | xxd -p -c 64)
    printf '%s|%s|%s' "$APP_ID" "$VER" "$SHA" \
      | openssl pkeyutl -sign -inkey priv.pem -rawin \
      | base64 -w0
  6. Upload the bundle, manifest, and signature on the app's detail page. The gateway re-hashes the bundle, re-builds the canonical message, and verifies the signature against your registered key — only then is the version published.

Manifest schema

{
  "app_id": "com.example.todo",
  "name": "Todo",
  "version": "1.2.0",
  "description": "A simple todo list",
  "category": "productivity",
  "publisher": "Example Inc.",
  "entry": "index.html",
  "permissions": [],
  "capabilities": ["filesystem.read", "notifications"],
  "jpos_min_version": "0.1.0",
  "icon": "icon.png",
  "homepage": "https://example.com"
}

Publisher tiers & namespaces

Trust gates which app id prefixes you can claim and whether your apps appear in the default catalog browse.

TierAllowed namespacesCatalog visibility
email_onlycom.unverified.<your-id>.* (sandbox)Hidden from default browse; install via direct link, dialog warns "unverified".
domain_verifiedReverse-DNS of any verified domain (e.g. verified example.comcom.example.*)Listed in default browse.
paid_verifiedSame as domain_verifiedEligible for "featured" + higher rate limits.
first_partyAny namespace (incl. reserved)Always visible. Admin-set only.

Reserved namespaces like com.apple.*, com.google.*, com.jpos.* are rejected unless you've verified the matching domain (or you're first_party).

Verifying a domain

  1. POST /api/publisher/domains with { "domain": "example.com" }. The response includes a challenge_token and the exact record_name / record_value to publish.
  2. Add a TXT record at _jpos-publisher.example.com with body jpos-publisher-verify=<token>.
  3. POST /api/publisher/domains/example.com/verify. We resolve via DNS-over-HTTPS at cloudflare-dns.com; on first success your tier flips from email_only to domain_verified.

Verified publisher — pricing & refunds

Upgrading from domain_verified to paid_verified is a one-time $50 USD payment processed by Stripe. There is no recurring subscription. Paid publishers get:

You must verify a domain before purchasing — that's how we tie a paid account to an identity worth charging for. Email-only accounts cannot upgrade directly.

Refund policy

Full refund within 30 days of purchase, no questions asked. Email support@jpos.app; we'll process the refund through Stripe and your tier will downgrade automatically when the refund clears. Already-published versions stay live for installed users.

Receipts & tax

Stripe emails you a receipt automatically and computes US sales tax where applicable. Find your full payment history under Billing history on the dashboard.

API tokens (for CI)

Mint a long-lived bearer from the dashboard. Use it on the upload endpoints — the token implies the publisher, so no X-Publisher-Id header is needed.

curl -X POST https://gateway.jpos.app/api/publisher/packages/com.example.todo/versions \
  -H "Authorization: Bearer jposp_..." \
  -F manifest=@manifest.json \
  -F bundle=@bundle.zip \
  -F "signature=$(cat sig.b64)"

Tokens are shown once at mint time and stored as SHA-256 hashes. Revoking a token from the dashboard invalidates it immediately.

API surface

Everything the portal does, you can do from a script. Auth via session cookie, publisher API token (jposp_…), or — in dev only — the bootstrap bearer + X-Publisher-Id.

Catalog: GET /api/marketplace/catalog hides email_only publishers' apps by default; pass ?include=unverified to see them. Each catalog item carries a trust block with tier, verified_domains, and rendered badges.