Lupokits API

Add the Lupokits marketing studio to any website, or drive it from your backend. Partners such as brokerages, CRMs and media platforms can open a pre-filled studio for any listing and receive finished designs back.

Want API access? We set up partner keys and pricing for each partner. Email admin@lupokits.com with your company and website.

Overview

Embed in 2 minutes

<script src="/embed.js" data-key="pk_live_YOUR_PUBLIC_KEY"></script>
<button id="kit">Create marketing kit</button>
<script>
  document.getElementById('kit').onclick = () => Lupokits.open({
    listing: {
      status: 'Just Listed',
      address: '87 Willow Lane', city: 'Westport, CT 06880',
      price: '$1,185,000', beds: '5', baths: '3', sqft: '3,050',
      photos: ['https://cdn.yoursite.com/listing/1.jpg', 'https://cdn.yoursite.com/listing/2.jpg'],
    },
    brand: { name: 'Jamie Lee', phone: '(555) 010-2030', brokerage: 'Oakline Realty', logo: 'https://…/logo.png' },
    kit: 'social-pro',
    onExport(files) {
      // files: [{ name, blob, type, format, channel, width, height }]
      files.forEach(f => upload(f.blob, f.name));
    },
  });
</script>

Try it on the live partner demo page.

Authentication

KeyWhereHow to send
pk_live_… public keyBrowser (embed SDK)data-key attribute or X-Api-Key header. Only works from the allowed origins registered for the key; the studio refuses to be framed anywhere else (CSP frame-ancestors).
sk_live_… secret keyYour server onlyAuthorization: Bearer sk_live_…. Never ship it to a browser.

Keys are made in Admin → Partner API (see partner keys). To check which key a request uses, call GET /api/v1/whoami.

Lupokits.open(options)

