Integration Guide
CRM & IDX

CRM & IDX Platform Video API Integration

Your platform already stores every listing photo, address, and agent brand. That is the complete input set for an AI listing video. This is the engineering guide to wiring generation into your CRM or IDX product so every listing gets a video without an agent lifting a finger.

February 11, 2026
15 min read
PhotoAIVideo Team
1 webhook
Integration Scope
on your existing listing event
~$3.17
Cost Per Listing
9 credits on API Growth
Zero
Agent Effort
videos appear automatically
Sticky
Retention Impact
video lives in your platform

Why Platforms Are the Natural Home for Listing Video

Every standalone video tool in real estate has the same structural problem: it has to convince an agent to leave the software they already work in, upload photos a second time, retype the address, and remember to come back. That friction is why adoption of listing video has stayed low despite universal demand for it.

A CRM or IDX platform has none of that friction. The photos are already uploaded. The address is already validated. The agent's headshot, logo, and brokerage colors are already on file. When a listing goes active, your system knows before anyone else does. You are one webhook away from a feature the standalone tools cannot match.

What has stopped platforms from shipping it is infrastructure. Rendering video means GPU capacity, a job queue, encoding pipelines, storage, and a team to keep it running. That is a multi-quarter commitment that never survives roadmap prioritization against features your agents are actively asking for. An API removes the infrastructure question entirely and leaves you with an integration problem your existing backend team can finish in a sprint.

You already own the listing data

Your platform stores addresses, photo galleries, prices, bed and bath counts, and agent branding. That is every input a listing video needs. A CRM or IDX product is better positioned to auto-generate video than any standalone tool, because the standalone tool has to ask the agent to upload what you already have.

It is a retention feature, not just a revenue line

Agents leave platforms that feel interchangeable. When their listing videos live in your product and are generated by your automations, migrating means giving up their video library and workflow. That is real switching cost created by a single integration.

It closes a competitive gap immediately

Video is on nearly every real estate platform roadmap and almost nobody has shipped it, because building rendering infrastructure is a multi-quarter project. Calling an API turns a roadmap epic into a sprint and lets you announce a feature your competitors are still scoping.

It monetizes without a new sales motion

You already bill your agents. Adding a video tier or a per-listing charge rides on billing you have already built, which is why the margin is so clean compared to launching a separate product.

The Integration Architecture

There are three components to build, regardless of your stack. A trigger that decides when a listing deserves a video, a proxy endpoint that calls the API with your server-side key, and a webhook receiver that attaches the finished video back to the listing record. Everything else is detail.

Start with the data model. You need a job table that maps the API's job id to your internal listing and tenant, because the webhook will arrive with the job id and nothing else. Without this table you cannot route a completed video back to the right listing.

-- Minimal schema for platform-side job tracking
CREATE TABLE api_video_jobs (
  id            BIGSERIAL PRIMARY KEY,
  job_id        TEXT UNIQUE NOT NULL,   -- id returned by the API
  listing_id    BIGINT NOT NULL REFERENCES listings(id),
  tenant_id     BIGINT NOT NULL REFERENCES tenants(id),
  status        TEXT NOT NULL DEFAULT 'processing',
  credits_used  INT,
  video_url     TEXT,
  error         TEXT,
  created_at    TIMESTAMPTZ NOT NULL DEFAULT now(),
  completed_at  TIMESTAMPTZ
);

CREATE INDEX idx_jobs_tenant_month ON api_video_jobs (tenant_id, created_at);
CREATE UNIQUE INDEX idx_one_active_per_listing
  ON api_video_jobs (listing_id) WHERE status = 'processing';

That partial unique index is doing important work. It makes it impossible to have two in-flight jobs for the same listing, which is the database-level defense against duplicate generation when your listing webhook fires twice in quick succession.

Choosing Your Generation Trigger

This decision determines your credit spend more than any other. Each option is defensible and most mature platforms end up combining two — an automatic trigger for the common case plus a manual button for exceptions.

Listing status changes to Active

Fire generation the moment a listing goes live in your platform.

