The Video That Wasn't There: A Cautionary Tale About API Race Conditions

May 02, 2026 • ArchyPress

null

The API returned 201 Created. The response included a valid post ID. Our database said 'published'. And yet — when you clicked the link — LinkedIn showed 'Post not found'. The ghost post. The phantom publish.

The Setup: A Perfectly Reasonable Architecture

You're building a social media publishing platform. Users schedule posts, your server publishes them at the right time. For LinkedIn video posts, the flow looks straightforward:

  1. Upload video binary to LinkedIn's pre-signed URL

  2. Finalize the upload (tell LinkedIn you're done)

  3. Create the post referencing the video URN

  4. Get back a 201 — post created, life is good

And it does work. For images. For text posts. Even for small videos sometimes. But for anything that takes LinkedIn more than a second to process? You've just created a post that references a video that doesn't exist yet.

The Problem: 201 Doesn't Mean 'Ready'

Here's what's actually happening behind LinkedIn's API:

State diagram showing video lifecycle: Uploaded → Processing → Available or Failed

When you call finalizeUpload, LinkedIn acknowledges receipt and starts transcoding. The video URN is valid — it's a real identifier — but the video isn't playable yet. It's in a PROCESSING state.

When you then create a post referencing that URN, LinkedIn's Posts API doesn't validate whether the video is ready. It happily returns 201 Created with a valid post ID. The post exists in their system. But when a human visits it, LinkedIn's frontend checks the video status and — finding it still processing or in a broken state — renders 'Post not found'.

Why This Is Insidious

No Error Signal

The API returns 201. No warning header, no degraded status field, nothing. Your monitoring sees green.

Intermittent Timing

Small videos process in < 1 second and work fine. The bug only manifests for longer videos, making it hard to reproduce.

Delayed Discovery

Users don't check their posts immediately. By the time they notice, the publish event is hours old.

Phantom Success

Your database says 'published'. Your dashboard shows a green checkmark. The post has a URL. Everything looks correct.

The Fix: Poll Before You Post

The solution is a readiness gate between 'upload complete' and 'create post'. After finalizing the video upload, poll the video status endpoint until LinkedIn confirms the video is AVAILABLE:

// Pseudocode — the pattern, not the implementation

async function uploadVideoAndWaitForReady(videoFile, ownerUrn) {

const videoUrn = await initializeAndUploadVideo(videoFile, ownerUrn);

await finalizeUpload(videoUrn);

// THE CRITICAL ADDITION: wait for processing

const maxAttempts = 30;

for (let i = 0; i < maxAttempts; i++) {

await sleep(2000); // 2-second intervals

const status = await getVideoStatus(videoUrn);

if (status === 'AVAILABLE') return videoUrn;

if (status === 'PROCESSING_FAILED') throw new ProcessingError();

}

throw new TimeoutError('Video processing timed out');

}

The General Pattern: Eventual Consistency Gates

This isn't a LinkedIn-specific quirk. It's a fundamental pattern in any system with eventual consistency between a write acknowledgment and actual readiness:

  • AWS S3: PutObject returns 200 but the object might not be readable for a few hundred milliseconds (rare but real in some regions)

  • Stripe: Creating a subscription returns immediately but webhooks confirming payment arrive later

  • Cloud video services (Mux, Cloudflare Stream): Upload returns a playback ID that won't resolve until transcoding finishes

  • DNS propagation: Record updated, but resolvers still serve stale data

The architectural lesson: a successful write response tells you the system accepted your request, not that the downstream state is ready for consumers.

Design Principles for Media Pipeline Reliability

Flowchart showing the reliable publish pipeline: Upload → Finalize → Poll Status → Create Post → Verify

1. Gate on Readiness

Never proceed to a dependent operation until the prerequisite resource is confirmed ready. Poll, webhook, or both.

2. Set Timeouts

Polling without a timeout is a resource leak. Set a maximum wait (60s for video processing) and fail gracefully.

3. Surface Failures

When processing fails, tell the user immediately rather than marking it 'published' and hoping they don't check.

4. Verify the End State

After creating the post, optionally fetch it back to confirm it resolves. Belt and suspenders.

What About Webhooks?

'Why not use a webhook instead of polling?' — great question. LinkedIn's Videos API doesn't offer a processing-complete webhook. Many media APIs don't. And even when they do, webhooks introduce their own failure modes:

  • Webhook delivery is not guaranteed (your server might be down, the webhook might fail retry limits)

  • You need infrastructure to receive and route webhooks (HTTP endpoint, queue, state machine)

  • Polling is simpler for short waits (< 60 seconds) where the operational cost is one HTTP call every 2 seconds

For video processing that takes minutes, webhooks are the right call. For 5–30 seconds of transcoding? Polling with a bounded loop is perfectly fine.

The Takeaway

APIs that accept your media and immediately return success are making you a promise about receipt, not readiness. The gap between those two concepts is where ghost posts live.

Every time you integrate with a media upload API, ask yourself: 'When this returns 200, is the resource actually usable by consumers?' If the answer is 'not immediately' — and it usually is — you need a readiness gate.

Building a social publishing platform?

ArchySocial handles multi-network scheduling, AI content generation, and all the async media processing quirks so you don't have to.

© 2026 Meet Archy
The Video That Wasn't There: API Race Conditions in Media Publishing | Archy Engineering | ArchyPress Platform