OptionDescription
keyPublic key (defaults to the script tag's data-key).
listingListing object (see below). Photos must be publicly reachable URLs.
brandAgent / brokerage branding shown on every design.
kitessentials (5 designs), social-pro (12) or complete (19). Omit to open a single design.
campaignjust-listed, coming-soon, open-house, price-improved, under-contract, just-sold.
template, theme, formatOptional style. IDs from the catalog endpoints.
containerSelector or element to render inline instead of a modal.
sessionOpen a server-created design session.
onExport(files, data)Called when the user clicks Send to …. data.captions contains the caption, hashtags and description.
onReady(), onClose()Lifecycle callbacks.

Returns { close(), frame }. Sites built before the rename can keep calling ListingKit.open().

Listing & brand objects

listing = {
  status, headline, tagline, address, city, price, oldPrice,
  beds, baths, sqft, lot, year, garage, propertyType,
  description, features: ['Ocean views', …], cta, url,
  openHouse: { date: '2026-11-15', start: '12:00', end: '15:00' },
  photos: ['https://…'], caption, hashtags: ['#…']
}
brand = { name, title, phone, email, website, brokerage, license,
          photo, logo, primary: '#1E5A66', accent: '#E09F6B', mode: 'light' | 'dark',
          displayFont, bodyFont }

displayFont and bodyFont take any of the 53 font families from GET /api/v1/fonts, for example 'Playfair Display' and 'DM Sans'.

Messages & security

The SDK and studio talk over postMessage: the studio sends listingkit:ready, your page answers with listingkit:init (listing, brand, kit…), and finished files come back as listingkit:export. The studio only accepts init from origins on your key's allow-list, and only posts exports back to that origin.

Templates, formats, themes & kits

GET/api/v1/templates public
GET/api/v1/formats public
GET/api/v1/themes public
GET/api/v1/fonts public
GET/api/v1/kits public

21 sizes (posts, stories, reels, banners, email and print), 12 color themes and 53 fonts ({ family, kind }, where kind is serif, sans, display or script).

GET/api/v1/catalog public

The template library partners can use: every public template (a layout in a theme, or a custom design) with id, name, layoutId, themeId, category and the sizes it supports. GET /api/v1/catalog/:id adds the saved design for custom templates.

GET/api/v1/plans public

Plans, prices and what each includes, as shown on the pricing page.

curl /api/v1/kits
{ "packages": [{ "id": "social-pro", "name": "Social Pro", "items": [{ "format": "ig-square", "role": "hero" }, …] }, …],
  "campaigns": [{ "id": "open-house", "status": "Open House", "style": "open-house" }, …],
  "statuses": ["Just Listed", "Coming Soon", …] }

Import a listing from a link

POST/api/v1/listings/import secret or public key

Reads public listing metadata (schema.org JSON-LD, Open Graph, embedded page data) and the photo gallery from a property website.

curl -X POST /api/v1/listings/import \
  -H "Authorization: Bearer sk_live_…" -H "Content-Type: application/json" \
  -d '{"url":"https://example-media.com/sites/87-willow-lane"}'

{ "source":  { "url": "…", "host": "…", "platform": "Aryeo" },
  "listing": { "address": "87 Willow Lane", "city": "Westport, CT 06880", "price": "$1,185,000",
               "beds": "5", "baths": "3", "sqft": "3,050", "year": "1998", "description": "…" },
  "photos":  ["https://…/1.jpg", "https://…/2.jpg", …],
  "contact": { "phone": "+15550102030", "email": "agent@…" },
  "confidence": 0.9, "warnings": [] }
Portals: Zillow, Redfin and Realtor.com often block automated access (especially from cloud servers), and their terms restrict scraping and reuse of listing content. Blocked links return 422 blocked with suggestions. For production, rely on the property website from your media delivery platform or a licensed MLS feed (see data sources).

AI listing copy

POST/api/v1/ai/describe secret or public key
{ "listing": { "address": "87 Willow Lane", "city": "Westport, CT 06880", "beds": "5", "baths": "3",
               "features": ["Wraparound porch", "Renovated kitchen"] },
  "tone": "warm",          // luxury | warm | modern | professional | playful
  "length": "medium",      // short | medium | long
  "emojis": true, "status": "Open House", "notes": "New roof in 2024" }

→ { "source": "ai", "model": "claude-opus-5-5",
    "copy": { "headline", "tagline", "description", "highlights": [], "instagramCaption",
              "hashtags": [], "storyText", "emailSubject" } }

Copy uses only the facts you send and follows US Fair Housing language rules. Without an AI key on the server, a built-in writer answers instead with "source": "template".

GET/api/v1/ai/status public

Whether Claude is connected: { "provider": "anthropic", "model": "claude-opus-5-5", "mode": "ai" | "template" }. AI-designed templates are part of the Lupokits app (Alpha plan) and are not exposed through the partner API.

Design sessions

POST/api/v1/sessions secret key
curl -X POST /api/v1/sessions \
  -H "Authorization: Bearer sk_live_…" -H "Content-Type: application/json" \
  -d '{"listing":{"address":"87 Willow Lane","photos":["https://…"]},"brand":{"name":"Jamie Lee"},"kit":"complete","campaign":"open-house"}'

{ "id": "ses_…",
  "editorUrl": "/studio?session=ses_…",
  "embedUrl":  "/studio?embed=1&key=pk_live_…&session=ses_…" }
GET/api/v1/sessions/:id
PATCH/api/v1/sessions/:id

Send agents the editorUrl (for example from your CRM when a listing goes live), or pass session to Lupokits.open(). Sessions are deleted 7 days after they are created; after that GET and PATCH answer 404 not_found and the links stop working.

Uploads & images

POST/api/v1/uploads secret or public key

Send a JPEG, PNG, WebP or MP4 as the raw request body (with its Content-Type). Returns { "url": "…", "bytes": 123456 }. Uploaded files are deleted after 7 days.

GET/api/v1/img?url=… public

Image proxy so photos from other domains can be drawn and exported without CORS problems. Private and internal addresses are refused.

Errors & limits

Errors return JSON: { "error": "blocked", "message": "…", "suggestions": [] }. Rate limits (per key or IP, per minute): import 20, AI 15, uploads 120, image proxy 400. Exceeding them returns 429 with Retry-After.

Health & key check

GET/api/v1/health public
{ "ok": true, "ai": { "provider": "anthropic", "model": "claude-opus-5-5", "mode": "ai" } }
GET/api/v1/whoami any
curl /api/v1/whoami -H "Authorization: Bearer sk_live_…"
{ "type": "secret", "key": { "name": "Oakline Realty", "plan": "partner" } }

A public key answers "type": "public". Without a key it answers { "type": "anonymous" }; a wrong or switched-off key, or a public key used from a site not on its list, answers { "type": "invalid", "reason": "…" }.

Partner keys

Partner keys are set up by the Lupokits team. To get yours, email admin@lupokits.com.

Create a key per partner in Admin → Partner API: name the partner, list the websites allowed to embed the studio, and set the brand name and color shown in the studio. The public key (pk_live_…) goes in the embed script; the secret key (sk_live_…) is shown once and stays on the partner's server. Usage per month is shown next to each key, and keys can be switched off at any time. On a local copy, a demo key is created on first run and shown at the top of this page.

Data retention

Lupokits keeps working files for 7 days:

Send finished files to your own storage with onExport or by downloading them. Self-hosted servers can change the period with LISTING_DAYS.

Listing data sources

Link import works with single-property websites (Spiro, Aryeo, HD Photo Hub, Tonomo), brokerage and IDX pages that publish standard metadata. For fully automatic, licensed data, connect your MLS through the RESO Web API (via your MLS's provider) and create design sessions when listings change. Only import listings you are authorized to market.

Self-hosting

VariablePurpose
PORTHTTP port (default 4310).
PUBLIC_URLPublic origin used in returned URLs, e.g. https://studio.yourdomain.com.
ANTHROPIC_API_KEYOne key for the whole site: turns on Claude for AI listing copy and AI-designed templates. Put it in the .env file and restart. AI_MODEL and AI_EFFORT are optional.
AI_DESIGN_EFFORT, AI_REVIEW_ROUNDSOptional: how hard Claude works on an AI template (default high) and how many times it looks at its own render and improves it (default 1).
CHROME_PATHOptional: Chrome or Edge for rendering AI drafts so Claude can review them. Found automatically on most machines.
LISTING_DAYSOptional: how many days listings, design sessions and uploads are kept (default 7).
SMTP_PASS (SMTP_HOST, SMTP_PORT, SMTP_USER, MAIL_FROM)Account emails: sign-up confirmation links and password reset links. For lupokits.com only SMTP_PASS is needed, the password of the admin@lupokits.com mailbox at GoDaddy; the others point it at another mailbox.
GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET"Continue with Google" on the login and sign-up pages, from a Google Cloud OAuth client (web application) with the redirect URI https://YOUR-DOMAIN/auth/google/callback.
ADMIN_EMAIL, ADMIN_PASSWORDThe first admin account (otherwise one is created and its password written to data/admin-login.txt). Changing ADMIN_PASSWORD later gives that admin the new password on the next start.
STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET, STRIPE_PRICE_PRO, STRIPE_PRICE_ALPHASubscriptions. Any plan can use STRIPE_PRICE_<PLAN ID> or a price ID set in Admin → Pricing; custom plans without one are charged at their monthly price (STRIPE_CURRENCY, default usd). Without Stripe keys, upgrades use a demo checkout outside production (ALLOW_DEMO_BILLING=1 allows it in production).
RENTCAST_API_KEYOptional: fills beds/baths/sq ft from an address.
TRUST_PROXY=1Behind a load balancer, so rate limits see real client IPs.
DATA_DIR, UPLOAD_DIRWhere the database and uploaded photos are stored.

Support

Questions about the API, your keys or your account? Email admin@lupokits.com. It's the one address for everything Lupokits.