openssl genpkey -algorithm Ed25519 -out priv.pem openssl pkey -in priv.pem -pubout -outform DER | tail -c 32 | xxd -p -c 64The hex string is your public key — paste it into the dashboard's Signing key form. Keep
priv.pem safe; the marketplace never sees it.
com.example.todo.
.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.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
{
"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"
}
Trust gates which app id prefixes you can claim and whether your apps appear in the default catalog browse.
| Tier | Allowed namespaces | Catalog visibility |
|---|---|---|
email_only | com.unverified.<your-id>.* (sandbox) | Hidden from default browse; install via direct link, dialog warns "unverified". |
domain_verified | Reverse-DNS of any verified domain (e.g. verified example.com → com.example.*) | Listed in default browse. |
paid_verified | Same as domain_verified | Eligible for "featured" + higher rate limits. |
first_party | Any 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).
/api/publisher/domains with { "domain": "example.com" }. The response includes a challenge_token and the exact record_name / record_value to publish._jpos-publisher.example.com with body jpos-publisher-verify=<token>./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.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:
?include=unverified.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.
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.
Stripe emails you a receipt automatically and computes US sales tax where applicable. Find your full payment history under Billing history on the dashboard.
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.
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.
POST /api/publisher/auth/request — body { "email": "..." }GET /api/publisher/auth/verify?token=… — sets cookie, redirects to dashboardPOST /api/publisher/auth/logoutGET /api/publisher/me — includes tier and verified_domainsPOST /api/publisher/keys — body { "ed25519_pubkey": "<hex>" }GET /api/publisher/packagesPOST /api/publisher/packages — body { "app_id", "name", … } (gated by tier)GET /api/publisher/packages/:id/statsPOST /api/publisher/packages/:id/versions — multipart (manifest, bundle, signature)POST /api/publisher/packages/:id/versions/:v/yankPOST /api/publisher/packages/:id/versions/:v/unyankGET /api/publisher/domains · POST /api/publisher/domainsPOST /api/publisher/domains/:domain/verify · DELETE /api/publisher/domains/:domainGET /api/publisher/api-tokens · POST /api/publisher/api-tokens · DELETE /api/publisher/api-tokens/:idPOST /api/publisher/payments/checkout — creates a Stripe Checkout Session, returns { "checkout_url" }GET /api/publisher/payments — your payment historyPOST /api/publisher/payments/webhook — Stripe webhook target (signature-verified, public)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.