Skip to main content
Some Kombo endpoints process requests asynchronously. Instead of returning the final result in the HTTP response, they return a task_id that you use to poll for the result. This is used for operations that may take longer to complete – such as bulk writes to a connected system.
This guide covers async endpoints in the Unified API. It does not apply to AI Apply.

How it works

Working with an async endpoint is a three-step process:
  1. Submit – Send a POST request with your payload. You receive a task_id immediately.
  2. Poll – Call the corresponding GET endpoint with the task_id at regular intervals.
  3. Collect the result – Once the status is COMPLETED or FAILED, the response contains the final result or error details.

Task statuses

Every task is in exactly one of three statuses:
StatusMeaningTerminal?
PENDINGThe task is still being processed. Keep polling.No
COMPLETEDThe task finished processing all items. The data field contains the results.Yes
FAILEDThe task could not be processed at all. The error field contains the details.Yes
COMPLETED means Kombo finished processing the task – it does not mean every item in the request succeeded. Individual items may have failed (e.g., one course could not be created in the connected tool). These per-item outcomes are reported in the data array, where each entry has its own status of SUCCEEDED or FAILED.In contrast, a task-level FAILED status means the task could not run at all – for example, because the credentials for the connected tool are no longer valid. In that case, no items were processed.In some cases, the connected tool supports batch processing, which Kombo uses internally. When that happens, multiple items may share the same outcome because they were processed as a single batch. Such scenarios are usually explained in the endpoint-specific docs or listed as action constraints in the coverage grid.

Submitting a request

Send a POST request just like any other action endpoint. The response contains a task_id that identifies the background task. The following example uses the LMS bulk upsert courses endpoint for illustration, but the pattern is the same for all async endpoints:
curl --request POST \
  --url 'https://api.kombo.dev/v1/lms/courses/bulk' \
  --header 'Authorization: Bearer <api_key>' \
  --header 'X-Integration-Id: <integration_id>' \
  --header 'Content-Type: application/json' \
  --data '{
    "items": [
      {
        "origin_id": "cybersecurity-101",
        "course": {
          "type": "EXTERNAL",
          "title": "Introduction to Cybersecurity",
          "description": "Learn the fundamentals of cybersecurity.",
          "course_url": "https://your-platform.com/courses/cybersecurity-101"
        }
      },
      {
        "origin_id": "data-privacy-basics",
        "course": {
          "type": "EXTERNAL",
          "title": "Data Privacy Basics",
          "description": "Understand GDPR and data privacy best practices.",
          "course_url": "https://your-platform.com/courses/data-privacy-basics"
        }
      },
      {
        "origin_id": "leadership-fundamentals",
        "course": {
          "type": "EXTERNAL",
          "title": "Leadership Fundamentals",
          "description": "Core skills for new and aspiring managers.",
          "course_url": "https://your-platform.com/courses/leadership-fundamentals"
        }
      }
    ]
  }'
Response:
{
  "status": "success",
  "data": {
    "task_id": "7FPJba3qRNBYaJgTvDKZi7mi"
  }
}

Polling for the result

Use the task_id from the previous step to poll the status endpoint.
curl --request GET \
  --url 'https://api.kombo.dev/v1/lms/courses/bulk/7FPJba3qRNBYaJgTvDKZi7mi' \
  --header 'Authorization: Bearer <api_key>' \
  --header 'X-Integration-Id: <integration_id>'

While the task is pending

{
  "status": "success",
  "data": {
    "task_id": "7FPJba3qRNBYaJgTvDKZi7mi",
    "status": "PENDING",
    "created_at": "2025-03-12T14:30:00.000Z",
    "completed_at": null
  }
}

When the task completes

The data field contains the results. The exact shape depends on the endpoint – check the API reference for details.
{
  "status": "success",
  "data": {
    "task_id": "7FPJba3qRNBYaJgTvDKZi7mi",
    "status": "COMPLETED",
    "created_at": "2025-03-12T14:30:00.000Z",
    "completed_at": "2025-03-12T14:30:47.000Z",
    "data": [
      {
        "origin_id": "cybersecurity-101",
        "status": "SUCCEEDED",
        "data": {
          "course_id": "26vafvWSRmbhNcxJYqjCzuJg"
        }
      },
      {
        "origin_id": "data-privacy-basics",
        "status": "SUCCEEDED",
        "data": {
          "course_id": "8kQes79O9lH0FaLxktO0yza"
        }
      },
      {
        "origin_id": "leadership-fundamentals",
        "status": "FAILED",
        "error": {
          "code": "INTEGRATION.UNKNOWN_ERROR",
          "message": "The connected tool returned an unexpected error."
        }
      }
    ]
  }
}

When the task fails

If the task fails before it can process all items, the response includes an error object. See the error handling guide for details on how to interpret it.
{
  "status": "success",
  "data": {
    "task_id": "7FPJba3qRNBYaJgTvDKZi7mi",
    "status": "FAILED",
    "created_at": "2025-03-12T14:30:00.000Z",
    "completed_at": "2025-03-12T14:30:12.000Z",
    "error": {
      "code": "INTEGRATION.AUTHENTICATION_FAILED",
      "message": "The integration credentials are no longer valid."
    }
  }
}

Idempotency

Async endpoints are idempotent by default. Sending the same request body for the same integration returns the existing task instead of creating a duplicate. This makes it safe to retry requests without risking duplicate processing – for example, if your initial request times out or you’re unsure whether it went through. Idempotency is derived automatically from the request body and integration details. Manual idempotency keys (e.g., via a header) are not currently supported. If this is something you need, please let us know. Async endpoints are designed for long-running operations that complete “eventually” rather than within seconds. The right polling interval depends on the action and the volume of data you’re processing – individual endpoints may include more specific guidance in the API reference. As a general rule:
  • Polling every few minutes is a good starting point for most use cases.
  • For very large payloads or operations that you expect to take longer, polling every 30–60 minutes may be more appropriate.
  • Stop polling once you receive a terminal status (COMPLETED or FAILED).
  • If a task stays PENDING longer than expected, don’t poll indefinitely. Set a reasonable timeout on your side and treat exceeding it as an error. The right threshold depends on the action – check the API reference for endpoint-specific guidance or reach out to our support.