Marketplace API reference

Every stable public endpoint on the marketplace API. Bearer-token authenticated (JWT or long-lived API key). Rate-limited. Third-party-integration-safe.

Base URL + auth

Base URL: https://railcall-marketplace-lggm.onrender.com

Every authenticated endpoint below accepts EITHER a JWT access token (from POST /auth/login) OR a long-lived API key (rc_ak_live_… minted at /marketplace/settings/api-keys). Header: Authorization: Bearer <token>.

Rate limits: 60 req/min per IP baseline. Some endpoints tighter (login/signup: 8/min; publish: 5/hour; design-partner: 3/hour).

Auth

POST/auth/registerauth: none

Create a new marketplace account.

request body
json
{
  "email": "you@company.com",
  "password": "at-least-12-chars-with-a-digit-1"
}
response (200)
json
{
  "access_token": "<JWT>",
  "refresh_token": "<opaque>",
  "user": { "id": "...", "email": "...", "email_verified": false, "is_admin": false }
}
POST/auth/loginauth: none

Sign in with email + password.

request body
json
{ "email": "you@company.com", "password": "..." }
response (200)
json
{ "access_token": "...", "refresh_token": "...", "user": {...} }
POST/auth/refreshauth: refresh token in body

Rotate the refresh token, get a fresh access token.

request body
json
{ "refresh_token": "..." }
response (200)
json
{ "access_token": "...", "refresh_token": "...", "user": {...} }
GET/auth/meauth: JWT

Current user profile. Powers dashboard hydration + SSO complete page.

response (200)
json
{ "id": "...", "email": "...", "email_verified": true, "is_admin": false }
GET/auth/api-keysauth: JWT (API keys refused)

List your long-lived API keys. Never returns the secret — only prefix + name + last-used.

response (200)
json
[
  {
    "id": "...",
    "name": "prod CI runner",
    "prefix": "rc_ak_live_A",
    "scopes": "publish read",
    "last_used_at": "2026-07-25T00:12:33Z",
    "revoked_at": null,
    "expires_at": null,
    "created_at": "2026-07-24T18:44:02Z"
  }
]
POST/auth/api-keysauth: JWT (API keys refused — lateral-privilege guard)

Create a long-lived API key. Secret returned exactly once in the response — save it now.

request body
json
{ "name": "prod CI runner", "scopes": "publish read" }
response (200)
json
{
  "id": "...",
  "name": "prod CI runner",
  "prefix": "rc_ak_live_A",
  "scopes": "publish read",
  "expires_at": null,
  "created_at": "2026-07-25T02:01:14Z",
  "secret": "rc_ak_live_A1b2c3D4e5F6g7H8i9J0k1L2"
}
DELETE/auth/api-keys/:idauth: JWT

Revoke an API key. Any CI job using it 401s on next request.

response (200)
json
{ "ok": true }

Listings (marketplace catalog)

GET/listingsauth: optional JWT (unauth returns public only)

Browse the catalog. Anonymous callers see only public listings; authenticated members additionally see org_internal listings owned by orgs they belong to.

query params
  • listing_typeworkflow | policy_pack | prompt_library | module
  • categoryone of the LISTING_CATEGORIES enum
  • qsearch string (matches title / slug / description)
  • limit1-100 (default 50)
  • offsetfor pagination
response (200)
json
{ "count": 50, "total": 137, "offset": 0, "limit": 50, "items": [ ... ] }
GET/listings/:idOrSlugauth: optional JWT

Full detail for a listing. 404s on org_internal listings the caller isn't authorized to see (deliberate — prevents slug enumeration).

response (200)
json
{
  "id": "...", "slug": "sami666/salesforce", "title": "...", "description": "...",
  "listing_type": "module", "price_cents": 19900, "pricing_model": "subscription",
  "billing_interval": "month", "publisher_pubkey": "...", "seller": {...}, ...
}
POST/listingsauth: JWT OR API key (BearerAuthGuard)

Publish a signed listing. Rate-limited 5/hour/seller. Requires a registered publisher pubkey + valid signature.

request body
json
{
  "id": "your-handle/your-module",
  "listing_type": "module",
  "title": "...",
  "description": "at least 20 chars",
  "category": "Modules",
  "price_cents": 0,
  "payload": { ... },
  "payload_sha": "<sha256 hex>",
  "publisher_pubkey": "<64 hex>",
  "publisher_sig": "<128 hex>",
  "created_at": "2026-07-25T12:00:00Z",
  "visibility": "public"
}
response (200)
json
{ "id": "cuid", "slug": "your-handle/your-module", "listing_type": "module", ... }

Organization (RBAC + billing surfaces)

POST/orgsauth: JWT

