Skip to content
Akram Moussaoui
CV PDF

ad2video

A service that turns the photos, description and equipment of a car listing into an ad video, built in partnership with OLX.

Employer
Groupe La Centrale
Role
Contributor at the start, then lead
Period
Stack
  • TypeScript strict
  • Node 24
  • AWS Lambda
  • SNS
  • SQS
  • DynamoDB
  • DynamoDB Streams
  • API Gateway
  • CloudFront
  • WAF
  • KMS
  • CDK v2

Context and problem

Car dealers publish their listings on La Centrale with photos, a description and a list of equipment. Together with OLX, we turn those listings into ad videos, generated by OLX's video API.

The service tracks the contracts and listings of 8,000 dealers. It has to generate each video at the right time, and never go over OLX's cap of 5,000 generations a day.

My role

I joined the project as a contributor at the start, then became its lead, in a team of 3 developers. A colleague built the base: syncing contracts and listings, and the read API. I designed and shipped the whole generation pipeline: listing eligibility, calls to the video API, status callbacks, then the daily batch and automatic video renewal. I also wrote the generation prompt with the team.

I am the direct technical contact for OLX's engineers, from design through delivery. Since the launch, I own the monitoring of the service and its improvements.

Architecture

ad2video architecture Sync: dealer account events go through SNS then SQS, and a Lambda syncs contracts and listings into DynamoDB. Generation: DynamoDB Streams triggers the orchestration Lambda, which applies the throttling, fetches the photos and calls OLX's video API. Access: the dealer back office goes through CloudFront and WAF, then a private API Gateway and a Lambda that reads DynamoDB. Everything is deployed with AWS CDK v2. CONTRACT AND LISTING SYNC VIDEO GENERATION ENABLING AND VIEWING Dealer accounts internal events SNS account topic SQS batches, partial retry Lambda sync DynamoDB single-table · KMS DynamoDB Streams changes Orchestration Lambda · throttling Photos internal service OLX video API external partner Dealer back office enable, view CloudFront + WAF only way in API Gateway private Lambda API prompt + video request, with retries INFRASTRUCTURE AWS CDK v2 · separate stateless and stateful stacks · strict TypeScript on Node 24
Diagram redrawn for this site. In blue, the parts behind the decisions below; dashed, the external partner. Internal services are not named.

The architecture is event-driven, on AWS:

  • Dealer account events arrive through SNS into an SQS queue, processed in batches.
  • A Lambda syncs contracts and listings into a single DynamoDB table, encrypted with KMS.
  • DynamoDB Streams triggers the orchestration Lambda. It applies the throttling, fetches the photos from an internal service, builds the prompt and calls OLX's video API, with retries.
  • The dealers' back office enables and views the videos through a private API Gateway, reachable only behind CloudFront and WAF.

The infrastructure is written in AWS CDK v2, with separate stateless and stateful stacks. The code is strict TypeScript on Node 24.

Key decisions

Defer a listing that was just generated

Context

OLX enforces a cap of 5,000 generations a day. A listing can change several times a day, and each change can call for a new video.

Choice

A first generation runs immediately. A listing already generated in the last 12 hours is deferred.

Rejected alternative

The default option: generate a video on every listing change, deferring nothing.

Consequence

The service stays under OLX's cap while producing 3,000 videos a day.

Move to a capped daily batch

Context

Generating on every listing change made the number of calls to the video API hard to predict, and every generation is billed.

Choice

Generation requests are stored in DynamoDB, in an index that only holds pending requests. A daily batch scheduled by EventBridge processes at most 3,000 requests per run, in arrival order. The batch time and size are CDK parameters.

Rejected alternative

Keep generating right away and rely only on the last 12 hours delay to smooth the volume.

Consequence

The daily volume is tuned without touching the code. Videos are also renewed automatically after 105 days, with at most 5 generations per listing.

Retry only the failed messages

Context

Account events reach SQS in batches. One failing message must not hold back the others.

Choice

Batch processing with partial failures, through batchItemFailures: only the failed messages go back to the queue.

Rejected alternative
The default option: a batch with one failure is retried in full.
Consequence

A failed message is retried on its own, without processing the rest of the batch again.

A single way in: CloudFront and WAF

Context

The API that enables and serves the videos is exposed to the dealers' back office.

Choice

API Gateway's default endpoint is disabled: the API is reachable only behind CloudFront and WAF.

Rejected alternative

The default option: keep API Gateway's public endpoint active next to CloudFront.

Consequence
No call can bypass the WAF rules.

Results

3,000 videos a day

The service is in production. It produces 3,000 videos a day for 8,000 dealers, without going over OLX's cap of 5,000 generations a day.

AI in the product

I designed the generation prompt with the team. It is built from the listing's data: its description and its equipment. The video itself is generated by OLX's API.

Limits and what I would do differently

  • The cap comes from outside. Throttling spreads generations over time, it does not add capacity: a listing edited several times waits for its new video.
  • Generation depends on a partner API. Retries absorb a transient error, but the video then arrives later.