Strength: Perfect timing — the video is ready exactly when the agent starts marketing.
Watch out: Listings that flip Active more than once can double-generate. Store a flag per listing.

Photo gallery reaches a threshold

Wait until the listing has at least eight usable photos, then generate.

Strength: Guarantees enough input for a good reel and avoids wasting credits on thin galleries.
Watch out: Some listings never reach the threshold, so pair it with a manual generate button.

Agent clicks Generate Video

An explicit button in your listing detail UI.

Strength: Zero wasted credits and it doubles as a usage signal for which agents value the feature.
Watch out: Adoption depends on discovery. Most agents will never find the button without prompting.

Nightly batch of new listings

A scheduled job that sweeps listings added in the last 24 hours.

Strength: Simplest to build and easiest to rate-limit across a large tenant base.
Watch out: Up to a day of delay, which matters most on fast-moving listings.

Wiring the Trigger to Generation

Here is the core handler. It runs inside your platform when a listing becomes active, selects the best photos, enforces a per-tenant cap, and enqueues the render. Note that it never blocks on the render itself.

// POST /internal/listings/:id/generate-video  (server-side only)
import { db } from "@/lib/db"

const API_BASE = "https://app.photoaivideo.com/api/v1"

export async function generateListingVideo(listingId: number) {
  const listing = await db.listing.findUnique({
    where: { id: listingId },
    include: { photos: { orderBy: { position: "asc" } }, tenant: true },
  })

  // 1. Guard: already generated or in flight?
  const existing = await db.apiVideoJob.findFirst({
    where: { listingId, status: { in: ["processing", "completed"] } },
  })
  if (existing) return { skipped: "already_generated" }

  // 2. Guard: enough usable photos?
  const photos = listing.photos.filter((p) => p.isPublic).slice(0, 10)
  if (photos.length < 6) return { skipped: "insufficient_photos" }

  // 3. Guard: tenant within monthly cap?
  const startOfMonth = new Date(new Date().setDate(1))
  const used = await db.apiVideoJob.count({
    where: { tenantId: listing.tenantId, createdAt: { gte: startOfMonth } },
  })
  if (used >= listing.tenant.monthlyVideoCap) {
    return { skipped: "tenant_cap_reached" }
  }

  // 4. Enqueue the render
  const res = await fetch(`${API_BASE}/videos/create-reel`, {
    method: "POST",
    headers: {
      "X-API-Key": process.env.PHOTOAIVIDEO_API_KEY!,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      images: photos.map((p) => ({ url: p.url, effect: pickEffect(p.roomType) })),
      aspect_ratio: "9:16",
      text_overlay: { text: listing.address, position: "bottom" },
      logo_url: listing.tenant.logoUrl,
      webhook_url: `${process.env.APP_URL}/webhooks/photoaivideo`,
    }),
  })

  if (!res.ok) {
    const body = await res.text()
    throw new Error(`Generation failed (${res.status}): ${body}`)
  }

  const { job_id } = await res.json()

  // 5. Record the mapping so the webhook can find its way home
  await db.apiVideoJob.create({
    data: { jobId: job_id, listingId, tenantId: listing.tenantId, status: "processing" },
  })

  return { jobId: job_id }
}

Three guards before any credit is spent, and the job mapping written immediately after the call succeeds. That ordering matters: if you write the mapping before the API call and the call fails, you will have a phantom job that blocks future generation for that listing.

Build this into your platform

Every endpoint, camera effect, credit cost, and plan tier is documented on the developer API page. Pick a plan, generate a server-side key, and you can have the integration above running against real listings today.

Receiving the Finished Video

The webhook is where the video becomes visible to your agents. Keep the handler small, idempotent, and fast — acknowledge with a 200 before doing anything slow like copying the file to your own storage.

