Query Publish Status
Synchronously query the status of a publish job. Designed for polling — the server does not write call logs, so it is safe to call at high frequency (recommended interval ≥ 3s).
Why this endpoint exists
Publishing is asynchronous: /tasks/publish returns a job_id immediately, while the actual write into the project-level aggregated tables happens in a background GCP Workflow. /export/ini reads those same tables — exporting before the publish finishes races stale data.
Before this endpoint, publish completion was only observable via the task.publish.completed webhook. Environments that cannot receive webhooks (local scripts, agent sandboxes, CLIs) had to sleep a "safety margin" after publishing before exporting. This endpoint replaces that guess with a deterministic wait: poll until completed, then export.
Endpoint
GET {BASE_URL}/api/open/v1/tasks/publish-statusPOST is also supported (job_id in the JSON body).
Auth Headers
Identical to Query Task Status:
| Header | Value |
|---|---|
X-Loxily-AppKey | Project App Key |
X-Loxily-Timestamp | Unix seconds (valid for 5 minutes) |
X-Loxily-Sign | MD5(timestamp + "" + appSecret) — GET has no body, so the body is an empty string in the signature |
Query Parameters
| Param | Type | Required | Description |
|---|---|---|---|
job_id | string | Yes | The publish_open_... job ID returned by Publish Translation Task |
Response Fields
Success (HTTP 200):
{
"success": true,
"code": 0,
"msg": "ok",
"trace_id": "8a3f12ab-4d2a-9f51-7e2c6d8a9101",
"data": {
"job_id": "publish_open_...",
"task_id": "68a12cb7e1f9a88b4ea23c77",
"status": "running",
"is_completed": false,
"is_failed": false,
"error": null,
"accepted_at": "2026-04-23T09:15:22.117Z",
"updated_at": "2026-04-23T09:15:40.552Z"
}
}The 4 status values
| status | Meaning |
|---|---|
pending | Publish accepted; the workflow has not reported progress yet |
running | The PUBLISH_TASK workflow is executing |
completed | Publish finished; the project-level aggregated tables are fully written — safe to call /export/ini |
failed | Publish failed; error carries the reason |
Terminal-state semantics are identical to the task.publish.completed / task.publish.failed webhooks — both read the same underlying data.
Error Codes
| code | Description |
|---|---|
| 400 | job_id missing |
| 401 | Invalid signature / expired timestamp |
| 404 | job_id does not exist, is not a publish job, or does not belong to this App Key |
| 500 | Internal error |
Ownership failures return 404
If the job_id exists but belongs to another App Key, the endpoint also returns 404 (not 403) to avoid leaking the job's existence.
Examples
cURL (GET)
APP_KEY="5685414646a54423c891d87194d87f3f"
APP_SECRET="<your App Secret>"
BASE_URL="https://api.loxily.com"
JOB_ID="publish_open_68a12cb7_1745399722"
TIMESTAMP=$(date +%s)
# GET has no body; sign with an empty body string
SIGN=$(printf '%s' "${TIMESTAMP}${APP_SECRET}" | md5)
curl -s "${BASE_URL}/api/open/v1/tasks/publish-status?job_id=${JOB_ID}" \
-H "X-Loxily-AppKey: ${APP_KEY}" \
-H "X-Loxily-Timestamp: ${TIMESTAMP}" \
-H "X-Loxily-Sign: ${SIGN}"Node.js: poll until published, then export
import crypto from 'crypto';
async function waitForPublish(jobId, { timeoutMs = 15 * 60 * 1000, intervalMs = 3000 } = {}) {
const start = Date.now();
while (Date.now() - start < timeoutMs) {
const timestamp = Math.floor(Date.now() / 1000).toString();
const sign = crypto.createHash('md5')
.update(`${timestamp}${process.env.LOXILY_APP_SECRET}`, 'utf8')
.digest('hex');
const resp = await fetch(
`${process.env.LOXILY_BASE_URL}/api/open/v1/tasks/publish-status?job_id=${jobId}`,
{
headers: {
'X-Loxily-AppKey': process.env.LOXILY_APP_KEY,
'X-Loxily-Timestamp': timestamp,
'X-Loxily-Sign': sign,
},
},
);
const { data } = await resp.json();
if (data.is_completed) return data;
if (data.is_failed) throw new Error(`Publish failed: ${data.error}`);
await new Promise((r) => setTimeout(r, intervalMs));
}
throw new Error(`Polling timeout (${timeoutMs}ms)`);
}
// publish → wait for the write to finish → then export
const { data } = await publishTask('68a12cb7e1f9a88b4ea23c77');
await waitForPublish(data.job_id);
// Aggregated tables are now fully written; /export/ini is safeCorresponding MCP tools
MCP integrators do not need to hand-roll polling:
loxily_query_publish_status— single query (one call to this endpoint)loxily_wait_for_publish— aggregate polling; blocks until a terminal state (default 15-minute cap, 3s interval)
FAQ
Q: How is this different from the task.publish.completed webhook? The webhook is push, this endpoint is pull. Webhooks are timely but require a public endpoint; polling works in any environment. Terminal-state semantics are identical.
Q: Why doesn't /tasks/status show publish progress?/tasks/status reflects the translation pipeline (import → scan → translate → smart optimize). Publishing is a separate workflow triggered after translation completes, and its job is not part of the task's jobs summary. Use this endpoint (or the webhook) for publish progress.
Q: Is there a rate limit? This endpoint does not write call logs and has no dedicated rate limit. A polling interval of ≥ 3s is recommended.