OverviewAuthenticationGenerate ImageGenerate CopyIntegrations APIWebhooksData ModelsCredit SystemError Codes

Zelko API

REST API reference for the Zelko platform.

Base URL
https://zelko.ai/api
Auth
Supabase JWT (Bearer token)
Content-Type
multipart/form-data or application/json
Response
application/json

The Zelko API gives you programmatic access to AI image transformation, listing copy generation, credit management, and third-party integrations. All endpoints are Next.js App Router route handlers and run on Vercel Edge.

Authentication

All API calls include a Supabase JWT access token in the Authorization header. Server-side routes use the Supabase service role key to read and write data on behalf of the authenticated user.

http
POST /api/generate-image
Authorization: Bearer <supabase_access_token>
Content-Type: multipart/form-data

Get a token from the Supabase client:

typescript
import { supabase } from "@/lib/supabase";

const { data: { session } } = await supabase.auth.getSession();
const token = session?.access_token;

Generate Image

POST/api/generate-image

Transforms a property photo using fal.ai Flux Pro Kontext. Accepts multipart/form-data with the source image and transformation parameters.

Request parameters

ParameterTypeRequiredDescription
fileFilerequiredSource room or exterior photo (JPG/PNG, max 10 MB)
operationstringrequiredstaging | style_swap | exterior_redesign | sky_replacement | color_change | furniture_removal
orgIdstringrequiredYour organization UUID (found in Dashboard → Settings)
stylestringoptionalDesign style (Modern, Luxury, Farmhouse…) or color name for color_change
roomTypestringoptionalliving_room | bedroom | kitchen | bathroom | dining_room. Default: living_room
projectIdstringoptionalProject UUID to associate the generated asset with
customPromptstringoptionalOverride the auto-generated prompt entirely
preserveStructurebooleanoptionalAppend structure-preservation instructions to prompt. Default: true
maskFileFileoptionalWhite-on-black PNG mask for furniture_removal inpainting
referenceFileFileoptionalReference style photo for style_swap / exterior_redesign. Upgrades model to kontext/max

Response

json
{
  "assetId": "a1b2c3d4-...",
  "outputUrl": "https://fal.media/files/...",
  "creditsUsed": 2
}

Operations & credit costs

OperationModelCreditsNotes
stagingflux-pro/kontext2Adds furniture to empty rooms
style_swapflux-pro/kontext2Upgrades to kontext/max with referenceFile
exterior_redesignflux-pro/kontext2Upgrades to kontext/max with referenceFile
color_changeflux-pro/kontext2Pass color name in style param
sky_replacementflux-pro/kontext1No room type or structure preservation
furniture_removalflux-pro/v1/fill2Pass white-on-black mask in maskFile

Example — Virtual Staging

typescript
const formData = new FormData();
formData.append("file", roomPhoto);
formData.append("operation", "staging");
formData.append("style", "Modern");
formData.append("roomType", "living_room");
formData.append("orgId", session.user.org_id);
formData.append("preserveStructure", "true");

const res = await fetch("/api/generate-image", {
  method: "POST",
  headers: { Authorization: `Bearer ${session.access_token}` },
  body: formData,
});

const { outputUrl, creditsUsed } = await res.json();

Example — Style Swap with reference photo

typescript
formData.append("operation", "style_swap");
formData.append("referenceFile", referencePhoto);
// Automatically upgrades to fal-ai/flux-pro/kontext/max

Generate Copy

POST/api/generate-copy

Generates MLS listing descriptions, social captions, and taglines using Claude. Runs a Fair Housing compliance check on all output. Free on all plans (0 credits).

Request body (JSON)

ParameterTypeRequiredDescription
typestringrequiredlisting | social | taglines
orgIdstringrequiredYour organization UUID (found in Dashboard → Settings)
propertyDetailsobjectoptional{ address, bedrooms, bathrooms, sqft, type, notes }
imageUrlstringoptionalProperty photo URL for vision-aware listing copy
assetIdstringoptionalExisting asset to attach the copy to

Response

json
// type: "listing"
{ "description": "Welcome to this stunning Modern masterpiece...", "compliance": { "compliance_check": "PASS" } }

// type: "social"
{ "instagram": "✨ Just listed!...", "facebook": "NEW LISTING...", "linkedin": "Excited to present..." }

// type: "taglines"
{ "taglines": ["Where Modern Luxury Meets Timeless Comfort", ...] }

Example

typescript
const res = await fetch("/api/generate-copy", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    type: "listing",
    orgId: "your-org-uuid",
    propertyDetails: {
      address: "123 Sunset Blvd, Los Angeles, CA",
      bedrooms: "4", bathrooms: "3", sqft: "2400",
      type: "Single Family",
      notes: "Renovated kitchen 2024, rooftop deck, mountain views",
    },
  }),
});
const { description, compliance } = await res.json();