// POST /webhooks/photoaivideo
export async function POST(req: Request) {
  const event = await req.json()
  const { job_id, status, video_url, credits_used, error } = event

  const job = await db.apiVideoJob.findUnique({ where: { jobId: job_id } })
  if (!job) return new Response("ok", { status: 200 })      // unknown job, ignore
  if (job.status !== "processing") return new Response("ok", { status: 200 }) // idempotent

  if (status === "completed") {
    await db.$transaction([
      db.apiVideoJob.update({
        where: { jobId: job_id },
        data: { status: "completed", videoUrl: video_url, creditsUsed: credits_used, completedAt: new Date() },
      }),
      db.listing.update({
        where: { id: job.listingId },
        data: { videoUrl: video_url, hasVideo: true },
      }),
    ])
    await notifyAgent(job.listingId)   // in-app badge or email
  } else {
    await db.apiVideoJob.update({
      where: { jobId: job_id },
      data: { status: "failed", error: error ?? "unknown" },
    })
  }

  return new Response("ok", { status: 200 })
}

The two early returns make this handler safe to call repeatedly, which matters because webhook delivery is at-least-once. Updating the job and the listing inside one transaction means an agent never sees a listing marked as having video without a URL attached.

If your architecture cannot accept inbound webhooks, poll GET /videos/{job_id} on a schedule instead. The async jobs and polling guide covers backoff strategy and reconciliation sweeps in depth.

Multi-Tenant Credits and Billing

This is where platform integrations differ most from single-company ones. You hold one API plan and resell capacity to many tenants, so you need your own accounting layer on top of the API's credit balance.

A listing costs photos + 1 credits — one per clip plus one to merge. An eight-photo reel is nine credits, roughly $3.17 on the API Growth plan. Your job table already records every generation with a tenant id, so per-tenant usage is a single aggregate query.

-- Monthly usage per tenant, for invoicing and cap enforcement
SELECT
  t.name,
  COUNT(*)                     AS videos_generated,
  SUM(j.credits_used)          AS credits_consumed,
  ROUND(SUM(j.credits_used) * 0.352, 2) AS cost_usd
FROM api_video_jobs j
JOIN tenants t ON t.id = j.tenant_id
WHERE j.status = 'completed'
  AND j.created_at >= date_trunc('month', now())
GROUP BY t.name
ORDER BY credits_consumed DESC;

Always cap before you meter

A per-tenant monthly cap enforced before the API call is your only real protection against a single brokerage importing ten thousand listings and consuming your entire plan overnight. Metering tells you what happened; the cap prevents it. Build both, and build the cap first.

For packaging, most platforms land on one of three models: bundle a set number of videos into existing tiers, charge a flat monthly video add-on per agent seat, or bill per listing generated. The SaaS packaging guide works through the margin math on each.

Handling IDX Gallery Quirks

IDX feeds introduce problems a normal upload flow never sees. The API fetches images by URL, so anything your feed does to those URLs becomes your problem.

Expiring signed URLs are the most common failure. If your IDX provider serves photos through short-lived tokens, the URL may be dead by the time the render worker fetches it. Copy the selected photos to your own persistent storage first and pass those URLs instead. Watermarked feed images are the second issue — generating from a watermarked source bakes the watermark into every frame, so prefer the unbranded original whenever your feed exposes one.

Finally, filter aggressively. IDX galleries routinely include floor plans, blurry exteriors, and duplicate angles. Selecting eight strong frames produces a better reel than passing thirty mediocre ones, and it costs a third as much. Ordering matters too — lead with the exterior, move through living spaces, and close on the primary bedroom or yard.

Mistakes That Cost Platforms Real Money

Sharing one API key across all tenants with no attribution

Use a single server-side key but record which tenant each job belongs to in your own database, keyed by job id. Without that mapping you cannot bill accurately, cannot enforce per-tenant limits, and cannot answer a support ticket about one brokerage's usage.

Generating on every listing update webhook

Update events fire constantly — price changes, description edits, photo reorders. Gate generation behind a persisted flag so each listing generates once unless the gallery meaningfully changes. This is the single most expensive mistake a platform can make.

Blocking your listing save on the render

