The Video That Wasn't There: A Cautionary Tale About API Race Conditions
May 02, 2026 • ArchyPress

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:
Upload video binary to LinkedIn's pre-signed URL
Finalize the upload (tell LinkedIn you're done)
Create the post referencing the video URN
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:
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
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.