Your first module — 30 minutes, zero to published
Walks you from `curl install.sh | bash` to a signed module live on the marketplace. Every command has expected output; copy-paste is safe. If you're building for the RailCall community contest, this is where you start.
A free module called your-handle/greeter with one command: greet. It takes a name input and returns {"message": "Hello, <name>!"}. Boring on purpose — the goal is to prove the whole loop works so you can substitute your real logic in step 6.
Prerequisites
- Python 3.9+ (
python3 --version) - curl or wget
- ~30 minutes
- An email address for the marketplace account
1. Install the RailCall CLI (30 seconds)
curl -sSL https://railcall.ai/install.sh | bashInstalls to ~/.railcall/ with a launcher on your PATH. Every downloaded file is sha256-pinned against the installer — a wire tamper fails the pin gate before anything touches disk.
Verify:
railcall version
# → RailCall CLI · station-v0.59 · <build hash>2. Create your marketplace account (2 min)
Sign up at railcall.ai/marketplace/signup (email + password). Click the verification link in your inbox.
Log in from the CLI:
railcall market login your@email.com
# → prompts for password
# → prints: logged in as your@email.com · session saved 06003. Mint a publisher keypair (1 min)
Every listing you publish is Ed25519-signed with your publisher key. The keypair is minted locally + never leaves your machine; the public half is registered on your account so verification works for anyone who downloads your module.
railcall market publisher init your-handle
# → generates ~/.railcall/publisher_key.json (0600)
# → prints your public key (64 hex chars)
railcall market publisher register
# → registers the pubkey on your marketplace account
# → prints: registered publisher pubkey <first 16 chars>...Registering the pubkey doesn't automatically let you publish paid modules — those require staff approval on the allowlist. Free modules work immediately. For the contest, publishing free is fine; paid comes after your first successful free module + a quick verification email from sami@railcall.ai.
4. Create the module scaffold (2 min)
Create the directory structure:
mkdir -p ~/railcall-modules/greeter/handlers
cd ~/railcall-modules/greetermodule.json
The manifest declares which commands your handler exposes + auth pattern + whether the module gates on a license. Copy this verbatim:
{
"slug": "your-handle/greeter",
"version": "0.1.0",
"description": "Says hello to a name you provide.",
"auth": { "type": "none" },
"commands": [
{
"name": "greet",
"description": "Return a greeting for the given name.",
"input_schema": {
"type": "object",
"properties": {
"name": { "type": "string", "description": "Who to greet." }
},
"required": ["name"]
},
"side_effects": "none"
}
],
"license_required": false
}Save that as ~/railcall-modules/greeter/module.json.
handlers/handler.py
The handler implements every command declared in module.json. One top-level function per command, name matches the manifest.
"""Handlers for the your-handle/greeter module.
Every command declared in module.json must have a matching top-level
function here. The function's return value becomes the receipt payload.
"""
def greet(inputs: dict, context: dict) -> dict:
"""Return a friendly greeting.
inputs: the JSON body validated against input_schema in module.json
context: RailCall runtime info — install_pubkey, org_id (if any),
workspace path, etc. Ignored here; useful for real modules
that need to know where they're running.
"""
name = inputs.get("name", "world")
return {"message": f"Hello, {name}!"}
Save that as ~/railcall-modules/greeter/handlers/handler.py.
5. Test locally before publishing (3 min)
A local "install" is the same thing a buyer's install is: a signedfolder inside the station's modules directory. The loader refuses unsigned bundles — locally too — so you sign first, drop the folder in place, and reload. Your commands then run through the exact airlock ceremony a buyer gets.
# one-time: mint your Ed25519 publisher keypair (skip if you have one)
railcall market publisher init your-handle
# sign the bundle — writes module.sig next to module.json
railcall market module sign ~/railcall-modules/greeter
# optional but recommended: offline self-check of the signed bundle
railcall market module verify ~/railcall-modules/greeter
# "install" = copy the signed folder into the station's modules dir
cp -r ~/railcall-modules/greeter ~/.railcall/station/modules/greeterThen open Studio (railcall studio), go to the Modules tab and click Reload all — no restart needed. Your module appears in the list (or in the rejected section with the exact reason: bad signature, schema problem, handler naming mismatch). Run greet from the command palette: the airlock stages it, shows you the preview, and on approve executes the handler + emits an Ed25519-signed receipt.
Module commands execute through the airlock — stage → preview → approve → signed receipt— in Studio (or as workflow nodes, or via MCP from Claude Desktop). A bare CLI runner would skip the approval ceremony that is the entire point of the platform, so it doesn't exist. Every buyer who installs your module gets the same behavior + receipt discipline — that's what they're paying for (or getting free, in your case).
6. Substitute your real logic
This is where you replace the greeter with what you actually want to build. Common patterns you'll want:
- API call to an external service: add
"auth": { "type": "api_key", "env_var": "MY_API_KEY" }tomodule.json; the CLI prompts the buyer to set the env var on install. - OAuth (Google, Salesforce, Slack, etc.): add
"auth": { "type": "oauth2", "provider": "salesforce" }. See the auth patterns doc for provider setup. - Multiple commands: add more entries to the
commandsarray + matching functions inhandler.py. - Side effects (sending, writing, deleting): set
"side_effects": "external"— RailCall's airlock forcespreview → approve → executefor those commands, so buyers see the diff before you fire.
7. Publish to the marketplace (2 min + review)
The publish command signs the bundle + uploads. Rate-limited to 5 publishes per hour per account.
cd ~/railcall-modules/greeter
railcall market publish .
# → signs bundle with your publisher key
# → uploads to railcall-marketplace-lggm.onrender.com
# → prints: published your-handle/greeter@0.1.0 · status: pending_reviewEvery new listing lands in a pending_review queue before it appears on the public marketplace. This protects buyers from broken or misleading modules and keeps the catalog trustworthy.
Approval is fast — during the contest window we review submissions same-day (often within a couple hours). You'll see the listing on your dashboard immediately in PENDING_REVIEW state; once approved, it flips to ACTIVE and appears on the public browse.
What we check: (a) the module actually installs + runs, (b) the description matches what it does, (c) no obvious security issues (secrets logged, side_effects flag lying). Anything else is fair game — we're not judging your architecture, just protecting buyers.
Once approved, anyone can install it with:
railcall market install your-handle/greeter
# → verifies your publisher signature, installs into the station's modules dir
# → the greet command appears in Studio's palette + Modules tab8. Verify a fresh buyer's install (3 min)
Best test: install it as a buyer would, on a machine (or after removing your local copy) that doesn't have your source. If it works here, it works for real buyers.
# Remove your local dev copy so the marketplace version is what loads
rm -rf ~/.railcall/station/modules/greeter
railcall market install your-handle/greeter
# Studio → Modules → Reload all → run greet from the palette
# → {"message": "Hello, fresh install test!"} + signed receiptMaking it paid (optional)
Once your free module is stable + you've been approved on the publisher-trust allowlist (ping us in Discord with a link to your listing):
- Flip
"license_required": truein module.json - Set your price at marketplace/dashboard → one-time or monthly subscription
railcall market publish .again — bump the version
RailCall takes 5% on one-time sales, 25% on subscription modules. Payouts to your wallet after a 5-day refund hold, then withdraw via Stripe Connect. Full terms: /legal/marketplace.
Next steps
- Module patterns reference: /docs/marketplace-developer/modules — auth patterns, side-effect classes, license flow
- Live reference modules: sami666/salesforce (paid, OAuth, subscription) · sami666/hubspot (free, Bearer token)
- Contest submissions: if you're building for the RailCall community contest, tag your listing with
contest:2026Q3in the description + read the contest brief for judging criteria. - Stuck? Post in our Discord with the exact command + error output. Response within one business day.
Your handler doesn't call the airlock directly — it just returns data. The RailCall runtime wraps every command in the airlock automatically: input validation, preview generation, approval gate (for side-effect commands), execution, receipt signing. You get all of that with zero extra code. That's the whole point of publishing to RailCall vs shipping a bare Python package.