# Imajin Network (MJN Protocol) # llms-full.txt — Complete technical reference for AI consumption # v0.5 · March 2026 · DRAFT # Ryan Veteze · ryan@imajin.ai · github.com/ima-jin/imajin-ai # # This document is intended as a complete integration reference. # An LLM should be able to build a working client from this document alone. --- ## 1. What This Is MJN is a cryptographic operating system that runs as Just a Bunch Of Services (JBOS). 15 HTTP services. Each does one thing. Each has its own schema. Each is independently deployable. No service mesh. No orchestration framework. No API gateway. Just Next.js apps behind Caddy. The services are commodity. Swap them, rewrite them, add more. The value is in the cryptographic substrate underneath — signed identity chains that make every action verifiable and every relationship bilateral. Without the chains, each service is a dumb CRUD app. With the chains, they form a sovereign operating system where identity, attribution, trust, and settlement are structural properties — not features bolted on after the fact. --- ## 2. Architecture: JBOS on a Cryptographic Substrate ``` JBOS (Just a Bunch Of Services) ┌──────────────────────────────────────────────────────────┐ │ www · events · chat · learn · market · coffee · links │ ← Userspace │ dykil · input · media │ (disposable) ├──────────────────────────────────────────────────────────┤ │ connections · profile · registry │ ← Trust + Discovery ├──────────────────────────────────────────────────────────┤ │ auth · pay │ ← Kernel │ attestations · .fair manifests · settlement │ (signed chains) ├──────────────────────────────────────────────────────────┤ │ DFOS Proof Chains (L0–L5) │ ← Substrate │ Ed25519 · CID · dag-cbor · countersignatures │ (cryptographic) └──────────────────────────────────────────────────────────┘ ``` **Why JBOS works:** - No inter-service trust assumptions. Services verify chains, not each other. - Any service can be replaced without affecting others. - New services inherit the full trust graph and identity system by importing `@imajin/auth`. - The complexity lives in the chain layer, not the orchestration layer. - Runs on a single server with pm2. No Kubernetes. No Docker in production. ### Service URLs | Service | URL | Role | |-------------|-----------------------------|-----------------------------------------------| | auth | auth.imajin.ai | Kernel — DIDs, sessions, attestations, DFOS | | pay | pay.imajin.ai | Kernel — Stripe, settlement, .fair | | connections | connections.imajin.ai | Trust — pods, invites, groups, vouch | | profile | profile.imajin.ai | Discovery — public profiles, handles | | registry | registry.imajin.ai | Federation — node discovery, DFOS relay | | events | events.imajin.ai | Userspace — events, tickets, check-in | | chat | chat.imajin.ai | Userspace — DID-based messaging, WebSocket | | media | media.imajin.ai | Userspace — asset storage, .fair sidecars | | input | input.imajin.ai | Userspace — voice transcription, upload relay | | learn | learn.imajin.ai | Userspace — courses, modules, enrollment | | market | market.imajin.ai | Userspace — consent-based local commerce | | coffee | coffee.imajin.ai | Userspace — tipping / support pages | | links | links.imajin.ai | Userspace — link-in-bio pages | | dykil | dykil.imajin.ai | Userspace — community spending surveys | | www | imajin.ai | Userspace — landing page, app launcher | Every service exposes OpenAPI at `/api/spec`. Shared packages: `@imajin/auth` · `@imajin/db` · `@imajin/fair` · `@imajin/chat` · `@imajin/ui` · `@imajin/config` · `@imajin/llm` · `@imajin/media` --- ## 3. Identity: DIDs, Tiers, and DFOS ### DID Format Actor DIDs take two forms depending on how they were created: - **Soft DID**: `did:imajin:` — random stable identifier minted at email verification. No keypair. Cannot sign. A placeholder key (`soft_`) is stored in the DB. - **Hard DID**: `did:imajin:` — deterministically derived from the Ed25519 public key. Cryptographically self-certifying. Can sign attestations, messages, etc. Conversation DIDs (chat): - DM: `did:imajin:dm:` — deterministic from participants - Group: `did:imajin:group:` — server-generated - Event chat: `did:imajin:event:` or `did:imajin:evt_` ### Three Standing Tiers Standing is computed from attestation history — not assigned by an administrator. - **soft**: Email-verified only. No keypair. Can buy tickets, enroll in courses. Cannot send messages, sign attestations, or create listings. Upgrade path: keypair registration. - **preliminary**: Ed25519 keypair + invite code. Full trust graph access. Can send messages, create events and listings, issue attestations. - **established**: Earned through attestation history. Can vouch for others, invite new members. ### Four Identity Scopes - **Actor**: One DID, one keypair. Subtypes: HumanActor, AgentActor (child key, scoped), DeviceActor. - **Family**: Intimate shared trust. Shared resources, delegated authority. - **Community**: Shared practice. Quorum-governed, contribution-weighted. - **Business**: Structured entity. Roles, delegation chains. Cannot initiate connections — all outreach is consent-gated and gas-priced. ### DFOS Integration Every `did:imajin` can be anchored to a DFOS proof chain (protocol.dfos.com). Same Ed25519 keypair produces both `did:imajin:xxx` and `did:dfos:xxx` — byte-compatible, same curve. DFOS relay: `registry.imajin.ai/relay/` Chain is canonical. `did:imajin` is a stable alias. Identity is self-certifying — verifiable without Imajin's server. `chainVerified: true` in session response means a DFOS chain has been registered and verified for this DID. ``` MJN (L6) ← identity + attribution + trust + settlement DFOS (L0–L5) ← proof chains, key rotation, countersignatures HTTPS ← transport ``` --- ## 4. Authentication Three flows. All produce the same session cookie (`imajin_session`). All cookies are `HttpOnly; Secure; SameSite=Lax; Domain=.imajin.ai` — valid across all `*.imajin.ai` services. In development: cookie name is `imajin_session_dev`. ### Flow 1: Soft DID (Email Only) For users without a keypair. Creates or retrieves a soft DID from email. **Option A: Direct soft session** ``` POST https://auth.imajin.ai/api/session/soft Content-Type: application/json { "email": "user@example.com", "name": "Jane Smith" // optional } ``` Response (200): ```json { "did": "did:imajin:abc123...", "handle": null, "type": "human", "name": "Jane Smith", "tier": "soft" } ``` Sets cookie: `imajin_session=` Error (400): ```json { "error": "Valid email required" } ``` Error (429): ```json { "error": "Too many requests", "retryAfter": 30 } ``` **Option B: OnboardGate (email verification flow)** Used when the caller needs proof that the email was verified (not just provided). This is the standard flow for unauthenticated buyers, ticket holders, etc. Step 1 — send verification email: ``` POST https://auth.imajin.ai/api/onboard Content-Type: application/json { "email": "user@example.com", "name": "Jane Smith", // optional "redirectUrl": "https://events.imajin.ai/evt_123", // optional "context": "buy a ticket" // optional, shown in email } ``` Response (200): ```json { "sent": true } ``` Always returns `{ sent: true }` even if email not found (prevents enumeration). Step 2 — user clicks link, server completes: ``` GET https://auth.imajin.ai/api/onboard/verify?token=<48-char-token> ``` On success: HTTP 302 redirect to `redirectUrl` (or `/`), with cookie set. Token TTL: 15 minutes. Grace window: email security scanners (Outlook Safe Links, etc.) may pre-consume the GET. If the token was used within the last 60 seconds, a new session is issued anyway. **Option C: Magic Link Re-auth (soft DIDs only)** For returning users with a soft DID who need to re-authenticate. Hard DID accounts (keypair holders) cannot use magic links — returns 403. ``` POST https://auth.imajin.ai/api/magic/send Content-Type: application/json { "email": "user@example.com", "redirectUrl": "https://events.imajin.ai/evt_123" // optional } ``` Response (200): `{ "sent": true }` Error if hard DID (403): ```json { "error": "This account requires private key authentication. Use your backup key file to log in." } ``` --- ### Flow 2: Challenge-Response (Ed25519 Keypair) For users with a keypair. This is how preliminary and established DIDs authenticate. Step 1 — request a challenge: ``` POST https://auth.imajin.ai/api/login/challenge Content-Type: application/json { "handle": "alice" // OR "did": "did:imajin:abc123..." } ``` Response (200): ```json { "challengeId": "chl_abc123xyz", "challenge": "a1b2c3d4...hex...", "expiresAt": "2026-03-25T10:05:00.000Z" } ``` Challenge TTL: 5 minutes. Step 2 — sign the challenge with your Ed25519 private key and verify: ``` POST https://auth.imajin.ai/api/login/verify Content-Type: application/json { "challengeId": "chl_abc123xyz", "signature": "deadbeef...128-char-hex-ed25519-sig...", "dfosChain": { ... } // optional: DFOS chain for chain-verified session } ``` Signature: Ed25519 sign of the raw `challenge` hex string using your private key. Response (200): ```json { "did": "did:imajin:abc123...", "handle": "alice", "type": "human", "name": "Alice" } ``` Sets cookie: `imajin_session=` Error (401): `{ "error": "Invalid signature" }` Error (400): `{ "error": "Challenge not found, expired, or already used" }` --- ### Flow 3: Registration (New Keypair Identity) For first-time registration. Requires an invite code for human identities. Service accounts (type != 'human') bypass the invite gate. Generate an Ed25519 keypair. Derive the DID client-side: ``` did = "did:imajin:" + sha256(publicKey_bytes).hex() ``` Sign the registration payload: ```javascript const payload = JSON.stringify({ publicKey, handle, name, type }); const signature = ed25519.sign(payload, privateKey); // hex-encoded ``` ``` POST https://auth.imajin.ai/api/register Content-Type: application/json { "publicKey": "abc123...64-char-hex...", "handle": "alice", // optional, 3-30 chars [a-z0-9_] "name": "Alice", // optional "type": "human", // human|agent|presence|org|device|service|event "signature": "deadbeef...128-char-hex...", "inviteCode": "abc123def456" // required for human type "email": "alice@example.com",// optional, for contact "dfosChain": { ... } // optional: DFOS chain to anchor immediately } ``` Response (201): ```json { "did": "did:imajin:abc123...", "handle": "alice", "type": "human", "created": true, "inviteAccepted": true } ``` Sets cookie: `imajin_session=`. Tier is `preliminary` on successful registration. If public key already exists (re-register): returns existing DID with 200 + new session cookie. If handle is taken by a different key: returns 409 `{ "error": "Handle already taken" }`. If invite invalid or used: 403 `{ "error": "Invalid or expired invite code" }`. Registration side effects (non-fatal): - Creates profile row in profile service - Auto-accepts invite (creates connection in connections service) --- ## 5. Session Model ### Checking Current Session ``` GET https://auth.imajin.ai/api/session Cookie: imajin_session= ``` Response (200): ```json { "did": "did:imajin:abc123...", "handle": "alice", "type": "human", "name": "Alice", "role": "member", "tier": "soft" | "preliminary" | "established", "chainVerified": false } ``` `chainVerified: true` means a valid DFOS chain has been registered and verified for this DID. Error (401): `{ "error": "Not authenticated" }` ### Cross-Service Auth The same session cookie is valid across all `*.imajin.ai` services. All services validate sessions by forwarding the cookie to `AUTH_SERVICE_URL/api/session`. No service-to-service JWT, no shared secret — just the cookie and the auth service as the single source of truth. ### Bearer Token (Machine Clients) Services also accept `Authorization: Bearer ` for machine clients. Tokens are validated via `POST /api/validate`: ``` POST https://auth.imajin.ai/api/validate Content-Type: application/json { "token": "" } ``` Response: ```json { "valid": true, "identity": { "id": "did:imajin:abc123...", "type": "human", "name": "Alice", "metadata": { ... } } } ``` Invalid or expired: `{ "valid": false, "error": "Invalid or expired token" }`. ### Auth Middleware Hierarchy The `@imajin/auth` package exposes three middleware functions used by every service: | Function | Tier accepted | Use case | |-------------------------|------------------------------|----------------------------------| | `requireAuth(request)` | soft, preliminary, established | Most endpoints | | `requireHardDID(request)` | preliminary, established | Signing, trust-gated actions | | `requireEstablishedDID(request)` | established only | Vouching, advanced trust ops | All return `{ identity: Identity }` or `{ error: string, status: number }`. `Identity` shape: ```typescript { id: string; // DID type: "human" | "agent" | "presence"; name?: string; handle?: string; tier?: "soft" | "preliminary" | "established"; chainVerified?: boolean; } ``` In API routes, pattern is: ```typescript const authResult = await requireAuth(request); if ('error' in authResult) { return NextResponse.json({ error: authResult.error }, { status: authResult.status }); } const { identity } = authResult; ``` --- ## 6. Attestations Every trust-relevant action emits a cryptographically signed attestation. Unsigned attestations are rejected at write — a 4xx, not a null signature. ### Live Attestation Types ``` transaction.settled Payment processed with verified .fair chain (pay) customer First transaction with a service (pay) connection.invited Invite extended (connections) connection.accepted Invite accepted (connections) vouch Inviter sponsors acceptee into trust graph (connections) session.created Login event with auth method metadata (auth) ``` ### Attestation Shape ```json { "id": "att_abc123xyz", "issuerDid": "did:imajin:issuer...", "subjectDid": "did:imajin:subject...", "type": "connection.invited", "contextId": "inv_abc123", "contextType": "connection", "payload": { "delivery": "link" }, "signature": "deadbeef...128-char-hex...", "cid": "bafyrei...", "authorJws": null, "witnessJws": null, "attestationStatus": null, "issuedAt": "2026-03-25T10:00:00.000Z", "revokedAt": null } ``` - `cid`: content address (dag-cbor + CID) of the payload — enables DFOS chain anchoring. - `authorJws`: issuer's JWS (new-style bilateral attestations). Null for legacy. - `witnessJws`: subject's countersignature after `POST /api/attestations/countersign`. - `attestationStatus`: `null` (legacy) | `"pending"` | `"bilateral"` | `"declined"`. ### Signature Format Sign the canonicalized JSON of: ```json { "subject_did": "did:imajin:subject...", "type": "connection.invited", "context_id": "inv_abc123", "context_type": "connection", "payload": { "delivery": "link" }, "issued_at": 1742900000000 } ``` `canonicalize()` sorts keys deterministically (RFC 8785). `issued_at` is Unix milliseconds. Sign the resulting UTF-8 string with your Ed25519 private key. Result is 128-char hex. ### Creating an Attestation ``` POST https://auth.imajin.ai/api/attestations Cookie: imajin_session= (or Authorization: Bearer ) Content-Type: application/json { "issuer_did": "did:imajin:issuer...", "subject_did": "did:imajin:subject...", "type": "vouch", "context_id": "inv_abc123", // optional "context_type": "connection", // optional "payload": { "note": "great colleague" }, // optional "signature": "deadbeef...128-char-hex...", "issued_at": 1742900000000 // optional, defaults to now } ``` Response (201): the full attestation object. Error (400): `{ "error": "Invalid type. Must be one of: transaction.settled, ..." }` Error (400): `{ "error": "Invalid signature" }` Error (401): `{ "error": "Not authenticated" }` ### Querying Attestations By query params: ``` GET https://auth.imajin.ai/api/attestations?subject_did=did:imajin:subject...&type=vouch&limit=20 ``` Params: `subject_did` (required), `type`, `issuer_did`, `status`, `limit` (max 100). Returns array of attestation objects, newest first. Non-revoked only. Shorthand (subject DID in path): ``` GET https://auth.imajin.ai/api/attestations/did:imajin:subject... ``` ### Countersigning (Bilateral Attestations) Only the attestation subject can countersign. Transitions status from `pending` → `bilateral`. ``` POST https://auth.imajin.ai/api/attestations/countersign Cookie: imajin_session= Content-Type: application/json { "attestationId": "att_abc123xyz", "witnessJws": "" } ``` Response (200): ```json { "id": "att_abc123xyz", "cid": "bafyrei...", "status": "bilateral" } ``` Error (409): `{ "error": "Cannot countersign — attestation is bilateral" }` (already done) Error (403): `{ "error": "Only the attestation subject can countersign" }` --- ## 7. .fair Attribution Every asset carries a `.fair` manifest: who contributed, in what proportion, under what terms. Unsigned manifests are rejected at settlement. ### Manifest Format ```json { "id": "asset-123", "version": "0.3.0", "type": "track", "contributors": [ { "id": "did:imajin:5Qn8...", "role": "artist", "weight": 0.6 }, { "id": "did:imajin:8Xk2...", "role": "producer", "weight": 0.4 } ], "terms": { "train": { "consent": "explicit", "price": 0.50 } }, "signature": "...", "platformSignature": "..." } ``` Multi-contributor manifests require each listed contributor's signature. With DFOS chain backing, creator proof is portable and verifiable independent of Imajin's server. ### Media Service .fair Sidecars When a file is uploaded to media service, a `.fair` sidecar is generated and stored alongside: ```json { "fair": "1.0", "id": "asset_xxx", "type": "image/jpeg", "owner": "did:imajin:uploader...", "created": "2026-03-25T10:00:00.000Z", "source": "upload", "access": { "type": "private" }, "attribution": [ { "did": "did:imajin:uploader...", "role": "creator", "share": 1.0, "chainProof": { ... } } ], "transfer": { "allowed": false } } ``` Access types: `"public"` (anyone can fetch), `"private"` (owner only), `"trust-graph"` (owner + allowedDids list). ### Market .fair Manifest (Auto-Generated) On purchase, a distribution manifest is built: ```json { "version": "1.0", "type": "market:purchase", "distributions": [ { "did": "did:imajin:seller...", "share": 0.99, "role": "seller" }, { "did": "did:imajin:platform", "share": 0.01, "role": "platform" } ] } ``` Custom `.fair` manifests on listings override this default. ### Settlement Every transaction verifies .fair signatures before processing. Splits follow the manifest. On completion, a `transaction.settled` attestation is emitted. Platform fee: configurable `platformFeeBps` on each connected Stripe account. Default: `DEFAULT_PLATFORM_FEE_BPS` (defined in pay service lib). --- ## 8. Services Reference ### 8.1 Auth Service (auth.imajin.ai) Auth is the kernel. All sessions originate here. All other services validate against it. **Identity Resolution** ``` GET https://auth.imajin.ai/api/identity/did:imajin:abc123 ``` Response (200): ```json { "did": "did:imajin:abc123...", "publicKey": "64-char-hex-ed25519-pubkey", "type": "human", "tier": "preliminary", "dfosDid": "did:dfos:abc..." // if chain registered } ``` Error (404): `{ "error": "Identity not found" }` **Lookup by DID (with profile data)** ``` GET https://auth.imajin.ai/api/lookup/did:imajin:abc123 ``` Response (200): ```json { "did": "did:imajin:abc123...", "handle": "alice", "name": "Alice", "type": "human", "tier": "preliminary", "avatarUrl": "https://media.imajin.ai/api/assets/asset_xxx", "metadata": { ... }, "createdAt": "2026-01-01T00:00:00.000Z" } ``` **DFOS Chain** ``` GET https://auth.imajin.ai/api/identity/did:imajin:abc123/chain ``` Response (200): ```json { "did": "did:imajin:abc123...", "dfosDid": "did:dfos:abc...", "log": ["eyJ...", "eyJ..."], "headCid": "bafyrei...", "keyCount": 3, "isDeleted": false } ``` **Chain Verification** ``` GET https://auth.imajin.ai/api/identity/did:imajin:abc123/verify ``` Response (200): ```json { "did": "did:imajin:abc123...", "dfosDid": "did:dfos:abc...", "hasDfosChain": true, "chainValid": true, "chainError": null, "chainLength": 3, "currentKeys": { "auth": 1, "assert": 1, "controller": 1 }, "dbConsistent": true, "isDeleted": false } ``` If no chain: `{ "hasDfosChain": false, "message": "Identity exists but has no DFOS chain" }` **Access Check** Determines whether the authenticated requester has access to the given resource DID. Used by chat service before serving messages. ``` GET https://auth.imajin.ai/api/access/did:imajin:event:evt_123 Cookie: imajin_session= ``` Response (200): ```json { "allowed": true, "role": "attendee", "governance": "ticket" } ``` Or: `{ "allowed": false }` Governance values: - `"ticket"` — event access via ticket ownership - `"owner"` — event access as creator/organizer - `"dm"` — direct message participant - `"group"` — group conversation member - `"member"` — pod member Rules by DID namespace: - `did:imajin:event:*` or `did:imajin:evt_*`: checks `events.tickets` for ownerDid, falls back to creator check - `did:imajin:dm:*`: allows access if conversation doesn't exist yet (DID is proof of membership), else checks participation - `did:imajin:group:*`: checks conversation_members and pod_members Error (401): `{ "error": "Not authenticated" }` --- ### 8.2 Chat Service (chat.imajin.ai) DID-keyed conversations. Every conversation is addressed by a DID. Auth service is the single access control authority. **Send Message** ``` POST https://chat.imajin.ai/api/d/did:imajin:dm:abc123/messages Cookie: imajin_session= Content-Type: application/json { "content": { "text": "Hello!" }, "contentType": "text", "replyToMessageId": "msg_abc", // optional "mediaType": "image", // optional "mediaPath": null, // optional, legacy "mediaAssetId": "asset_xxx", // optional, new-style "mediaMeta": { "width": 1920 }, // optional "conversationName": "Event Lobby" // optional, only on first message } ``` `content` must be an object (shape is determined by contentType). `contentType`: `"text"` | `"system"` | `"media"` | `"voice"` | `"location"` Requires tier != `soft`. Soft DIDs get 403: `{ "error": "Please verify your account to send messages" }`. Access is verified via `GET auth.imajin.ai/api/access/:did` — 403 if denied. Conversations are auto-created on first message if they don't exist. Response (201): ```json { "message": { "id": "msg_abc123", "conversationDid": "did:imajin:dm:abc123...", "fromDid": "did:imajin:sender...", "content": { "text": "Hello!" }, "contentType": "text", "replyToMessageId": null, "mediaType": null, "mediaPath": null, "mediaAssetId": null, "mediaMeta": null, "signature": "deadbeef...", "linkPreviews": [], "createdAt": "2026-03-25T10:00:00.000Z", "deletedAt": null, "reactions": [] } } ``` Message signatures: best-effort Ed25519 signed by the node's chain key. Never blocks delivery. Link previews: extracted async after response, then broadcast via WebSocket as `message_updated`. **List Messages** ``` GET https://chat.imajin.ai/api/d/did:imajin:dm:abc123/messages?limit=50&before=msg_abc Cookie: imajin_session= ``` `limit`: 1–100 (default 50). `before`: message ID for cursor-based pagination (returns messages older than that message). Response (200): ```json { "messages": [ { ...message + reactions array... } ], "hasMore": true } ``` Messages returned in chronological order (oldest first). `hasMore: true` means more pages exist. --- ### 8.3 WebSocket Protocol (chat.imajin.ai) Real-time messaging. Connect to `wss://chat.imajin.ai/ws`. **Connection** Authentication can happen two ways: 1. Cookie-based (automatic): WebSocket upgrade request includes the session cookie. If valid, server sends `{ "type": "connected" }` immediately. 2. Token-based (deferred): Connect without cookie. Server sends `{ "type": "auth_required" }`. Client sends auth message. Server validates and sends `{ "type": "connected" }` or closes. **Client → Server Messages** ```json // Deferred auth (token-based) { "type": "auth", "token": "" } // Subscribe to a conversation or DID { "type": "subscribe", "conversationId": "did:imajin:dm:abc123..." } { "type": "subscribe", "did": "did:imajin:dm:abc123..." } // legacy alias // Typing indicator { "type": "typing", "conversationId": "did:imajin:dm:abc123...", "name": "Alice" } // Stop typing { "type": "stop_typing", "conversationId": "did:imajin:dm:abc123..." } // Keep-alive { "type": "ping" } ``` **Server → Client Messages** ```json // Connection established { "type": "connected" } // Auth required (no cookie on upgrade) { "type": "auth_required" } // New message delivered { "type": "new_message", "message": { ...full message object... } } // Message updated (e.g. link previews loaded) { "type": "message_updated", "message": { ...updated message... } } // Typing indicator from peer { "type": "user_typing", "conversationId": "did:imajin:dm:abc123...", "did": "did:imajin:peer...", "name": "Bob" } // Peer stopped typing { "type": "user_stop_typing", "conversationId": "did:imajin:dm:abc123...", "did": "did:imajin:peer..." } // Presence change { "type": "user_presence", "did": "did:imajin:peer...", "online": true, "lastSeen": null } { "type": "user_presence", "did": "did:imajin:peer...", "online": false, "lastSeen": "2026-03-25T10:00:00.000Z" } // Pong { "type": "pong" } // Error { "type": "error", "message": "Not authenticated. Send auth message first." } ``` **Notes:** - Typing indicators auto-expire after 5 seconds of silence. - Presence is tracked per-DID (multiple tabs share a DID; last tab closing triggers offline). - Subscription is per-conversation DID. Subscribe after connecting to receive messages. - Internal broadcast: HTTP API routes call `POST /__ws_broadcast` (internal, same port) to push messages through the WebSocket server. --- ### 8.4 Market Service (market.imajin.ai) Consent-based local commerce. Three seller tiers control how purchase is handled: | Tier | Description | |---------------------|----------------------------------------------------| | `public_offplatform` | Contact info required; no on-platform checkout | | `public_onplatform` | Stripe checkout; any authenticated user | | `trust_gated` | Stripe checkout; requires hard DID (keypair) | **Create Listing** ``` POST https://market.imajin.ai/api/listings Cookie: imajin_session= Content-Type: application/json { "title": "Vintage Leather Jacket", "description": "Size L, barely worn", "price": 15000, // cents (CAD 150.00) "currency": "CAD", // default "category": "clothing", "images": ["asset_xxx"], // up to 8, asset IDs or URLs "imageAssetIds": ["asset_xxx"], // new-style asset IDs "quantity": 1, "sellerTier": "public_onplatform", "contactInfo": { // required for public_offplatform "phone": "+1-555-555-5555", "email": "seller@example.com", "whatsapp": "+1-555-555-5555" }, "trustThreshold": null, "rangeKm": 50, "metadata": {}, "type": "sale", // sale | rent "showContactInfo": false, "expiresAt": null } ``` Response (201): full listing object. Validation errors (400): - `title` required - `price` must be > 0 - `images` max 8 items - `public_offplatform` requires at least one of contactInfo.phone/email/whatsapp **Browse Listings** ``` GET https://market.imajin.ai/api/listings?category=clothing&status=active¤cy=CAD&seller_tier=public_onplatform&seller_did=did:imajin:abc&sort=newest&page=1&limit=20 ``` Params: - `status`: active (default) | paused | sold | rented | removed - `sort`: `newest` (default) | `price_asc` | `price_desc` - `seller_did`: filter by seller (authenticated seller sees all their own statuses) - `exclude`: listing ID to exclude (for "other items by seller" queries) - Unauthenticated requests: `trust_gated` listings automatically excluded Response (200): ```json { "listings": [ { ...listing, "images": ["https://media.imajin.ai/api/assets/asset_xxx?w=400"] } ], "total": 42, "page": 1, "limit": 20, "hasMore": true } ``` Images are resolved via `resolveMediaRef(ref, 'card')` — asset IDs become full URLs at 400px. **Get Listing** ``` GET https://market.imajin.ai/api/listings/lst_abc123 ``` Trust-gated listings return 403 for unauthenticated requests: ```json { "error": "This listing is only available to verified members", "gated": true } ``` Images in detail view are resolved at 800px (`detail` preset). **Update Listing (seller only)** ``` PATCH https://market.imajin.ai/api/listings/lst_abc123 Cookie: imajin_session= Content-Type: application/json { "status": "paused", "price": 12000 } ``` Status transition table: ``` active → paused | sold | rented | unavailable paused → active | removed unavailable → active | removed sold → removed rented → removed removed → (terminal) ``` Response (200): updated listing object. Error (400): `{ "error": "Cannot transition listing from 'active' to 'removed'. Allowed: paused, sold, rented, unavailable" }` **Delete Listing (soft delete)** ``` DELETE https://market.imajin.ai/api/listings/lst_abc123 Cookie: imajin_session= ``` Sets status to `removed`. Response: `{ "success": true }` **Purchase Listing** ``` POST https://market.imajin.ai/api/listings/lst_abc123/purchase Cookie: imajin_session= Content-Type: application/json { "quantity": 1 // optional, defaults to 1 } ``` - `public_offplatform`: returns 400 (contact seller directly) - `public_onplatform`: any authenticated user - `trust_gated`: requires hard DID (preliminary/established), else 403 Response (200): ```json { "url": "https://checkout.stripe.com/pay/cs_live_xxx", "sessionId": "cs_live_xxx" } ``` Redirect the user to `url`. Market delegates entirely to pay service (sovereign node model). A pending transaction record is created in pay service's DB. --- ### 8.5 Pay Service (pay.imajin.ai) Stripe-backed settlement. All services delegate payment to pay service. **Create Checkout Session** ``` POST https://pay.imajin.ai/api/checkout Content-Type: application/json { "items": [ { "name": "Vintage Leather Jacket", "description": "Size L", // optional "amount": 15000, // cents, integer, 50–99,999,900 "quantity": 1, // integer, 1–100 "image": "https://..." // optional } ], "currency": "CAD", // USD | CAD | EUR | GBP "mode": "payment", // optional: payment | subscription "customerEmail": "user@example.com", // optional "successUrl": "https://market.imajin.ai/checkout/success?session_id={CHECKOUT_SESSION_ID}", "cancelUrl": "https://market.imajin.ai/listings/lst_abc", "metadata": { "service": "market", "listingId": "lst_abc", "sellerDid": "did:imajin:seller...", "buyerDid": "did:imajin:buyer..." }, "fairManifest": { ... }, // optional "sellerDid": "did:imajin:seller..." // optional: resolves to Stripe connected account } ``` `sellerDid` lookup: if the seller has a connected Stripe account (`chargesEnabled: true`), the session routes through that account with a platform fee applied. Response (200): ```json { "id": "cs_live_xxx", "url": "https://checkout.stripe.com/pay/cs_live_xxx", "expiresAt": "2026-03-25T11:00:00.000Z", "transactionId": "tx_abc123" } ``` Error cases: - 400: `{ "error": "items array is required" }` - 400: `{ "error": "items[0].amount must be >= 50 (Stripe minimum is 50 cents)" }` - 400: `{ "error": "items[0].quantity must be <= 100" }` - 400: `{ "error": "Seller hasn't completed payment setup" }` (no chargesEnabled on connected account) - 429: rate-limited (10 requests per 60 seconds) --- ### 8.6 Connections Service (connections.imajin.ai) Trust graph. Pods are the building block — a pod is a trusted group of DIDs. **Invite System** Two delivery modes: - `link`: a shareable invite URL anyone with the link can use. Role-based limits on pending count. - `email`: sends directly to a specific email. Requires hard DID + trust graph membership. Invite limits by role (link invites only): ``` admin: unlimited legendary: 10 pending trusted: 5 pending member: 3 pending (default) newbie: 1 pending ``` Create invite: ``` POST https://connections.imajin.ai/api/invites Cookie: imajin_session= Content-Type: application/json // Link invite: { "delivery": "link", "note": "Welcome to the community", // optional "maxUses": 1 // optional } // Email invite: { "delivery": "email", "toEmail": "friend@example.com", "note": "Hey! Join us on Imajin." } ``` Response (201): ```json { "invite": { "id": "inv_abc123", "code": "abc123def456...", "fromDid": "did:imajin:sender...", "fromHandle": "alice", "toEmail": null, "delivery": "link", "status": "pending", "maxUses": 1, "expiresAt": null }, "url": "https://connections.imajin.ai/invite/did:imajin:alice.../abc123...", "remaining": 2 } ``` Email invite emits `connection.invited` attestation. Issues cooldown (7 days, one pending email invite at a time). List invites: ``` GET https://connections.imajin.ai/api/invites Cookie: imajin_session= ``` Response: ```json { "invites": [ { "id": "inv_abc", "code": "...", "delivery": "link", "status": "accepted", "acceptedBy": "Bob", "acceptedHandle": "bob", "daysAgo": 3, "url": "https://..." } ], "role": "member", "limit": 3, "pending": 1, "remaining": 2 } ``` **Pods (Trust Groups)** Pods are the unit of trust graph membership. Being in any 2-person pod means you're in the trust graph. Create pod (requires trust graph membership): ``` POST https://connections.imajin.ai/api/pods Cookie: imajin_session= Content-Type: application/json { "name": "Design Team", "description": "Our creative core", // optional "avatar": "asset_xxx", // optional "type": "personal", // personal | team | community "visibility": "private" // private | public } ``` Response (201): `{ "pod": { "id": "pod_abc", "name": "...", "ownerDid": "...", ... } }` Creator is added as `role: "owner"` automatically. List pods (own membership): ``` GET https://connections.imajin.ai/api/pods Cookie: imajin_session= ``` Response: `{ "pods": [ { ...pod... } ] }` --- ### 8.7 Media Service (media.imajin.ai) Asset storage with .fair access control and on-the-fly resizing. **Upload Asset** ``` POST https://media.imajin.ai/api/assets Cookie: imajin_session= Content-Type: multipart/form-data file= // required, max 50MB filename=photo.jpg // optional (uses file.name if omitted) context={"app":"market","feature":"events","entityId":"evt_123","access":"public"} // optional JSON string ``` Allowed types: images, audio, video, text, PDF, markdown, CSV, JSON, YAML, SVG. Deduplication: same hash + same owner → returns existing asset with `"deduplicated": true`. Response (201): ```json { "id": "asset_abc123", "url": "https://media.imajin.ai/api/assets/asset_abc123", "filename": "photo.jpg", "mimeType": "image/jpeg", "size": 204800, "hash": "sha256hex...", "storagePath": "/mnt/media/did_imajin_abc/assets/asset_abc123.jpg", "fairManifest": { "fair": "1.0", "access": { "type": "private" }, ... }, "createdAt": "2026-03-25T10:00:00.000Z" } ``` Context auto-creates system folders: ``` bugs → "Bug Reports" folder chat → "Chat" folder profile → "Profile" folder events → "Events" folder market → "Profile" folder voice → "Audio Recordings" folder ``` **Retrieve Asset** ``` GET https://media.imajin.ai/api/assets/asset_abc123 GET https://media.imajin.ai/api/assets/asset_abc123?w=400 GET https://media.imajin.ai/api/assets/asset_abc123?download=true ``` `?w=`: on-the-fly resize (images only, powered by Sharp). Cached separately from original. Access control from .fair manifest: - `public`: no auth required - `private`: session cookie required, owner only - `trust-graph`: session required, must be in `allowedDids` Response headers: - `Content-Type: image/jpeg` - `ETag: "sha256hex"` (resized: `"sha256hex-w400"`) - `Cache-Control: public, max-age=86400` (or `private, max-age=3600` for non-public) - `X-Fair-Access: public` Supports `If-None-Match` for conditional GET → 304 Not Modified. **Delete Asset** ``` DELETE https://media.imajin.ai/api/assets/asset_abc123 Cookie: imajin_session= ``` Owner only. Hard deletes both file and DB record. Response: `{ "ok": true }` **Rename Asset** ``` PATCH https://media.imajin.ai/api/assets/asset_abc123 Cookie: imajin_session= Content-Type: application/json { "filename": "new-name.jpg" } ``` **List Assets** ``` GET https://media.imajin.ai/api/assets?type=image&search=photo&sort=created&order=desc&limit=50&offset=0 Cookie: imajin_session= ``` Returns assets owned by authenticated user. Internal API (machine-to-machine): ``` GET https://media.imajin.ai/api/assets Authorization: Bearer X-Owner-DID: did:imajin:target... ``` **Media URL Resolution (client-side, `@imajin/media`)** ```typescript import { resolveMediaRef } from '@imajin/media'; resolveMediaRef("asset_abc123") // original URL resolveMediaRef("asset_abc123", "thumbnail") // ?w=200 resolveMediaRef("asset_abc123", "card") // ?w=400 resolveMediaRef("asset_abc123", "detail") // ?w=800 resolveMediaRef("asset_abc123", "og") // /og (1200×630) resolveMediaRef("asset_abc123", 600) // ?w=600 resolveMediaRef("https://...", "card") // returned unchanged (not an asset ID) ``` Only `asset_xxx` prefixed strings are processed. Legacy URLs pass through unchanged. --- ### 8.8 Events Service (events.imajin.ai) Events, tickets, check-in, and .fair splits. Key routes: ``` GET /api/events list/search events POST /api/events create event GET /api/events/:id event detail PATCH /api/events/:id update event (organizer) DELETE /api/events/:id delete event GET /api/events/mine authenticated user's events GET /api/events/by-did/:did resolve event by its DID GET /api/events/:id/my-ticket current user's ticket for this event GET /api/events/:id/tiers ticket tiers POST /api/events/:id/tiers create ticket tier POST /api/events/:id/tickets/:tid/check-in check in a ticket POST /api/events/:id/tickets/:tid/refund refund a ticket POST /api/events/:id/tickets/:tid/resend-email resend ticket email POST /api/events/:id/invites create event invite GET /api/events/:id/invites list invites for event POST /api/events/:id/fair update .fair manifest GET /api/events/:id/queue queue management POST /api/events/:id/hold place hold on tickets GET /api/events/:id/guests guest list GET /api/events/:id/admins admin list GET /api/events/:id/cohosts cohost list ``` Event DIDs are assigned on creation. The event chat conversation shares this DID: `did:imajin:event:`. Access to the conversation is gated by ticket ownership. --- ## 9. Integration Patterns ### Adding a New Service Every new service follows the same pattern: 1. Import `@imajin/auth` for authentication: ```typescript import { requireAuth, requireHardDID } from '@imajin/auth'; export async function POST(request: Request) { const authResult = await requireAuth(request); if ('error' in authResult) { return Response.json({ error: authResult.error }, { status: authResult.status }); } const { identity } = authResult; // identity.id, identity.tier, etc. // ... } ``` 2. Set `AUTH_SERVICE_URL` in environment: ``` AUTH_SERVICE_URL=https://auth.imajin.ai ``` 3. Read session in server components via `getSession()`: ```typescript import { getSession } from '@imajin/auth'; // In Next.js Server Components only const identity = await getSession(); // reads from next/headers cookie store ``` 4. Emit attestations via `emitAttestation()`: ```typescript import { emitAttestation } from '@imajin/auth'; await emitAttestation({ issuer_did: identity.id, subject_did: targetDid, type: 'connection.invited', context_id: resourceId, context_type: 'connection', payload: { ... }, }).catch(err => console.error('Attestation error:', err)); ``` The `emitAttestation` helper handles signing via the node's keypair and posting to `AUTH_SERVICE_URL/api/attestations`. ### Consuming Identity Resolve handle → DID → profile: ```bash # Lookup by DID curl https://auth.imajin.ai/api/lookup/did:imajin:abc123 # Identity (public key + tier) curl https://auth.imajin.ai/api/identity/did:imajin:abc123 # Full chain (portable, self-verifying) curl https://auth.imajin.ai/api/identity/did:imajin:abc123/chain ``` ### Cross-Service Cookie Propagation When service A needs to call service B on behalf of the user, forward the session cookie: ```typescript const response = await fetch(`${EVENTS_SERVICE_URL}/api/events`, { headers: { Cookie: request.headers.get('cookie') || '', 'Content-Type': 'application/json', }, }); ``` Or after minting a fresh session token (for server-to-server initiated calls): ```typescript const cookieConfig = getSessionCookieOptions(); const token = await createSessionToken({ sub: did, type: 'human', tier: 'preliminary' }); await fetch(`${PROFILE_SERVICE_URL}/api/profile`, { method: 'POST', headers: { Cookie: `${cookieConfig.name}=${token}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ ... }), }); ``` ### Rate Limiting All public endpoints implement in-memory sliding-window rate limiting. Default limits per route: - Session/soft: 10 per minute - Onboard/magic: 5 per minute - Register: 5 per minute - Login/challenge: 10 per minute - Checkout: 10 per minute - Media upload: 20 per minute Exceeded: ```json { "error": "Too many requests", "retryAfter": 30 } ``` Header: `Retry-After: 30` ### CORS All kernel services (`auth`, `pay`) return CORS headers via `corsHeaders(request)`. CORS is configured per origin (not wildcard) and respects the `Origin` header. Preflight requests get `OPTIONS` handlers returning `204`. --- ## 10. Cryptographic Stack | Primitive | Purpose | |-----------------|---------------------------------------------------------------| | Ed25519 | DID keypairs, attestation signing, .fair signing, session auth | | SHA-256 | Content hashing, hard DID derivation | | dag-cbor + CID | DFOS content addressing (attestation CIDs) | | JWS | DFOS signature format for chain operations | | DFOS chain | Identity anchoring, key rotation, countersignatures | | JWT | Session cookies (HS256, signed by auth service) | | nanoid | Opaque IDs for soft DIDs, attestations, invites, assets | All persistence: Postgres. Per-service schemas in a shared database. Schema names match service name (e.g., `events.*`, `chat.*`, `connections.*`, `profile.*`). Key encodings: - Ed25519 private key: 32-byte seed, hex-encoded (64 chars) - Ed25519 public key: 32-byte point, hex-encoded (64 chars) - Ed25519 signature: 64 bytes, hex-encoded (128 chars) - DFOS keys also supported in multibase (`z` prefix, base58btc) The same Ed25519 keypair produces both `did:imajin:xxx` and `did:dfos:xxx`. Byte-compatible. No derivation. Your DID is also a valid Solana wallet address. --- ## 11. The Five Primitives Everything in MJN reduces to five primitives. They compose. ### 1. Attestation Every trust-relevant act emits a cryptographically signed record. Unsigned attestations are rejected at write — a 4xx, not a null signature. On onboarding, both node and Actor sign a root record (bilateral). Every subsequent attestation references this root. DFOS countersignatures anchor it to chain. Standing computation: ``` standing(did, scope?) = f( positive_attestations, // weighted by type, issuer_type, issuer standing negative_attestations, // flags weighted by severity, time-decayed recency_weights, // time-decay over issued_at issuer_standing, // recursive: issuer standing affects weight node_context // optional: scoped to specific community ) ``` ### 2. Communication DID-based messaging. Every conversation is itself a DID: - `did:imajin:dm:` — direct message (deterministic from sorted participant DIDs) - `did:imajin:group:` — group channel (random, server-generated) - `did:imajin:event:` — event conversation Messages signed by sender DID. Auth service is the single access control authority. Real-time via WebSocket. ### 3. Attribution (.fair) Every asset carries a `.fair` manifest: who contributed, in what proportion, under what terms. Cryptographically signed with Ed25519. ### 4. Settlement Every transaction verifies .fair signatures before processing. Splits follow the manifest. Completion emits a `transaction.settled` attestation. Current: Stripe Connect (multi-seller payouts), EMT (bank transfer). Planned: MJN token on Solana. Dual-currency (fiat or MJN). Atomic .fair splits. Gas model for declared-intent marketplace: - Tier 1 (trust graph connections): free / near-free - Tier 2 (declared interest pool, local match): medium gas - Tier 3 (extended reach, opted-in): high gas Frequency-scaled depth gating: 1st = 1×, 2nd = 1.5×, 3rd = 3×, 4th = 7×, 5th = 15×, 6th+ = 40×. ### 5. Discovery Federated registry at `registry.imajin.ai`. Nodes announce presence, operators, and served scopes. Node DIDs are chain-verified on registration. Trust-gated queries: access to a node's resources is gated by trust graph position. k-anonymity enforced. Matching is local — declared interests never leave the node. Exit credentials: signed portable credential with public summary (aggregate stats) and encrypted context (full attestation history, encrypted under departing Actor's public key). --- ## 12. What Imajin Rejects - **Subscriptions.** You own it forever. - **Cloud dependency.** Self-hosted, privacy-first. The reference runs on a single HP ProLiant. - **Vendor lock-in.** Open firmware. Open source. Every service replaceable. - **Surveillance capitalism.** Your data stays on your node. - **Planned obsolescence.** Repairable. Expandable. No forced updates. - **Orchestration theater.** No Kubernetes. No service mesh. No API gateway. JBOS. ### The Sovereignty Property A node doesn't need the registry to exist — only to be discoverable. If the registry dies, you keep your keypair, your chain proofs, your attestation history. The exit door is always open. Federation is the honest architecture: central registry for discovery (ships fast, proves concept), with a path to on-chain registry (Solana), then mesh trust (no central authority). Every interaction is typed (human/agent/device) so you always know what you're talking to. Agents use the same DID format, same keypairs, same trust graph. Structural honesty, not policy. --- ## 13. Build Stats (March 2026) | Metric | Value | |-------------------------------|---------------------------------------| | Services | 15 | | Lines of code | 122,721 | | Registered identities | ~120 | | Issues closed | 235 | | Days building | 50 | | Builders | 1 human + AI | | Traditional estimate (COCOMO) | $2.3M / 14.4 months / 9.2 people | | Actual cost | $76,445 / 50 days / 1 person | --- ## 14. What's Live (March 2026) - ✅ 15-service JBOS running on single server - ✅ Three-tier DID identity (soft/preliminary/established) - ✅ DFOS chain-backed identity across all services - ✅ DFOS relay at registry.imajin.ai/relay/ - ✅ Bilateral attestations via DFOS countersignatures - ✅ 6 live attestation types with Ed25519 signing - ✅ .fair cryptographic signing at settlement - ✅ Stripe Connect multi-seller payouts - ✅ Trust graph (pods, invites, groups, vouches) - ✅ DID-based real-time chat with WebSocket - ✅ Federation registry - ✅ MJN token reserved on Solana (12rXuUVzC71zoLrqVa3JYGRiXkKrezQLXB7gKkfq9AjK) - ✅ Auth integration test suite (43 tests) - ✅ Market service (listings, trust-gated checkout) - ✅ OnboardGate for unauthenticated buyer flows ## 15. What's Next - ⏳ Standing computation from attestation history - ⏳ Portable exit credentials - ⏳ MJN token settlement on Solana (dual-currency) - ⏳ Declared-intent marketplace with gas model - ⏳ Family / Community / Business DID scopes - ⏳ Encrypted chat (key epoch model) - ⏳ Node registration via DFOS relay (first boot flow) - ⏳ Gossip protocol via heartbeat extension - ⏳ First federated node outside the reference server --- ## 16. Quick Reference - Source: github.com/ima-jin/imajin-ai - Whitepaper: imajin.ai/mjn-whitepaper - Developer Guide: imajin.ai/developer-guide - .fair spec: github.com/ima-jin/.fair - DFOS protocol: protocol.dfos.com - DFOS relay: registry.imajin.ai/relay/ - OpenAPI specs: https://.imajin.ai/api/spec - First demonstration: April 1, 2026 ### Common curl Flows ```bash # 1. Create soft DID session curl -c cookies.txt -X POST https://auth.imajin.ai/api/session/soft \ -H "Content-Type: application/json" \ -d '{"email":"user@example.com","name":"Jane"}' # 2. Get current session curl -b cookies.txt https://auth.imajin.ai/api/session # 3. Request challenge for keypair auth curl -X POST https://auth.imajin.ai/api/login/challenge \ -H "Content-Type: application/json" \ -d '{"handle":"alice"}' # 4. Verify challenge signature curl -c cookies.txt -X POST https://auth.imajin.ai/api/login/verify \ -H "Content-Type: application/json" \ -d '{"challengeId":"chl_xxx","signature":"deadbeef..."}' # 5. Create attestation curl -b cookies.txt -X POST https://auth.imajin.ai/api/attestations \ -H "Content-Type: application/json" \ -d '{"issuer_did":"did:imajin:issuer...","subject_did":"did:imajin:subject...","type":"vouch","signature":"..."}' # 6. Send a message curl -b cookies.txt -X POST "https://chat.imajin.ai/api/d/did:imajin:dm:abc123.../messages" \ -H "Content-Type: application/json" \ -d '{"content":{"text":"Hello!"},"contentType":"text"}' # 7. Upload an image curl -b cookies.txt -X POST https://media.imajin.ai/api/assets \ -F "file=@photo.jpg" \ -F 'context={"app":"market","access":"public"}' # 8. Create a listing curl -b cookies.txt -X POST https://market.imajin.ai/api/listings \ -H "Content-Type: application/json" \ -d '{"title":"Jacket","price":15000,"currency":"CAD","sellerTier":"public_onplatform"}' # 9. Purchase a listing curl -b cookies.txt -X POST https://market.imajin.ai/api/listings/lst_abc/purchase \ -H "Content-Type: application/json" \ -d '{"quantity":1}' # 10. Create invite link curl -b cookies.txt -X POST https://connections.imajin.ai/api/invites \ -H "Content-Type: application/json" \ -d '{"delivery":"link"}' ``` --- # END OF DOCUMENT