Integrations API

Manage third-party integrations (Zapier, Follow Up Boss, HubSpot). Credentials are stored per-org and fired automatically when an asset completes.

Connect an integration

POST/api/integrations/[provider]
ParameterTypeRequiredDescription
orgIdstringrequiredOrganization UUID
webhook_urlstringoptionalZapier only — https://hooks.zapier.com/...
api_keystringoptionalFollow Up Boss only
access_tokenstringoptionalHubSpot only — Private App token
typescript
// Zapier
await fetch("/api/integrations/zapier", { method: "POST", headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ orgId: "your-org-uuid", webhook_url: "https://hooks.zapier.com/..." }) });

// Follow Up Boss
await fetch("/api/integrations/fub", { method: "POST", headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ orgId: "your-org-uuid", api_key: "fub_xxxxxxxxxx" }) });

// HubSpot
await fetch("/api/integrations/hubspot", { method: "POST", headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ orgId: "your-org-uuid", access_token: "pat-na1-xxx" }) });

Disconnect

DELETE/api/integrations/[provider]
typescript
await fetch("/api/integrations/zapier", { method: "DELETE",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ orgId: "your-org-uuid" }) });

Check status

GET/api/integrations/[provider]?orgId=...
json
{ "connected": true }

Webhooks (Zapier)

When Zapier is connected, Zelko POSTs a payload to your webhook URL every time an image generation completes — use it to trigger CRM updates, Slack alerts, Google Sheets logging, etc.

Payload schema

json
{
  "event":        "asset.completed",
  "asset_id":     "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "output_url":   "https://fal.media/files/generated-room.jpg",
  "operation":    "staging",
  "style":        "Modern",
  "room_type":    "living_room",
  "credits_used": 2,
  "org_id":       "org-uuid-here",
  "timestamp":    "2026-03-14T12:34:56.789Z"
}

Setup in Zapier

  1. 1Trigger: Webhooks by ZapierCatch Hook
  2. 2Copy the webhook URL from Zapier
  3. 3Paste it into Dashboard → Integrations → Zapier → Connect
  4. 4Generate an image to send a test payload
  5. 5Add your Action step (HubSpot, Google Sheets, Slack, etc.)

Data Models

Asset

typescript
type Asset = {
  id:                string;
  project_id:        string | null;
  org_id:            string;
  type:              "image" | "video" | "text" | "flyer" | "social";
  subtype:           string | null;
  input_url:         string | null;
  output_url:        string | null;
  generation_params: Record<string, unknown> | null;
  model_used:        string | null;
  credits_cost:      number;
  status:            "pending" | "processing" | "completed" | "failed";
  created_at:        string;
};

Organization

typescript
type Organization = {
  id:                string;
  name:              string;
  owner_id:          string;
  plan:              "free" | "starter" | "professional" | "agency" | "enterprise";
  credits_remaining: number;
  stripe_customer_id: string | null;
  created_at:        string;
};

Project

typescript
type Project = {
  id:            string;
  org_id:        string;
  address:       string;
  property_type: "residential" | "commercial" | "land" | "multi-family" | null;
  status:        "active" | "archived";
  thumbnail_url: string | null;
  created_at:    string;
  updated_at:    string;
};

Credit System

Credits are tracked per organization and deducted atomically using a PostgreSQL stored procedure to prevent race conditions.

Free
10 lifetime
Starter
50 / month
Professional
200 / month
Agency
600 / month
sql
-- Atomic credit deduction
select deduct_org_credits(p_org_id := 'org-uuid', p_amount := 2);

-- Add credits (Stripe webhook)
select add_org_credits(p_org_id := 'org-uuid', p_amount := 50);

Credit costs

ParameterTypeRequiredDescription
Image transformation2 creditsoptionalstaging, style_swap, exterior_redesign, color_change, furniture_removal
Sky replacement1 creditoptionalsky_replacement
Video (10s)8 creditsoptionalComing Q2 2026 — Kling AI
Flyer PDF1 creditoptionalComing Q2 2026
Listing copyFREEoptionalgenerate-copy endpoint — Claude Sonnet
Social captionsFREEoptionalgenerate-copy endpoint — Claude Haiku

Error Codes

StatusErrorCause
400Missing required fields: file, operation, orgIdMissing required form fields
400Unknown operation: {op}Invalid operation key
401Invalid credentialsIntegration API key/token failed validation
402Insufficient creditsorg.credits_remaining < creditCost
500Failed to create asset recordSupabase insert failed
500fal.ai returned no output imagefal.ai returned empty images array
500Generation failedUnexpected error — check server logs