Skip to content

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-status

POST is also supported (job_id in the JSON body).

Auth Headers

Identical to Query Task Status:

HeaderValue
X-Loxily-AppKeyProject App Key
X-Loxily-TimestampUnix seconds (valid for 5 minutes)
X-Loxily-SignMD5(timestamp + "" + appSecret) — GET has no body, so the body is an empty string in the signature

Query Parameters

ParamTypeRequiredDescription
job_idstringYesThe publish_open_... job ID returned by Publish Translation Task

Response Fields

Success (HTTP 200):

json
{
  "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

statusMeaning
pendingPublish accepted; the workflow has not reported progress yet
runningThe PUBLISH_TASK workflow is executing
completedPublish finished; the project-level aggregated tables are fully written — safe to call /export/ini
failedPublish 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

codeDescription
400job_id missing
401Invalid signature / expired timestamp
404job_id does not exist, is not a publish job, or does not belong to this App Key
500Internal 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)

bash
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

javascript
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 safe

Corresponding 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.