Self-serve org creation. Caller becomes the org's first owner. Refuses if caller is already a member of an org.

request body
json
{ "slug": "acme", "name": "Acme Inc", "email_domains": ["acme.com"], "billing_email": "billing@acme.com" }
response (200)
json
{ "id": "...", "slug": "acme", "name": "Acme Inc", "email_domains": [...], "created_at": "..." }
GET/org/membersauth: JWT + OrgRoleGuard (any role in org)

Roster of the caller's org. Returns 403 if caller isn't in any org.

response (200)
json
[ { "id": "...", "user_id": "...", "email": "...", "role": "owner", "joined_at": "..." } ]
POST/org/invitationsauth: JWT + admin/owner role

Invite by email. Sends invite email with one-time redemption token.

request body
json
{ "email": "teammate@acme.com", "role": "operator" }
response (200)
json
{ "id": "...", "email": "...", "role": "operator", "expires_at": "..." }
POST/org/invitations/acceptauth: JWT (invitee)

Redeem an invitation token. Adds you to the org.

request body
json
{ "token": "<raw invite token from email URL>" }
response (200)
json
{ "ok": true, "organization_id": "...", "role": "operator" }
GET/org/auditauth: JWT + admin/owner role

Admin audit log (member changes, vault-config changes, SCIM events, billing events).

query params
  • eventfilter by event slug
  • limit1-500 (default 100)
  • beforeISO timestamp for keyset pagination
response (200)
json
[ { "id": "...", "actor_kind": "user", "event": "member.invited", "target": "teammate@acme.com", "details": {...}, "created_at": "..." } ]
GET/org/vault-configauth: JWT + any org role

Studio reads this on boot to learn where to mirror receipts.

response (200)
json
{ "vault_config": { "driver": "s3", "bucket": "...", "region": "us-east-1", "access_key_ref": "env:AWS_ACCESS_KEY_ID", ... } }
GET/org/exportauth: JWT + owner role

GDPR Art. 20 / CCPA export. JSON dump of everything the marketplace stores about the org.

response (200)
json
{ "generated_at": "...", "organization": {...}, "members": [...], "invitations": [...], "org_listings": [...], "audit_log_recent": [...] }

Billing

GET/billing/pricingauth: none

Public pricing config for the /pricing + /org/billing pages. Everything here reads from marketplace env config.

response (200)
json
{ "min_seats": 2, "trial_days": 14, "price_per_seat_month_usd_cents": 10000, "free_seat_cap": 1 }
GET/org/billing/seat-statusauth: JWT + any org role

Seat gauge: tier, cap, active members, pending invites, subscription state.

response (200)
json
{ "tier": "free", "seat_cap": 1, "seats_used": 1, "active_members": 1, "pending_invites": 0, "subscription": null }
POST/org/billing/team/checkoutauth: JWT + owner role

Start a Team-tier Stripe Checkout session. Returns a hosted URL you redirect the buyer to. Card captured now, first charge at trial_end.

request body
json
{ "seats": 5, "billing_email": "billing@acme.com" }
response (200)
json
{ "url": "https://checkout.stripe.com/c/pay/cs_live_..." }
POST/org/billing/portalauth: JWT + admin/owner role

Get a Stripe Billing Portal deep-link for self-service subscription management.

response (200)
json
{ "url": "https://billing.stripe.com/session/..." }

Design partners

POST/design-partnersauth: none

Submit a design-partner application. Rate-limited 3/hour/IP. Duplicates within 24h return silent success.

request body
json
{
  "full_name": "Jane Doe",
  "work_email": "jane@acme.com",
  "organization": "Acme Inc",
  "role": "CTO",
  "team_size": "11-50",
  "regulated_data": "phi",
  "target_regime": "HIPAA BAA",
  "timeline": "60-90",
  "what_would_change": "at least 20 characters describing the concrete change"
}
response (200)
json
{ "ok": true, "application_id": "...", "duplicate": false }

Miscellaneous

GET/healthauth: none

Liveness probe. Returns 200 + { status: 'ok' } if the marketplace is up.

response (200)
json
{ "status": "ok" }
What's NOT public

Anything under /admin/* is staff-only + not part of the public API contract. Anything returned only inside the JWT payload (session_generation, etc.) is implementation detail; don't build against it. If you need an integration surface that's not listed above, drop a note in our Discord with the use case — we'd rather add a real endpoint than have you scrape.

Error shape

Every 4xx/5xx returns:

json
{
  "statusCode": 400,
  "message": "human-readable error" or ["message 1", "message 2"],
  "error": "Bad Request"
}

Rate-limit errors are 429 with the same shape. Auth failures are 401 for missing/invalid JWT + 403 for insufficient role.