xYago APIv1

Importación de empleados

Carga o sincroniza hasta 500 empleados por petición. xYago procesa el fichero en segundo plano y te devuelve el resultado fila a fila. Ideal para la carga inicial desde el ERP y para sincronizaciones nocturnas.

Flujo

  1. Encola la importación: POST /employees/import con {"rows": [...]} (1 a 500 filas). Respuesta 202 con job_id.
  2. Consulta el estado con GET /employees/import/{job_id} cada 2-5 segundos hasta que status sea completed o failed.
  3. Revisa results: cada fila indica created, updated o failed con su error. Corrige las fallidas en el ERP y reenvíalas.

Cada fila

Una fila tiene la forma del cuerpo de POST /employees (ver la referencia) y external_id es obligatorio: es la clave del upsert.

Ejemplo

curl

curl -X POST "https://api.xyago.com/v1/employees/import" \
  -H "Authorization: Bearer $XYAGO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "rows": [
      {
        "external_id": "SIESA-000123",
        "first_name": "Valentina",
        "last_name": "Rojas Castaño",
        "phone_number": "+573005550123",
        "email": "valentina.rojas@example.com",
        "document_number": "1020304050",
        "job_title": "Auxiliar de bodega",
        "contract_type": "Full-time fixed-term",
        "hire_date": "2026-02-01",
        "contract_end_date": "2027-01-31",
        "work_shifts": [{"day": "Monday", "start": "07:00", "end": "16:00"}]
      },
      {"external_id": "SIESA-000087", "job_title": "Conductor de reparto"}
    ]
  }'
# => 202 {"job_id": "0b8c6d4e-2f1a-4c3b-9e5d-7a6b8c9d0e1f", "status": "queued"}

curl "https://api.xyago.com/v1/employees/import/0b8c6d4e-2f1a-4c3b-9e5d-7a6b8c9d0e1f" \
  -H "Authorization: Bearer $XYAGO_API_KEY"

Python

import os
import time

import requests

BASE_URL = "https://api.xyago.com/v1"
session = requests.Session()
session.headers["Authorization"] = f"Bearer {os.environ['XYAGO_API_KEY']}"


def import_employees(rows):
    response = session.post(f"{BASE_URL}/employees/import", json={"rows": rows}, timeout=60)
    response.raise_for_status()
    job_id = response.json()["job_id"]
    while True:
        job = session.get(f"{BASE_URL}/employees/import/{job_id}", timeout=30).json()
        if job["status"] in ("completed", "failed"):
            return job
        time.sleep(3)


job = import_employees(rows_from_erp)  # lista de dicts, máx. 500 por llamada
for row in job["results"]:
    if row["status"] == "failed":
        print(row["external_id"], row["error_code"], row["error_message"])

C#

var accepted = await http.PostAsJsonAsync("employees/import", new { rows = rowsFromErp });
accepted.EnsureSuccessStatusCode();
var jobId = (await accepted.Content.ReadFromJsonAsync<JsonElement>()).GetProperty("job_id").GetString();

JsonElement job;
do
{
    await Task.Delay(TimeSpan.FromSeconds(3));
    job = await http.GetFromJsonAsync<JsonElement>($"employees/import/{jobId}");
} while (job.GetProperty("status").GetString() is "queued" or "processing");

foreach (var row in job.GetProperty("results").EnumerateArray())
    if (row.GetProperty("status").GetString() == "failed")
        Console.WriteLine($"{row.GetProperty("external_id")}: {row.GetProperty("error_code")}");

Resultado

{
  "job_id": "0b8c6d4e-2f1a-4c3b-9e5d-7a6b8c9d0e1f",
  "status": "completed",
  "total_rows": 3,
  "processed_rows": 3,
  "created": 1,
  "updated": 1,
  "failed": 1,
  "created_at": "2026-03-02T15:00:00Z",
  "started_at": "2026-03-02T15:00:01Z",
  "finished_at": "2026-03-02T15:00:04Z",
  "results": [
    {
      "row_index": 0,
      "external_id": "SIESA-000123",
      "status": "created",
      "employee_id": "2c9a4e71-5b3d-4f8e-a6c2-7d1e0f9b3a54",
      "error_code": null,
      "error_message": null
    },
    {
      "row_index": 1,
      "external_id": "SIESA-000087",
      "status": "updated",
      "employee_id": "5e7b1d93-2a4c-4b6e-8f0a-3c5d7e9f1b26",
      "error_code": null,
      "error_message": null
    },
    {
      "row_index": 2,
      "external_id": "SIESA-000150",
      "status": "failed",
      "employee_id": null,
      "error_code": "validation_error",
      "error_message": "phone_number: Field required; job_title: Field required"
    }
  ]
}
statusqueued → processing → completed. failed indica que el job entero no se pudo procesar (no que alguna fila falle): reenvíalo.
total_rows, processed_rowsProgreso.
created, updated, failedContadores por resultado.
results[]Una entrada por fila: row_index (posición en rows, desde 0), external_id, status, employee_id y, si falló, error_code y error_message.

Errores por fila

Además de cualquier error de POST/PATCH /employees (employee_already_exists, email_already_registered, invalid_employee_data, upstream_error... ver errores):

invalid_rowLa fila no es un objeto JSON.
invalid_external_idFalta external_id o no tiene un formato válido.
duplicate_external_idEl external_id aparece más de una vez en la misma importación.
validation_errorLa fila no cumple el esquema de POST /employees (o de PATCH).

Reglas útiles