Skip to main content
A File Transfer is the record of one file Twine delivered to a connected system. You upload a file through the API naming where it should go, Twine delivers it, and the record tells you what the destination system called the document once it arrived. That last part is usually the point. A learning platform issuing a certificate does not just need the PDF filed in the HR system; it needs the HR system’s identifier for that document so it can reference it from the employee’s competence assignment. Twine never links a file to a competence assignment on your behalf — it hands you the identifier and you supply it where you need it.
Twine does not keep your files. An uploaded file is removed shortly after it has been delivered. The file transfer record — including the destination’s identifier for the document — is kept indefinitely, so a transfer from months ago is still readable long after the file is gone.

Uploading

Send the file base64 encoded, alongside where it should go:
The response comes back immediately, before the file has been delivered:
The decoded file may be at most 5 MB.

Where the file goes

A file travels through a file transfer domain mapping that has Twine on the source side and your destination system on the target side. If your organization has exactly one such mapping, it is used automatically and you can omit domain_mapping_id. Otherwise, name the one you want: list them with GET /v1/org/domain-mappings and pick the one whose from_domain is file_transfer. Naming a mapping that does not exist is rejected with a 422 rather than accepted and left undelivered, so a mistyped id fails immediately instead of at poll time. Twine does not check whether the destination can actually accept the file. If something is missing or unsupported, the transfer fails during delivery and the reason appears on the job — see Status below.

Employees

employee_id is the Twine employee UUID. Twine resolves it to that employee’s identifier in the destination system itself, so you never need to know the remote id. The field is optional, because not every document belongs to someone. Be aware that destinations which file documents under an employee will reject one that has none — Hibob, for example, stores documents per employee and nowhere else. Destinations addressed by path, such as SFTP and S3, take files with no employee happily.

Retrying safely

Supply an idempotency_key of your own choosing, unique within your organization — something stable per document, such as your own identifier for the certificate. Repeating a request with a key you have used before always returns 200 and the original transfer’s id, never a duplicate record. What happens next depends on how the first attempt ended: So a retry after a network timeout is safe, and so is a retry after a delivery failure — you never need a fresh key to get a failed document moving again. A retry re-sends the file you post with it (Twine does not keep the original bytes), and the transfer record adopts that file’s name, size and checksum.

Polling for the identifier

Delivery is asynchronous, so the identifier is not available on the response to the upload. Poll the transfer until it is:

What the identifier looks like

Destination systems do not agree on how a document is addressed, so a transfer carries three fields and fills in whichever apply:
  • remote_id — the destination’s own identifier for the document. This is what most HR systems return and what you will usually want.
  • remote_path — where the file landed, for destinations addressed by path rather than by id (SFTP, S3). For these, the path is the identity; there is no id.
  • remote_employee_id — the employee’s identifier in the destination. Some systems only address a document as the pair of this and remote_id.
A few systems return nothing identifying at all on upload. Those transfers reach delivered with every remote field null, which means the file arrived but the destination gave nothing to reference it by.
A file that was already there is delivered with no remote_id. If a document with the same name already exists in the destination and the domain mapping does not have overwrite enabled, Twine does not upload the file a second time. The transfer is reported as delivered — the document you wanted in the destination is in the destination — but because no upload happened, there is no identifier to report.This is indistinguishable from “the destination returns no identifier” unless you look: check remote_metadata.note, which explains it in plain text on exactly these transfers.If you need the identifier back every time, either enable overwrite on the domain mapping, or upload under a name you know is unique.

Status

Conditions

Uploads are subject to the same conditions as any other domain. Two are checked, and both must pass:
  • File transfer conditions are evaluated against the uploaded file itself — its content_type, size, original_name and so on. This is where a rule like “PDFs only” belongs.
  • Employee conditions are evaluated against the employee the file belongs to, when you supplied an employee_id. A file with no employee skips this check rather than failing it.
A file that any condition excludes reaches rejected, not failed — it was never sent, and nothing went wrong. If an upload you expected to land keeps coming back rejected, check the conditions on the destination’s system integration and on its file transfer domain mapping. status is worked out from the delivery job each time you read the transfer rather than being stored on it. That keeps it honest, but it also means you cannot filter a list by status. To find transfers in a particular state, filter Jobs instead, or read status off each transfer.

Listing

Returns file transfers newest first, with the usual cursor pagination and filtering. You can filter by employee_id, system_integration_id, file_name, inserted_at and updated_at. This lists transfers Twine performed. It is not a view of the documents that exist in a connected system — Twine does not mirror the destination’s document library.

Your own reference data

The optional metadata object is returned unchanged on every read of the transfer, which is a convenient place to keep the identifier your own system knows this document by. It takes a flat object with string keys and string, number or boolean values, up to 2 KB encoded. It is also readable from property mappings, which is how a value you send with the upload can influence the delivery itself. Map metadata on the File transfer side and pull a key out of it — filing a document in a folder your own system chooses per upload, rather than one the integration fixes for every file, is the usual reason to. See Hibob for a worked example.
Two keys are reserved and always describe the transfer itself, so sending them has no effect: source (always public_api) and content_sha256 (the checksum Twine computed).