Skip to main content
GET
/
v1
/
jobs
/
{job_id}
Get Job Status
curl --request GET \
  --url https://api.stru.ai/v1/jobs/{job_id} \
  --header 'Authorization: <authorization>'
{
  "401": {},
  "404": {},
  "job_id": "<string>",
  "status": "<string>",
  "created_at": "<string>",
  "completed_at": {},
  "results": {},
  "error": {}
}

Overview

Check the status of an asynchronous job (search, document generation, etc.) and retrieve results once processing is complete. This endpoint works for all job types across the Stru API platform.
Universal Endpoint: This endpoint handles job status for Search, Mathcad, and Excel APIs using the same unified interface.

Authentication

Authorization
string
required
Bearer token (API key from app.stru.ai)
Authorization: Bearer sk_live_abc123...

Path Parameters

job_id
string
required
Unique job identifier returned when creating the job
job_abc123xyz

Response

Returns the current job status and results (if complete).
job_id
string
Job identifier
status
string
Current job statusPossible values:
  • "pending" - Queued, not yet started
  • "in_progress" - Currently processing
  • "completed" - Finished successfully
  • "failed" - Encountered an error
created_at
string
ISO 8601 timestamp when job was created
completed_at
string | null
ISO 8601 timestamp when job completed (null if not complete)
results
object | null
Job results (only present when status is "completed")For search jobs, contains SearchResults object For file generation jobs, contains download URL
error
object | null
Error details (only present when status is "failed")

Example Requests

curl https://api.stru.ai/v1/jobs/job_abc123xyz \
  -H "Authorization: Bearer sk_live_abc123..."

Example Responses

Pending Job

{
  "job_id": "job_abc123xyz",
  "status": "pending",
  "created_at": "2025-10-17T10:30:00Z",
  "completed_at": null,
  "results": null
}
Status Code: 200 OK

In Progress

{
  "job_id": "job_abc123xyz",
  "status": "in_progress",
  "created_at": "2025-10-17T10:30:00Z",
  "completed_at": null,
  "results": null
}

Completed (Search Job)

{
  "job_id": "job_abc123xyz",
  "status": "completed",
  "created_at": "2025-10-17T10:30:00Z",
  "completed_at": "2025-10-17T10:30:03.245Z",
  "results": {
    "total": 15,
    "page": 1,
    "per_page": 25,
    "total_pages": 1,
    "matches": [
      {
        "file_id": "asce-7-22",
        "filename": "ASCE_7-22.pdf",
        "description": "ASCE 7-22: Minimum Design Loads...",
        "design_standard": "us",
        "snippet": "...deflection of structural members shall not exceed L/360...",
        "page_number": 142,
        "relevance_score": 0.92,
        "context": "Section 10.5.3 - Deflection Limits. The deflection...",
        "content_type": "text"
      }
    ]
  }
}

Completed (File Generation Job)

{
  "job_id": "job_xyz789abc",
  "status": "completed",
  "created_at": "2025-10-17T10:30:00Z",
  "completed_at": "2025-10-17T10:30:15.891Z",
  "results": {
    "file_url": "https://storage.stru.ai/temp/calc-report-xyz.mcdx?signature=...",
    "file_type": "mcdx",
    "expires_at": "2025-10-18T10:30:15.891Z"
  }
}

Failed Job

{
  "job_id": "job_abc123xyz",
  "status": "failed",
  "created_at": "2025-10-17T10:30:00Z",
  "completed_at": "2025-10-17T10:30:25.123Z",
  "error": {
    "code": "SEARCH_TIMEOUT",
    "message": "Search operation timed out after 30 seconds",
    "details": {
      "query": "very complex query...",
      "timeout_ms": 30000
    }
  }
}

Error Responses

404
Not Found
Job ID does not exist
{
  "error": {
    "code": "JOB_NOT_FOUND",
    "message": "Job job_abc123xyz not found"
  }
}
401
Unauthorized
Missing or invalid API key
{
  "error": {
    "code": "UNAUTHORIZED",
    "message": "Invalid or missing API key"
  }
}

Polling Best Practices

Don’t poll at a fixed interval. Start with 1 second, then double the delay after each check (1s, 2s, 4s, 8s).This reduces unnecessary API calls and respects rate limits.
delay = 1
while status != "completed":
    time.sleep(delay)
    status = check_job_status(job_id)
    delay = min(delay * 2, 8)  # Max 8 seconds
Always implement a timeout to prevent infinite polling loops.
max_wait = 30  # seconds
elapsed = 0

while elapsed < max_wait and status != "completed":
    time.sleep(delay)
    elapsed += delay
    status = check_job_status(job_id)
Don’t just check for “completed”. Handle pending, in_progress, and failed states.
if status == "completed":
    return results
elif status == "failed":
    raise Exception(f"Job failed: {error}")
elif status in ["pending", "in_progress"]:
    continue  # Keep polling
Exponential backoff helps, but also track your total request count to stay under 120/minute.
# Track requests per minute
request_count = 0
minute_start = time.time()

while True:
    if time.time() - minute_start > 60:
        request_count = 0
        minute_start = time.time()

    if request_count >= 120:
        time.sleep(60 - (time.time() - minute_start))
        continue

    check_job_status(job_id)
    request_count += 1

Job Retention

Jobs and their results are retained for different durations based on your subscription tier:
TierJob RetentionAccess to Results
Free1 hour1 hour
Pro24 hours24 hours
EnterpriseCustomCustom
After the retention period, the job ID becomes invalid and returns a 404 error. Download any generated files within the retention window.

Typical Processing Times

Expected job completion times by operation type:
OperationTypical Duration
Search (simple query)1-3 seconds
Search (complex semantic)3-8 seconds
Mathcad document generation5-15 seconds
Excel spreadsheet generation3-10 seconds
PDF conversion10-30 seconds
Processing times vary based on system load and job complexity. Always implement proper polling with timeouts.

Next Steps

Process Search Results

Learn how to work with search matches, page numbers, and relevance scores

Download Generated Files

For file generation jobs, use the signed URL to download your .mcdx or .xlsx files

Error Handling

Implement robust error handling for failed jobs and timeouts

Rate Limiting

Understand rate limits and implement exponential backoff in your polling logic