Appearance
Asynchronous Article Updates
The asynchronous article update API allows you to queue screenshot replacements for one or more help center articles in a single request. Jobs are processed in the background via an SQS-backed queue, and you can poll for completion at any time.
This is the recommended way to sync screenshots with your help center. It replaces the legacy synchronous PUT /helpcenter/article/:id approach, which could not safely handle multiple image updates to the same article in quick succession.
Why asynchronous?
When a user clicks "Accept and Sync" on several rows in rapid succession, each update touches the same article on the help center. Synchronous processing caused race conditions - the second update might overwrite the first, or the help center API might reject concurrent writes. The async queue serializes updates per article, automatically delaying overlapping jobs and retrying them after a short cooldown.
For GitHub/Mintlify integrations, asynchronous processing is required: every commit on a Mintlify-tracked branch triggers a full redeploy, so updates are coalesced into a single pull request instead of individual commits.
Supported platforms
| Platform | Integration platform | Provider value |
|---|---|---|
| Intercom | helpcenter | intercom |
| Zendesk | helpcenter | zendesk |
| Helpjuice | helpcenter | helpjuice |
| Help Scout | helpcenter | helpscout |
| Mintlify (via GitHub) | github | mintlify |
Create article update jobs
POST /helpcenter/article/async
Submits a batch of screenshot replacement jobs. Each job targets a specific article and screenshot. The API validates the request, persists each job to DynamoDB, dispatches messages to the processing queue, and returns immediately with a 202 Accepted.
Request
Headers
| Header | Required | Description |
|---|---|---|
x-api-key | Yes | Your LaunchBrightly API key |
Content-Type | Yes | application/json |
Body
json
{
"integrationId": "d9e45598-d24d-40d8-baf6-33b6185c61b3",
"data": [
{
"articleId": "8444283",
"screenshots": [
{
"name": "settings-page.png",
"metadata": {
"ImageUniqueID": "601eb123-a472-4ffd-ae8c-0f939ae9df3ex"
},
"hash": "4d441888f21eceed2443eef4ba9b4e4d6c198f86904c258d476fed10a6b2e555",
"url": "https://your-bucket.s3.us-east-1.amazonaws.com/your-team/2026-01-15/screenshot-001.png"
}
]
}
]
}cURL
bash
curl --location --request POST 'https://api.launchbrightly.com/helpcenter/article/async' \
--header 'x-api-key: YOUR_API_KEY' \
--header 'Content-Type: application/json' \
--data '{
"integrationId": "d9e45598-d24d-40d8-baf6-33b6185c61b3",
"data": [
{
"articleId": "8444283",
"screenshots": [
{
"name": "settings-page.png",
"metadata": {
"ImageUniqueID": "601eb123-a472-4ffd-ae8c-0f939ae9df3ex"
},
"hash": "4d441888f21eceed2443eef4ba9b4e4d6c198f86904c258d476fed10a6b2e555",
"url": "https://your-bucket.s3.us-east-1.amazonaws.com/your-team/2026-01-15/screenshot-001.png"
}
]
}
]
}'Field reference
| Field | Type | Required | Constraints | Description |
|---|---|---|---|---|
integrationId | string | Yes | Must be a valid, active integration ID | The integration to use for authentication with the help center. Created via POST /integration. |
automergePR | boolean | No | Defaults to false | GitHub/Mintlify only. When true, the pull request is automatically merged after creation. |
data | array | Yes | 1–100 items | Array of article update jobs. |
data[].articleId | string | Yes | - | The external article identifier on the help center platform (e.g., Intercom article ID, Zendesk article ID, or the file path for Mintlify). |
data[].screenshots | array | Yes | 1–50 items per article | Screenshots to replace in this article. |
data[].screenshots[].metadata | object | Yes | 1–5 key-value pairs; values must be non-empty strings | PNG metadata key-value pairs used for matching. This is the highest-priority matching method. |
data[].screenshots[].hash | string | Yes | Non-empty string | The pixel hash of the screenshot. Used as the second-priority matching method. |
data[].screenshots[].name | string | No | Non-empty when provided | The filename of the screenshot. |
data[].screenshots[].url | string | Yes | Valid HTTP/HTTPS URL; max 4,096 characters; filename portion max 200 characters | A pre-signed or publicly accessible URL to the replacement image. |
Success Response
Code: 202 Accepted
json
{
"articleRequestId": "c3a1f9e2-7b4d-4e8a-9f5c-2d6b8a3e1f7d",
"jobs": [
{
"id": "a1b2c3d4-5e6f-7890-abcd-ef1234567890",
"status": "pending",
"createdAt": "2026-02-11T11:01:50.068Z"
}
]
}| Field | Description |
|---|---|
articleRequestId | A unique identifier for this batch. Use it to poll for job status via the status endpoint. |
jobs | Array of created jobs, one per screenshot in the request. |
jobs[].id | Unique job identifier. |
jobs[].status | Initial status. Will be pending for valid jobs or failed if pre-validation failed (e.g., filename too long). |
jobs[].createdAt | ISO 8601 timestamp of job creation. |
INFO
A 202 response means the jobs have been accepted and queued. It does not mean they have completed. Poll the status endpoint to track progress.
Batch request (multiple articles and screenshots)
You can update multiple screenshots across multiple articles in a single request:
cURL
bash
curl --location --request POST 'https://api.launchbrightly.com/helpcenter/article/async' \
--header 'x-api-key: YOUR_API_KEY' \
--header 'Content-Type: application/json' \
--data '{
"integrationId": "d9e45598-d24d-40d8-baf6-33b6185c61b3",
"data": [
{
"articleId": "8444283",
"screenshots": [
{
"metadata": { "ImageUniqueID": "601eb123-a472-4ffd-ae8c-0f939ae9df3ex" },
"hash": "4d441888f21eceed2443eef4ba9b4e4d6c198f86904c258d476fed10a6b2e555",
"url": "https://your-bucket.s3.us-east-1.amazonaws.com/your-team/2026-01-15/screenshot-001.png"
}
]
},
{
"articleId": "8444284",
"screenshots": [
{
"metadata": { "ImageUniqueID": "401qb163-d472-5eed-ae2c-0f931ae2d5fe2" },
"hash": "b6975b10b36df3cc8e41b16a9be8c4f691aa1a2059d3dbcfde31d16fe6d5f65d",
"url": "https://your-bucket.s3.us-east-1.amazonaws.com/your-team/2026-01-15/screenshot-002.png"
}
]
}
]
}'Response
json
{
"articleRequestId": "c3a1f9e2-7b4d-4e8a-9f5c-2d6b8a3e1f7d",
"jobs": [
{
"id": "a1b2c3d4-5e6f-7890-abcd-ef1234567890",
"status": "pending",
"createdAt": "2026-02-11T11:01:50.068Z"
},
{
"id": "f9e8d7c6-5b4a-3210-fedc-ba9876543210",
"status": "pending",
"createdAt": "2026-02-11T11:01:50.068Z"
}
]
}Error Responses
Validation errors - Code: 422
When a required field is missing or a constraint is violated, the API returns a 422 with an errors array. For example, if integrationId is missing:
json
{
"errors": [
"integrationId is required."
]
}All validation errors follow this same shape. The table below lists the validation rules that can trigger a 422:
| Validation Rule | Error Message |
|---|---|
| Body is not an object | The request body must be an object. |
Missing integrationId | integrationId is required. |
automergePR is not a boolean | automergePR must be a boolean. |
data is empty or exceeds 100 items | data must be an array of article job objects (1 to 100 jobs). |
Missing articleId in a job | articleId is required for each job. |
| Screenshots array empty or exceeds 50 | You can only update up to 50 screenshots per (article) job. |
| Screenshot missing required fields | The screenshot must contain metadata, hash, and url fields. |
| Metadata exceeds 5 key-value pairs | metadata should contain at most 5 key-value pairs |
| Screenshot URL is not a valid URL | screenshots.*.url is not a valid URL. |
| Screenshot URL exceeds 4,096 characters | screenshots.*.url should not be longer than 4096 characters. |
| Integration is not a help center or GitHub type | The integrationId provided is not a HelpCenter integration. |
| Integration credentials are invalid | The API credentials are invalid. |
Missing API key - Code: 403
json
{
"message": "Missing or invalid authentication token.",
"statusCode": 403,
"error": {
"message": "Missing or invalid authentication token.",
"type": "MISSING_AUTHENTICATION_TOKEN"
}
}Check job status
GET /helpcenter/article/request/:articleRequestId
Retrieves the current status and a summary of all jobs associated with an articleRequestId. Use this to poll for completion after submitting jobs.
Request
Headers
| Header | Required | Description |
|---|---|---|
x-api-key | Yes | Your LaunchBrightly API key |
URL Parameters
| Parameter | Required | Description |
|---|---|---|
articleRequestId | Yes | The batch identifier returned by POST /helpcenter/article/async |
cURL
bash
curl --location --request GET 'https://api.launchbrightly.com/helpcenter/article/request/c3a1f9e2-7b4d-4e8a-9f5c-2d6b8a3e1f7d' \
--header 'x-api-key: YOUR_API_KEY'Success Response - all jobs completed
Code: 200 OK
json
{
"summary": {
"total": "2",
"pending": "0",
"completed": "2",
"failed": "0",
"delayed": "0"
},
"jobs": [
{
"id": "a1b2c3d4-5e6f-7890-abcd-ef1234567890",
"externalArticleId": "8444283",
"name": "settings-page.png",
"metadata": {
"ImageUniqueID": "601eb123-a472-4ffd-ae8c-0f939ae9df3ex"
},
"hash": "4d441888f21eceed2443eef4ba9b4e4d6c198f86904c258d476fed10a6b2e555",
"matchType": "metadata",
"status": "completed",
"createdAt": "2026-02-11T11:01:50.068Z",
"updatedAt": "2026-02-11T11:01:52.892Z"
},
{
"id": "f9e8d7c6-5b4a-3210-fedc-ba9876543210",
"externalArticleId": "8444284",
"name": "dashboard.png",
"metadata": {
"ImageUniqueID": "401qb163-d472-5eed-ae2c-0f931ae2d5fe2"
},
"hash": "b6975b10b36df3cc8e41b16a9be8c4f691aa1a2059d3dbcfde31d16fe6d5f65d",
"matchType": "hash",
"status": "completed",
"createdAt": "2026-02-11T11:01:50.068Z",
"updatedAt": "2026-02-11T11:03:57.712Z"
}
]
}Response field reference
| Field | Description |
|---|---|
summary.total | Total number of jobs in this batch. |
summary.pending | Jobs not yet picked up by a worker. |
summary.completed | Jobs that matched and replaced a screenshot successfully. |
summary.failed | Jobs that could not complete (no match found, invalid credentials, max retries exceeded, etc.). |
summary.delayed | Jobs temporarily held back because another update for the same article is in progress. These auto-retry after ~30 seconds. |
jobs[].matchType | Which matching algorithm found the image: metadata, hash or empty string if no match. |
jobs[].status | One of: pending, processing, completed, failed, delayed. |
jobs[].error | Present only when status is failed. Contains message and type. |
Success Response - with a failed job
When a screenshot cannot be matched in the live article, the job's status is failed and an error object is included:
cURL
bash
curl --location --request GET 'https://api.launchbrightly.com/helpcenter/article/request/c3a1f9e2-7b4d-4e8a-9f5c-2d6b8a3e1f7d' \
--header 'x-api-key: YOUR_API_KEY'Code: 200 OK
json
{
"summary": {
"total": "1",
"pending": "0",
"completed": "0",
"failed": "1",
"delayed": "0"
},
"jobs": [
{
"id": "a1b2c3d4-5e6f-7890-abcd-ef1234567890",
"externalArticleId": "8444283",
"name": "nonexistent-image.png",
"metadata": {
"ImageUniqueID": "601eb123-a472-4ffd-ae8c-0f939ae9df3ex"
},
"hash": "4d441888f21eceed2443eef4ba9b4e4d6c198f86904c258d476fed10a6b2e555",
"matchType": "",
"status": "failed",
"error": {
"message": "Screenshot not found in the live article",
"type": "RuntimeException"
},
"createdAt": "2026-02-11T11:45:52.260Z",
"updatedAt": "2026-02-11T11:45:57.022Z"
}
]
}Success Response - with a delayed job
When two jobs target the same article in quick succession, the second job is delayed while the first completes:
Code: 200 OK
json
{
"summary": {
"total": "2",
"pending": "0",
"completed": "1",
"failed": "0",
"delayed": "1"
},
"jobs": [
{
"id": "a1b2c3d4-5e6f-7890-abcd-ef1234567890",
"externalArticleId": "8444283",
"name": "settings-page.png",
"metadata": { "ImageUniqueID": "601eb123-a472-4ffd-ae8c-0f939ae9df3ex" },
"hash": "4d441888f21eceed2443eef4ba9b4e4d6c198f86904c258d476fed10a6b2e555",
"matchType": "hash",
"status": "completed",
"createdAt": "2026-02-11T11:01:50.068Z",
"updatedAt": "2026-02-11T11:01:52.892Z"
},
{
"id": "f9e8d7c6-5b4a-3210-fedc-ba9876543210",
"externalArticleId": "8444283",
"name": "billing-page.png",
"metadata": { "ImageUniqueID": "601eb123-a472-4ffd-ae8c-0f939ae9df3ex" },
"hash": "b6975b10b36df3cc8e41b16a9be8c4f691aa1a2059d3dbcfde31d16fe6d5f65d",
"matchType": "",
"status": "delayed",
"createdAt": "2026-02-11T11:01:50.068Z",
"updatedAt": "2026-02-11T11:01:54.680Z"
}
]
}INFO
Delayed jobs automatically retry after ~30 seconds (with ±2 seconds of jitter). A job is retried up to 3 times before being marked as failed with a MaxDelayRetryReached error.
Success Response - GitHub/Mintlify batch
When the batch targets a GitHub/Mintlify integration, the response includes additional top-level fields for the pull request:
cURL
bash
curl --location --request GET 'https://api.launchbrightly.com/helpcenter/article/request/c3a1f9e2-7b4d-4e8a-9f5c-2d6b8a3e1f7d' \
--header 'x-api-key: YOUR_API_KEY'Code: 200 OK
json
{
"summary": {
"total": "1",
"pending": "0",
"completed": "1",
"failed": "0",
"delayed": "0"
},
"pullRequestUrl": "https://github.com/your-org/your-docs-repo/pull/42",
"pullRequestMerged": true,
"branch": "lb-c3a1f9e2-7b4d-4e8a-9f5c-2d6b8a3e1f7d",
"jobs": [
{
"id": "a1b2c3d4-5e6f-7890-abcd-ef1234567890",
"externalArticleId": "docs/getting-started.mdx",
"name": "settings-page.png",
"metadata": { "ImageUniqueID": "601eb123-a472-4ffd-ae8c-0f939ae9df3ex" },
"hash": "4d441888f21eceed2443eef4ba9b4e4d6c198f86904c258d476fed10a6b2e555",
"matchType": "metadata",
"status": "completed",
"createdAt": "2026-02-11T11:01:50.068Z",
"updatedAt": "2026-02-11T11:01:55.000Z"
}
]
}| Field | Description |
|---|---|
pullRequestUrl | URL of the pull request created on the docs repository. null if no changes were needed. |
pullRequestMerged | true if automergePR was set and the PR was merged successfully. |
branch | The working branch name (format: lb-<articleRequestId>). |
Error Response
Condition: The articleRequestId does not exist - Code: 404
cURL
bash
curl --location --request GET 'https://api.launchbrightly.com/helpcenter/article/request/c3a1f9e2-7b4d-4e8a-9f5c-000000000000' \
--header 'x-api-key: YOUR_API_KEY'json
{
"message": "The record c3a1f9e2-7b4d-4e8a-9f5c-000000000000 does not exist or is invalid.",
"statusCode": 404,
"error": {
"message": "The article update job id could not be found: c3a1f9e2-7b4d-4e8a-9f5c-000000000000",
"type": "RESOURCE_NOT_FOUND"
}
}Job lifecycle
A job moves through the following statuses:
pending → processing → completed
→ failed
→ delayed → processing → completed
→ failed
→ delayed → delayed → failed (MaxDelayRetryReached)| Status | Description |
|---|---|
pending | Job created and queued for processing. |
processing | A worker has picked up the job. |
delayed | Another update for the same article is in flight. The job will auto-retry after ~30 seconds. |
completed | The screenshot was matched and replaced in the live article. |
failed | The job could not complete. Check the error field for details. |
Common failure reasons
| Error Type | Description |
|---|---|
RuntimeException | Screenshot not found in the live article - no match on metadata, hash, or name. |
MaxDelayRetryReached | The job was delayed 3 times (another update kept running). |
FILE_NAME_TOO_LONG | The screenshot URL's filename exceeds 200 characters. Pre-validated at submission; the job is created with failed status immediately. |
Duplicate detection
To prevent race conditions when multiple updates target the same article, the async worker serializes updates:
- Before processing, the worker checks for other recent in-flight jobs (pending, processing, or delayed) targeting the same article and integration, within a 60-second window.
- If an earlier job exists, the current job is re-queued with a ~30 second delay (30s base ± 2s jitter).
- The job's
delayCountis incremented on each delay. After 3 delays, the job fails. - Completed and failed jobs no longer hold a lock - they don't cause subsequent jobs to delay.
This applies only to non-GitHub integrations. GitHub/Mintlify batches are inherently serialized because the entire batch is processed as a single SQS message.
Batch size limits
| Constraint | Limit |
|---|---|
Jobs per request (data array length) | 1–100 |
| Screenshots per article job | 1–50 |
| Metadata key-value pairs per screenshot | 1–5 |
| Screenshot URL length | Max 4,096 characters |
| Screenshot filename length (from URL) | Max 200 characters |
HTTP status codes
| Code | Description |
|---|---|
202 | Jobs accepted and queued for processing (POST) |
200 | Job status retrieved successfully (GET) |
403 | Unauthorized or missing API key |
404 | articleRequestId not found |
422 | Validation error in request body |
500 | Unexpected server error |