Architecture
How Marka is built — the canonical listing model, the mutation pipeline every state change flows through, the two order rails, search, and the AI seam.
These pages describe how Marka works underneath the API — the models and invariants the endpoints are a view onto. You do not need any of it to make a call. You need it when you are deciding how much to trust a response, why an operation is shaped the way it is, or what happens between your request returning and the effect being visible somewhere else.
Everything here is written from the code it describes, and every page names the files that make it true. Where a behaviour is deliberately narrower than it could be, that is stated rather than smoothed over.
The shape of the system
Marka is a canonical-listing marketplace. A seller creates one Listing. That listing is the
single record of what is for sale; every other representation of it — a buyer-facing search
result, a projection onto eBay, the snapshot frozen into an order — is derived from it and knows
which one it came from.
Around that sit four things worth understanding separately:
The mutation pipeline
Pure domain functions return a MutationBundle; one serializable transaction writes the entity, its audit log and its events together.
Listings and channels
One canonical listing, projected to external marketplaces as ChannelListings — with authority, drift detection and decrement fan-out.
Payments and order rails
Two rails, one of which never touches Stripe. Direct charges, a 0% application fee, and where the money actually sits.
Search
Typesense hybrid search, a fire-and-forget index write with a reconciling cron behind it, and the candidate ceiling.
The AI seam
Typed tools only, a 60-second signed identity token, and why nothing on this seam can publish.
Four invariants that explain most of the design
Money is an integer in the minor unit of its currency, everywhere. Not at the edges — the
whole way through. 4500 with "currency": "CAD" is CA$45.00 in the database, in the domain
layer, in the order's frozen price breakdown, and in the unit_amount handed to Stripe. Nothing
converts on the way, and a fractional amount is rejected at the edge rather than rounded quietly.
The only conversion to major units happens at the display edge, when a number becomes a string a
human reads.
Every state change is audited, and the audit is written in the same transaction as the change. Not appended afterwards by a listener that might not run. The audit row, the domain events, and the entity write commit together or not at all. See the mutation pipeline.
The seller is the merchant of record. Marka never holds the money. Card payments are Stripe Connect direct charges on the seller's own connected account, and the marketplace application fee is fixed at zero — not discounted, structurally absent. See payments and rails.
Publishing is a human decision. Every programmatic surface — the API, the SDK, both MCP servers, the in-app AI assistant — can create and edit drafts. Publishing a listing is what makes it visible to buyers and commits a seller to selling the thing, and it is deliberately excluded from every AI surface. The HTTP API exposes it as its own endpoint with its own entitlement check, never as a field you can set.
Where this is written down
| Area | Code |
|---|---|
| Pure business logic, no I/O | packages/domain |
| The only package that touches Postgres | packages/db |
| Types, money helpers, the event vocabulary | packages/shared |
| Typesense indexing and querying | packages/search |
| eBay / Shopify / shipping adapters | packages/connectors-* |
| Durable background workers | apps/iii-engine/workers/* |
| The Mastra AI service | apps/ai |
| Web app, API routes, server actions | apps/web |
OpenCode
Connect OpenCode to Marka's MCP server at https://joinmarka.com/api/mcp with one remote entry in your user-level opencode.json. The key never lands in a commit.
The mutation pipeline
Every Marka domain mutation is a pure function's MutationBundle, committed in one Prisma transaction at Serializable isolation and retried on P2034 conflicts.