LinkedIn video post stuck in processing: two status ladders to poll
LinkedIn tracks a video asset (PROCESSING, AVAILABLE) and a post (PUBLISH_REQUESTED, PUBLISHED) separately. Poll both, then compare with a Sume job poll.

A LinkedIn video that never appears has two places to look, because the API keeps two separate status fields: the video asset has a status, and the post has a lifecycleState. The asset must reach AVAILABLE before the post can serve it. The post is created as PUBLISHED, but a later read can return PUBLISH_REQUESTED while processing finishes, or PUBLISH_FAILED if processing did not succeed.
| Field | Value | Meaning on the page |
|---|---|---|
| Video status | WAITING_UPLOAD | Waiting for the client to finish uploading |
| Video status | PROCESSING | Generating missing artifacts |
| Video status | PROCESSING_FAILED | Client error such as file too large or unsupported format, or an internal error |
| Video status | AVAILABLE | All required artifacts are ready |
| Post lifecycleState | PUBLISH_REQUESTED | Submitted, not ready to render; published asynchronously |
| Post lifecycleState | PUBLISH_FAILED | Not processed; an edit is needed to retry |
| Post lifecycleState | DRAFT | Visible to the author only |
| Post lifecycleState | PUBLISHED | Visible to all; the only value accepted on create |
What to read when it stalls
On the video, GET /rest/videos/{urn} returns status, and processingFailureReason appears only when the status is PROCESSING_FAILED. On the post, the Posts API returns the published version by default (viewContext is READER). Add viewContext=AUTHOR to see the latest unpublished version, which can be PUBLISHED, DRAFT or PROCESSING.
Two upload errors are worth separating from processing: EXPIRED_UPLOAD_URL (400) and a 401 on an expired upload URL. The Videos API says upload URLs typically expire 30 days after initialization, and that finalizing needs the part ETags in upload order.
The same loop on the Sume side
The render that feeds LinkedIn is also asynchronous. A Sume job moves through queued, processing and then a terminal completed, failed or canceled; the jobs guide says to use exponential backoff and never to resubmit a paid request just because your own process timed out. Only after completed should you fetch the result and start the LinkedIn upload, so a LinkedIn PROCESSING_FAILED never gets blamed on a render that was still running.
The script below polls a Sume job until it reaches a terminal state. Set SUME_API_KEY and pass the job id.
import json, os, sys, time, urllib.request
def status(job_id):
req = urllib.request.Request(
f"https://api.sume.com/v1/jobs/{job_id}/status",
headers={"Authorization": f"Bearer {os.environ['SUME_API_KEY']}"})
with urllib.request.urlopen(req, timeout=30) as r:
return json.load(r)
def main():
delay = 2
while True:
body = status(sys.argv[1])
print(body.get("status"))
if body.get("status") in ("completed", "failed", "canceled"):
return
time.sleep(delay)
delay = min(delay * 2, 30)
main()Sources
Related posts
More in Developers
- MAI-Transcribe-2 on OpenRouter: wav, base64, transcript JSON
OpenRouter exposes microsoft/mai-transcribe-2 at POST /api/v1/audio/transcriptions with base64 audio and returns JSON with transcript and usage.
- MAI-Voice-2.1 on OpenRouter: audio/speech call, mp3 or pcm
OpenRouter serves microsoft/mai-voice-2.1 at POST /api/v1/audio/speech and returns raw audio bytes, mp3 or pcm. Here is the call and how Sume jobs differ.
- Decision-1 returns 429: back off, then fail closed before Sume
Microsoft says retry a Decision-1 429 with exponential backoff. Cap the retries, then route the brief to review so a throttled gate never renders.
- Decision-1, then Haiku 5.5, then a person: a three-tier gate
Add an abstention option to a Decision-1 choice, escalate unsure briefs to Claude Haiku 5.5, and keep people for the rest, before any paid Sume call.
Written by Sume