Skip to main content
POST /developer/v1/ingest/{source_type} accepts a batch of records from an external system (an HRIS, payroll, or your own directory) and queues it for asynchronous processing. One request carries records of one kind: legal entities, departments, job titles, persons, or employees. Use it to seed a tenant from another system and to keep it in sync afterwards. Re-sending a record with the same external_id updates the object it created.

Request

Body: A complete request that loads one person:
Response 200:
ACCEPTED means the batch was stored and queued. Whether each record was applied is known only after processing, see Results.
The Idempotency-Key header is required: without it the response is 400 with the code INTEGRATION.IDEMPOTENCY_REQUIRED. Re-sending with the same key does not create a second load — you get a 409 carrying that same code. If the batch content changes, change the key too.

Who the key belongs to matters

The batch is applied on behalf of the key owner. Two consequences:
  • The key must be created by a super-administrator: ingest:write is available only to keys of users holding the PERMISSIONS:MANAGE permission.
  • Reading the result back with GET /employees is bounded by the owner’s permission profile. Create the key as an administrator whose profile covers the entire company, otherwise the list will miss employees you just loaded, and an owner with no profile at all gets 400 METADATA.FIELD_NOT_VISIBLE.

Load order

References between records are resolved by external_id, but not every object can be referenced before it exists. Send batches in this order:
1

ORGANIZATIONS

Legal entities. Everything else references them.
2

DEPARTMENTS

A parent department may appear in the same batch or an earlier one.
3

JOB_TITLES

4

PERSONS

Persons must be loaded before employees. An employee whose external_person_id is not loaded yet fails.
5

EMPLOYEES

Links a person to a legal entity, department and job title.
A department, job title or legal entity referenced before its own record arrives is created as an empty placeholder and filled in when its batch comes. Persons have no placeholder: a person is an account resolved by IIN. Batches are processed by several workers at once, so ACCEPTED on the previous request does not mean it has been applied. Placeholders make the order of ORGANIZATIONS, DEPARTMENTS and JOB_TITLES forgiving, but send EMPLOYEES only after the PERSONS batch has had time to apply (a few seconds per hundred records), and re-send the employees that failed because their person was not there yet.

Matching by external_id

Every record carries an external_id — the object’s identifier in your system. The platform stores an external_id → internal id binding per source_type:
  • a record with a known external_id updates the existing object;
  • a record with a new external_id creates an object only when create_missing: true, otherwise it is skipped;
  • references between records (external_department_id, external_person_id, etc.) also use external identifiers — the platform resolves them itself.
The external_id binding lives on the platform side and is not returned by GET /employees: the Employee object has no external_id field and no filter for it. To reconcile data after a load, keep the mapping on your side or rely on natural keys (the employee_number, the IIN).
Bindings are the only match key. A person or employee that already existed in the tenant before the integration (created by hand or by another source) is not matched by IIN or employee number: a record with a new external_id tries to create a second person with the same IIN and fails. Start the integration on a tenant whose employees come only from this source, or agree the migration of existing records with support.

Record schemas

Fields outside the schema are ignored. Dates are YYYY-MM-DD strings. Empty strings are treated as “not set”.

ORGANIZATIONS

DEPARTMENTS

JOB_TITLES

PERSONS

A person is the identity behind an employee. Loading a person creates or finds the platform account by IIN; the email becomes the login of that account when it has none yet.
Identity-document fields (passport number, issuing authority, registration address, citizenship, gender) are not loaded through ingest today. Send them and they are ignored; fill them in the app or wait for the schema to grow.

EMPLOYEES

Not covered by the employee schema today: employment type, manager, work email, location. They stay as set in the app. Reinstating a terminated employee through ingest is not supported; do it in the app.

1C · EMPLOYEES_FULL and DEPARTMENTS

With source_type = 1C the records follow the 1C export format — an employee together with the person, fields in Russian: ТабельныйНомер, ДатаПриема, Организация.Ссылка, Подразделение.Ссылка, Должность.Ссылка, and a nested ФизЛицо object (Имя, Фамилия, Отчество, ИИН, Телефон, ДатаРождения). 1C · DEPARTMENTS carries Ссылка, Наименование, Родитель.Ссылка. Use the generic KEDO schema for any other system.

Updates and terminations

Send the record again with the same external_id and the new values. Always send the complete record, not only the changed fields. To terminate an employee, send the employee record with left_date: the employee becomes TERMINATED and end_date is set.
An employee record without left_date is applied as ACTIVE. If a terminated employee is re-sent without left_date (for example in a full re-sync), the employee is reactivated. Keep left_date in every batch that carries a terminated employee, or leave terminated employees out of later batches.
Deleting objects through ingest is not supported.

Results

Processing is asynchronous. For each batch the platform keeps a processing log with four counters: There is no endpoint yet that returns this log or per-record errors for an ingest_request_id. Practical ways to confirm the result:
  1. Wait for processing: batches of hundreds of records take seconds.
  2. Re-read the data: GET /employees?updated_since=…&expand=person returns the created and updated employees, GET /employees/{id} a single one.
  3. Subscribe to the employee.changed webhook — every changed profile arrives as an event.
Records that fail do not stop the batch: the rest is applied. Fix the failed records and re-send them in a new batch with a new Idempotency-Key.
An unknown payload_type or source_type is not rejected: the request is accepted and every record is counted as failed. Double-check the spelling — a typo does not return a 400.

Errors

Next steps

Employee sync

Full and incremental export back out.

Limits

Request rates and sizes.