The Offers System lets content creators (buyers / brands) submit licensing offers to account owners (sellers / talents) for use of their IP — AI-generated images for ads, voice-overs for radio spots, video, music, and so on. Brands discover talents via Z-links (e.g. /z/alex-rivera-a3f2), pick a campaign type, fill out the brief, optionally generate candidate images or voice reads, and submit. Talents review offers in their Agent dashboard and can accept, reject, or counter — including multi-field counters (price, usage, sharing, script, picked sample, picked voice, image kind), not just price. Brands then authorize and capture payment via Stripe; talents deliver; brands mark complete (or the auto-release timer does it for them).
buyerAccountId is resolved from the session cookie; perspective=admin requires the admin role.OFFER_TRANSITIONS (packages/types/src/types/Offer.ts). See Architecture for the diagram.saveAsDraft: true, resume from /dashboard/drafts, and submit when ready. Drafts are exempt from the "candidate session required" rule.IMAGE campaigns fan out to multiple image models (OpenAI, Nano-Banana, Flux); VOICE campaigns generate voice reads; VIDEO requires both. The brand picks a model key (and optionally flips imageKind between DRAFT/FINAL) at submit. skipImageGeneration lets a brand bypass image generation.*_minor_unit (integer) plus a currency ISO 4217 code. Zero-decimal currencies handled in packages/util/src/currency.ts. (6a07f00f)counter_changes jsonb carries a whitelisted partial of fields a talent (or brand) may propose. Applied onto the canonical columns and fees recomputed. (3b6f6cbf)ADMIN_REVIEW; admins get an email plus an in-app notification. Post-approval counters stay private between brand and talent — they don't re-enter the queue.flag, ask_talent, auto_counter, or auto_reject (the last two skip admin review).PENDING_PAY_CAPTURE) then captured (PAID); an admin queue + cron handle release and 7-day auth voiding.expires_at / expire_policy; a cron sends stale reminders and transitions to EXPIRED.ef054780)Idempotency-Key.packages/db)offers table with the 14-state offer_status enum, the offer_expire_policy enum, and the image_kind enum. counter_changes jsonb for multi-field counters.createOffer, getOfferById, updateOfferStatus, updateOfferDraft, listOffersBySellerAccount, listOffersByBuyerAccount, listAllOffers, the payment/escrow helpers, the below-threshold auto-actions, and the cron query helpers. See API reference.packages/contracts)submitOfferBodySchema, respondToOfferBodySchema (with offerCounterChangesSchema), deliverOfferBodySchema, completeOfferBodySchema, requestRevisionBodySchema, disputeOfferBodySchema, the admin capture schemas, getOfferResponseSchema, listOffersQuerySchema / listOffersResponseSchema.OfferStatus, ExpirePolicy, OfferCounterChanges, OFFER_TRANSITIONS, Offer, NewOffer, OfferHistory types in @zooly/types.apps/zooly-app)Core lifecycle endpoints under /api/offers/:
POST /api/offers/submit)GET /api/offers/list) — seller / buyer / admin perspectivesGET /api/offers/[id])POST /api/offers/respond) — accept / reject / counter (with counterChanges)POST /api/offers/deliver)POST /api/offers/complete)POST /api/offers/revision), Dispute (POST /api/offers/dispute), Cancel (POST /api/offers/cancel)Plus admin, candidate-session, blast, and cron routes — see the full endpoint list.
Stripe Connect onboarding for talent payouts: /api/payments/connect/start + /api/payments/connect/return plus the account.updated webhook (packages/srv-stripe-payment). Full walkthrough in Stripe Connect (Talent Payouts).
packages/offers/client)submitOffer(), fetchOffers(), respondToOffer(), fetchPayoutRoute(), startStripeConnect() in packages/offers/client/src/lib/appApi.ts. New code routes through @zooly/api-client (W1-01 foundations).
The offers UI is a single React SPA in packages/offers/client (exported as App), mounted by both the Next.js zooly-app and the Vite zooly-app2 over the route subtrees it owns (/z, /agent, /dashboard).
imageKind and the model key, calls submitOffer().needs-connect / needs-completion / connected).zooly-app, /offers/[id]/pay and /offers/[id]): Stripe Elements payment, deliverable preview, "Mark complete".packages/ui-offers (OfferActionBar, OfferStatusBadge, OfferSummaryCard, DeliverablePreview, …).