Generation is asynchronous and takes minutes. Enqueue the job, return immediately, and update the listing record when the webhook arrives. Never hold an HTTP request or a database transaction open waiting on a render.

Exposing your API key to the browser

All calls happen from your backend. If your frontend can read the key, any agent can extract it and drain your credit balance. Proxy every request through your own authenticated endpoint.

Launching to every tenant at once

Enable it for a handful of design-partner brokerages first. You will discover gallery quality problems and credit consumption patterns that are far cheaper to fix at ten tenants than at ten thousand.

A Four-Phase Rollout

Resist the urge to ship this to your whole tenant base at once. Credit consumption at scale is hard to predict from a test account, and the failure modes you will hit are cheaper to fix small.

Phase 1

Backend plumbing

Add an api_video_jobs table, a proxy endpoint, and a webhook receiver. Generate for a single internal test listing end to end. No agent-facing UI yet.

Phase 2

Design partners

Enable a manual Generate Video button for three to five friendly brokerages. Watch credit burn, gallery quality, and failure reasons for two weeks.

Phase 3

Automatic triggers

Turn on status-change generation for those same tenants with a per-tenant monthly cap. Confirm the cap logic actually holds under real volume.

Phase 4

General availability and billing

Expose it as a tier or add-on across your tenant base, with usage metering wired into your existing invoices and a clear overage policy.

Positioning It to Your Agents and Your Market

The feature sells itself best when agents never have to think about it. The strongest framing is not "we added a video tool" — it is "every listing you enter now comes with a video." One is a feature you have to teach; the other is a benefit that lands instantly in a demo.

For sales and marketing, this becomes a differentiator in every competitive deal. Most CRM and IDX competitors have video on a roadmap. Being able to demo a listing going active and a finished vertical reel appearing minutes later is a moment competitors cannot match without the same infrastructure investment you just avoided.

Because generation is server-to-server, nothing in the output references PhotoAIVideo. The videos carry your platform's branding and your agents' logos, which means the feature strengthens your brand rather than introducing a third party into your product experience.

Common Questions

Do I need a separate API key for each tenant?

No. Use one server-side key for your platform and attribute usage internally with a tenant id on each job row. Per-tenant keys would mean managing thousands of credentials and separate plans, which is the wrong model for a platform. The authentication and credits guide covers key rotation and scoping.

How long does a listing video take?

A few minutes for a typical eight to ten photo reel, depending on queue depth. Because it is asynchronous, your platform should treat it as a background job that resolves via webhook rather than something an agent waits on.

What happens when a render fails?

Credits are refunded automatically and the webhook arrives with a failed status and an error reason. The most common cause on IDX integrations is an image URL the render worker cannot reach publicly, which is why copying photos to your own storage first is worth the effort.

Can agents customize the output?

Yes, as much as you choose to expose. You control aspect ratio, text overlays, logo placement, and per-clip camera effects through the request payload. Most platforms start with sensible automatic defaults and add controls only where agents ask for them. See the API documentation for the full parameter list.

How do I estimate which plan to start on?

Multiply your expected monthly listings by ten credits and add a buffer for retries. A platform generating for 80 listings a month needs roughly 800 credits, which lines up with the API Growth tier. Start one tier below your projection during the design-partner phase and scale up once real consumption data exists.

Getting Started

The integration is genuinely small relative to its impact: a job table, a guarded generation function, and a webhook handler. Everything hard about video — GPU capacity, encoding, storage, retries — sits on the other side of the API call.

Start with one internal listing and the code in this article. Once you see a finished vertical reel land on a listing record automatically, the rollout plan writes itself.

Give every agent on your platform listing video

Review the endpoints, credit costs, and plan tiers, then generate a server-side key and ship the integration. Your agents get video on every listing; you get a retention feature your competitors are still scoping.

Related Articles

Add AI Listing Videos to Your MLS

Feed ingestion, member rollout, and compliance considerations.

Bulk Listing Video Generation

Queues, concurrency limits, and backfilling thousands of listings.

PhotoAIVideo Developer API

Endpoint reference, camera effects, credit costs, and plan tiers.