Upload API
Applications and integrations can ingest assets into Fotoware by using the Upload API to transfer a file by sending it in a series of chunks.
Required permissions
Upload requests require the following:
- If non-interactive application authorization is used, then the client can upload assets to any location where upload is supported.
- If the client is impersonating a specific user then that user must have upload permission on the upload location.
Note: The maximum file size for upload is 25 GB.
Upload procedure
An asset is uploaded using the following 4 step process:
1. Send initial upload request |
POST /fotoweb/api/uploads/ Client makes a request to start an upload and specifies all the parameters (e.g. destination, filename, file size). If successful, the server creates an upload task and returns the upload details that the client uses in further requests. |
2. Send file data |
POST /fotoweb/api/uploads/<UPLOAD_ID>/chunks/<CHUNK_INDEX> Client sends a file in one or more chunks based on the details returned by the initial upload request. Server checks if all chunks have been received, based on file size. |
3. Send XMP sidecar file (if required) |
POST /fotoweb/api/uploads/<UPLOAD_ID>/xmp Client sends a XMP sidecar file based on the details returned by the initial upload request. |
4. Poll status for completion |
GET /fotoweb/api/uploads/<UPLOAD_ID>/status Client repeatedly polls the status of the upload task to monitor the progress and completion of the upload. Upload is finished when post-processing is finished on the server. |
1. Send initial upload request
The initial upload request begins the process for uploading a single asset by setting up an upload task with a unique Upload ID which is used in all following API requests. The Upload ID is not a credential, and the API requests still use the regular authentication and permission checking, which means each user can only access their own uploads.
Metadata can be specified in this step.
The destination
parameter of the initial upload request determines where the asset will be uploaded to. To upload a new asset to an archive, set the destination
to the URL of the collection. For On-Premises systems, it is possible to upload an asset to the Upload Area by setting the destination
to null
.
Metadata fields can be overwritten, extended, or deleted using metadata patch instructions in the metadata
parameter. Metadata patch instructions are applied relative to the existing XMP metadata in the uploaded asset.
Metadata of the uploaded asset is applied as follows:
- If an XMP sidecar file is uploaded, then the metadata in the XMP sidecar file is used. Any embedded metadata in the asset file itself is ignored.
- Otherwise, embedded XMP metadata in the asset file is used, if present (and if the file format supports it).
- Finally, metadata patch instructions, provided in the
metadata
attribute of the initial upload request, are applied relative to the provided XMP metadata. See the section about applying metadata for details.
Request syntax
POST /fotoweb/api/uploads/ Content-Type: application/json Accept: application/json
with a request body containing the upload request parameters in JSON format:
{ "destination": "/fotoweb/archives/5000/DocFolder/SubFolder/", "folder": "NewFolder1/NewSubFolder2/" | null "filename": "image.jpeg" | null "hasXmp": true | false, "fileSize": 3145728, "checkoutId": "..." | null, "metadata": { fields: [...], attributes: [...] } | null "comment": "..." | null }
Initial upload request parameters:
Parameter | Type | Description |
---|---|---|
destination |
URL (String) | The destination can be one of the following:
Note: The collection or asset MUST exist! |
folder |
URL (String) | The folder parameter is optional and is used to create new folders during an upload only when the destination is a collection URL for an archive with the Copy / Move / Upload to archive option set to User selection, otherwise the request will return 400 Bad Request. Also, the canCreateFolders flag must be true for the destination collection. For more information, see Ingestion.This must be set to null when using an asset URL destination or an On-Premises Upload Area destination. If the folder parameter is not present the files will be uploaded to the location specified by the destination collection URL.To create folders, set the folder parameter to a list of nested folders separated by forward-slashes (e.g. "NewFolder1/NewSubFolder2" ) which will be created relative to the location given in the destination collection URL. Leading and trailing slashes in the folder path are ignored, and multiple slashes are treated as single slashes (e.g. "/folder1//folder2/" is interpreted as "folder1/folder2" ). The special folder names . and .. are treated as invalid, as are all other invalid folder names, e.g. names containing invalid characters such as ?* .
|
filename |
String | Filename of the file being uploaded and MUST be given when uploading to a folder. If destination is an existing asset URL, then this parameter SHOULD be null when the filename of the asset is not meant to change. When uploading a new revision of an existing asset which also changes the filename or extension, then the filename parameter specifies the new filename. |
hasXmp |
Boolean | This boolean value specifies whether an XMP sidecar file is to be uploaded.
If set to |
fileSize |
Integer | Exact size of the file in bytes. |
checkoutId |
String | This parameter MUST be set to null .Used by internal Fotoware plug-ins to upload a new version of a checked-out file. |
metadata |
Object | The metadata parameter contains metadata patch instructions which are used to change the metadata of the uploaded file. See Applying metadata to fields during upload below for more details.
|
comment |
String | Comment to add to the revision. Set comment to null when not required.When uploading a new revision of a file that already exists, this becomes the comment of the new revision. |
Applying metadata to fields during upload
Metadata can be applied to an asset during upload. This ensures strong metadata consistency in Fotoware archives. If a client uploaded an asset and applied metadata later in a separate request, then these assets might be without metadata, without consistent metadata or without required metadata fields for a short period of time after the upload. Applying metadata during upload ensures that all assets in the Fotoware systems always have fully consistent metadata.
Metadata can be applied to an asset in an upload request. This is done by adding a metadata
parameter to the request body to apply fields
patch instructions to the asset:
{ ... "metadata": { "fields": [ {"id": 5, "value": "Roadrunner"}, {"id": 80, "value": "Wyle E. Coyote", "action": "add"}, {"id": 25, "action": "erase"}, {"id": 25, "action": "add", "value": ["chicken", "food"]} ] } ... }
Metadata patch instructions
In the JSON body, fields
is an array of metadata patch instructions where each instruction has the following parameters:
Parameter | Type | Description |
---|---|---|
id |
Integer | Numeric ID of the destination metadata field. This parameter is required. |
action |
String | Metadata action to perform, as described below. This parameter is optional. If omitted, the default action is "add" . |
value |
String, Array(String) | Metadata value(s) to set or add. This parameter is optional or required, depending on the action, as described below. Depending on the action and field type, this can be a single value (given as a string) or zero or multiple values (given as an array of strings). |
The action
parameter defines what the metadata patch instruction does:
|
Description |
---|---|
"add" |
Set the field to the given value or values. The Example: // If the field is a non-bag field: "value": "foo" // Replaces existing value with "foo" "value": ["foo"] // Replaces existing value with "foo" "value": [] // valid, but does nothing "value": ["foo", "bar"] // INVALID, because at most one value may be given // If the field is a bag field: "value": "foo" // Adds "foo" to existing values "value": ["foo"] // Adds "foo" to existing values "value": ["foo", "bar"] // Adds "foo" and "bar" to existing values "value": [] // valid, but does nothing |
"append" |
Append the given If the field is a bag field, then the given value is appended to the first value of the existing array of values of the field. The |
"prepend" |
Prepend the given value to the existing value of the destination field. If the field does not have a value, then this action is equivalent to If the field is a bag field, then the given value is prepended to the first value of the existing array of values of the field. The |
"erase" |
Remove the given field from the metadata. If the field is a bag field, then all values in the bag are removed. |
Example: Applying metadata to non-bag fields
The following metadata patch instructions:
{ "fields": [ {"id": 500, "value": "V1"}, {"id": 501, "action": "erase"}, {"id": 502, "action": "append", "value": "V3"}, {"id": 503, "action": "prepend", "value": "V4"} ] }
modifies the existing metadata as follows:
Field |
Existing value |
Final value |
---|---|---|
500 |
E1 |
V1 |
501 |
E2 |
(empty) |
502 |
E3 |
E3V3 |
503 |
E4 |
V4E4 |
Example: Applying metadata to a bag field
The following metadata patch instructions:
{ "fields": [ {"id": 25, "action": "erase"}, {"id": 25, "action": "add", "value": ["food", "chicken"]}, {"id": 80, "action": "add", "value": "Wyle E. Coyote"} ] }
modifies the following existing metadata as follows:
Field |
Existing values |
Final values |
---|---|---|
|
|
|
|
|
|
Note: To replace all values in a bag field with new values, use the erase
action before adding new values with the add
action.
A special case: Metadata in PDF files
When uploading PDF files to Fotoware, any metadata that is added to the PDF during upload will be committed to storage as an XMP sidecar that accompanies the PDF. This has the practical implication that when subsequently downloading the PDF from a Fotoware user interface, a zip file will be delivered with the PDF file and the sidecar included. However, when downloading PDFs using the API, for which there is no UI, only the PDF file will be delivered, and any added metadata will not be included in the downloaded file since it has not been embedded.
To successfully embed metadata into the PDF so that it can be downloaded completely via the API, there are two options:
- Apply the metadata to the PDF post-upload. This will ensure that any sidecar information is embedded in the actual PDF
- Execute a metadata patch operation on any field in the PDF post-upload. This will trigger the re-indexing of the PDF and embed any sidecar information into the PDF file.
Metadata fields that are automatically set
When uploading assets, Fotoware sets certain fields automatically. This is described here.
Order in which metadata is applied
When an asset is uploaded, any metadata changes will be applied in the following order:
-
The revision's metadata will be set to the metadata of the uploaded file when uploading a new revision.
-
The metadata in the uploaded XMP sidecar file will overwrite the asset's metadata if
hasXmp
istrue
. -
Otherwise, ALL XMP metadata embedded in the uploaded asset or an XMP sidecar file will be stored in the system, including unknown fields and fields that are not in any metadata views or in the metadata configuration (MDC).
Changing the file attributes during upload
You can change the file attributes of an asset during upload by adding a metadata
parameter to the request body to apply attributes
patch instructions to the asset:
{ ... "metadata": { "attributes": [ {"key": "mt", "value": "2022-12-24T11:22:33Z"} ] } ... }
Parameter |
Description |
---|---|
key |
Specifies which attribute to set.
|
value |
Specifies the value of the attribute. Example: "mt": "2018-01-02T11:22:33Z" The ISO 8601 format is accepted. Note: Time zone information is not retained. The value MUST be in UTC with an explicit time zone designator ( |
Example: Preserving the modification date/time of uploaded assets
When uploading assets via the API you might want to preserve the original modification time of the assets being uploaded. To do so, get the file modification date from the file to upload (e.g. using the file system API on your platform), then send it in the upload request using the "mt" attribute, as shown here:
{ "destination": "/fotoweb/archives/5000/", "filename": "sunset.jpg" "hasXmp": false, "fileSize": 895841, "checkoutId": null, "metadata": { "attributes": [ {"key": "mt", "value": "2022-02-10T14:22:33Z"} ] } "comment": null }
Full example of an initial upload request
The following example shows request parameters to initiate uploading a 5 GB video file to new subfolders within an archive with changes to the metadata and file attributes:
{ "destination": "/fotoweb/archives/5000/DocFolder/SubFolder/", "folder": "NewFolder1/NewSubFolder2/" "filename": "roadrunnervideo.mp4" "hasXmp": false, "fileSize": 5368709120, "checkoutId": null, "metadata": { "fields": [ {"id": 5, "value": "Roadrunner"}, {"id": 80, "value": "Wyle E. Coyote", "action": "add"}, {"id": 25, "action": "erase"}, {"id": 25, "action": "add", "value": ["chicken", "food"]} ], "attributes": [ {"key": "mt", "value": "2022-12-24T11:22:33Z"} ] } "comment": null }
Response: 200 OK
On success, the upload response is:
200 OK Content-Type: application/json
with a response body:
{ "id": "<UPLOAD_ID>", "chunkSize": 4194304, "numChunks": 1280, "hasXmp": false }
Parameter | Type | Description |
---|---|---|
id |
String | The <UPLOAD_ID> is a random 128-bit GUID encoded as BASE64URL. |
chunkSize |
Integer | The upload chunk size in bytes. |
numChunks |
Integer | Total number of chunks to upload. |
hasXmp |
Boolean | This boolean value specifies whether an XMP sidecar file is to be uploaded.
If set to |
Response: 400 Bad Request
On a bad request, the response is:
400 Bad Request
with a response body:
{ "value": "<VALUE>", "heading": "...", "description": "...", "technicalInfo": "..." }
where <VALUE>
is:
Bad Request <VALUE> |
Description |
---|---|
invalid_argument |
If one or more request parameters have invalid parameters (such as fileSize being negative), or the request body is in an invalid JSON format. |
metadata_not_allowed |
Ifmetadata was not null or hasXmp was true . |
destination_invalid_url |
If the value of destination was an invalid URL. |
destination_not_found |
If the asset or collection given by destination was not found or the archive was disabled. |
destination_invalid_asset |
If destination refers to an asset URL and the uploaded revision file type is unsuitable for existing asset (e.g. file extension of uploaded revision file is not compatible with file extension of existing asset). |
destination_invalid_location |
|
invalid_filename |
|
invalid_folder |
If folder is not null , and
|
invalid_checkout_id |
If checkoutId was given and was not valid. |
The other Bad Request attributes (heading
, description
, technicalInfo
) are the standard human-readable error information returned by all API endpoints.
Response: 403 Forbidden
On a forbidden request, the response is:
403 Forbidden
This error will occur if:
- the user does not have permission to upload in the destination archive
- the user does not have the Allow upload to upload area group permission and
destination
isnull
- the
checkoutId
exists but does not belong to the user.
2. Send file data
Using the information from the initial upload response begin uploading the file in "chunks".
Chunking
The chunk size is returned in the response to the initial upload request. The number of chunks of a file depends only on the file size:
numChunks = ceil(fileSize / chunkSize)
Chunk indexes are zero-based so the first chunk to upload will have <CHUNK_INDEX> = 0
.
Chunks can be uploaded in any order, even in parallel. The client application will need to keep track of which chunks have been successfully received by the server or have failed and need to be retried. The server will return 204 No Content
when a chunk has been successfully received. Truncated upload requests are rejected.
Clients may retry uploading the same chunk after a request timeout. If a chunk has already been received successfully, then a request to upload it again will fail with 409 Conflict
with no other side effects. If any number of multiple upload requests of the same chunks are made simultaneously, then at most one of them may return 204 No Content
(and its content will be used). All others should fail with 409
Conflict
if no other error occurs. In this case, it is unspecified which request will succeed. This means that it is safe to retry upload requests, even when it is unknown if the server has received a previous attempted request, as long as all requests to upload the same chunk have an identical payload.
Once all the chunks have been successfully received, the upload is finalized on the server by creating an upload background task to merge the chunks into a single file. Uploading chunks after an upload is finalized results in a 409 Conflict
error but has no other side effects and the upload background task will continue.
If an upload is not finalized, and no more chunks have been received for a certain amount of time, then the upload has expired. All future attempts to upload with the same <UPLOAD_ID>
will result in response 404 Not Found
.
HTTP Multipart requests
Upload uses HTTP Multipart requests, as defined in RFC1341 (external link).
Note: You usually do not need to implement RFC1341 yourself. Most likely, the HTTP client library of your programming platform, which you use for sending HTTP requests, has built-in support for HTTP multipart requests. This is the standard format for uploading files, so check if your HTTP client library has functions for attaching files to requests.
All line breaks must be CR+LF
. For more information, see RFC 7578 (external link).
Request syntax
POST /fotoweb/api/uploads/<UPLOAD_ID>/chunks/<CHUNK_INDEX> Content-Type: multipart/form-data;boundary=<RANDOM_BOUNDARY> --<RANDOM_BOUNDARY> Content-Disposition: form-data;name="chunk";filename="chunk" Content-Type: application/octet-stream <BINARY_CHUNK_DATA> --<RANDOM_BOUNDARY>--
Parameter | Type | Description |
---|---|---|
<UPLOAD_ID> |
String | The Upload ID returned by the initial upload request. |
<CHUNK_INDEX> |
Integer | Chunk index is the unique zero-based number of the chunk which needs to be incremented by 1 for each chunk. The first chunk to upload will have <CHUNK_INDEX> = 0 . The last chunk will have <CHUNK_INDEX> = numChunks-1 . |
<RANDOM_BOUNDARY> |
String | A random string (at least 16 bytes) created by the client. |
name |
String | The example "chunk" value for name is ignored. |
filename |
String | The example "chunk" value for filename is ignored. |
<BINARY_CHUNK_DATA> |
Binary data | The content of the chunk that will contain chunkSize bytes of binary data. |
Here is an example of a complete upload request for a file chunk:
POST /fotoweb/api/uploads/<UPLOAD_ID>/chunks/<CHUNK_INDEX> Content-Type: multipart/form-data; boundary=WUG7zDvyA3hVq16Q --WUG7zDvyA3hVq16Q Content-Disposition: form-data;name="chunk";filename="chunk" Content-Type: application/octet-stream <BINARY_CHUNK_DATA> --WUG7zDvyA3hVq16Q--
Response: 204 No Content
On success, the upload chunk response is:
204 No Content
Response: 404 Not Found
If there is no upload task with ID <UPLOAD_ID>
that belongs to the requesting user, the upload chunk response is:
404 Not Found
Note: If an upload is not finalized, and no more chunks have been received for a certain amount of time, then the upload has expired. All future attempts to upload with the same <UPLOAD_ID>
will result in 404 Not Found
Response: 415 Unsupported Media Type
If the request body is not multipart/form-data
, the upload chunk response is:
415 Unsupported Media Type
Response: 400 Bad Request
On a bad request, the upload chunk response is:
400 Bad Request
with a response body:
{ "value": "<VALUE>", "heading": "...", "description": "...", "technicalInfo": "..." }
where <VALUE>
is:
Bad Request <VALUE> |
Description |
---|---|
invalid_chunk_index |
If the chunk index is negative or greater than the number of chunks (numChunks ) for the given upload task . |
invalid_chunk_size |
If the chunk size is wrong and doesn't match the chunkSize returned by the initial upload response. |
invalid_number_of_files |
If no files or more than one file were attached to the request body. |
any other value | If the request is otherwise invalid. |
The other Bad Request attributes (heading
, description
, technicalInfo
) are the standard human-readable error information returned by all API endpoints.
Response: 409 Conflict
When a chunk with the same index has already been received successfully or the same chunk is already being uploaded by another process, the upload chunk response is:
409 Conflict
with a response body:
{ "value": "<VALUE>", "heading": "...", "description": "...", "technicalInfo": "..." }
where <VALUE>
is:
Conflict <VALUE> |
Description |
---|---|
already_finalized |
If the upload task is not in awaitingData state. |
already_uploaded |
If the chunk with the same index has already been uploaded. |
The other Bad Request attributes (heading
, description
, technicalInfo
) are the standard human-readable error information returned by all API endpoints.
3. Send XMP sidecar file (optional)
This step is only necessary if the original file has metadata stored in an XMP sidecar file. If the original file does not have an XMP sidecar file, this step can be skipped.
This is an optional request to upload an XMP sidecar file that contains the metadata of the asset file. If the asset has an XMP sidecar file, then the initial upload request must declare it usinghasXmp
is true
. The XMP sidecar file can then be uploaded in one go using the following request, and the upload will be finalized once the server has received all the chunks and the XMP sidecar file.
POST /fotoweb/api/uploads/<UPLOAD_ID>/xmp Content-Type: multipart/form-data;boundary=<RANDOM_BOUNDARY> --<RANDOM_BOUNDARY> Content-Disposition: form-data;name="xmp";filename="xmp" Content-Type: application/octet-stream <XMP_DATA> --<RANDOM_BOUNDARY>--
HTTP Multipart requests
Upload uses HTTP Multipart requests, as defined in RFC1341 (external link).
Note: You usually do not need to implement RFC1341 by yourself. Most likely, the HTTP client library of your programming platform which you use for sending HTTP requests has built-in support for HTTP multipart requests. This is the standard format for uploading files, so check if your HTTP client library has functions for attaching files to requests.
All line-breaks must be CR+LF
. For more information, see RFC 7578 (external link).
Parameter | Type | Description |
---|---|---|
<UPLOAD_ID> |
String | The Upload ID returned by the initial upload request. |
<RANDOM_BOUNDARY> |
String | A random string (at least 16 bytes) created by the client. |
name |
String | The example "xmp" value for name is ignored. |
filename |
String | The example "xmp" value for filename is ignored. |
<XMP_DATA> |
Binary data | The content of the XMP sidecar file. |
Response: 204 No Content
On success, the XMP sidecar file upload response is:
204 No Content
Response: 404 Not Found
If there is no upload task with ID <UPLOAD_ID>
that belongs to the requesting user, the XMP sidecar file upload response is:
404 Not Found
Response: 415 Unsupported Media Type
If the request body is not multipart/form-data
, the XMP sidecar file upload response is:
415 Unsupported Media Type
Response: 400 Bad Request
On a bad request, the XMP sidecar file upload response is:
400 Bad Request
with a response body:
{ "value": "<VALUE>", "heading": "...", "description": "...", "technicalInfo": "..." }
where <VALUE>
is:
Bad Request <VALUE> |
Description |
---|---|
invalid_metadata |
If the XMP data is malformed. |
metadata_not_allowed |
If the initial upload task was created without hasXmp set to true . |
invalid_number_of_files |
If no files or more than one file were attached to the request body. |
any other value | If the request is otherwise invalid. |
The other Bad Request attributes (heading
, description
, technicalInfo
) are the standard human-readable error information returned by all API endpoints.
Response: 409 Conflict
On a conflict, the XMP sidecar file upload response is:
409 Conflict
with a response body:
{ "value": "<VALUE>", "heading": "...", "description": "...", "technicalInfo": "..." }
where <VALUE>
is:
Conflict <VALUE> |
Description |
---|---|
already_finalized |
If the upload task is not in awaitingData state. |
already_uploaded |
If the XMP sidecar file has already been uploaded. |
The other Bad Request attributes (heading
, description
, technicalInfo
) are the standard human-readable error information returned by all API endpoints.
4. Poll status for completion
This request is made to poll the status of the upload.
GET /fotoweb/api/uploads/<UPLOAD_ID>/status Accept: application/json
Response: 200 OK
On success, the status response is:
200 OK Content-Type: application/json
with a response body:
{ "status": "awaitingData" | "pending" | "inProgress" | "done" | "failed", "result": { "assetUrl": "/fotoweb/archives/5000/DocFolder/SubFolder/file.jpeg" | null, "assetDetails": {...} | null } | null, "error": { "value": "...", "message": "..." } | null }
Parameter | Type | Description |
---|---|---|
status |
Enum (string) | The status of the <UPLOAD_ID> upload task.
The |
result |
Array of Objects | The result of the upload task. This is null unless status is done . |
result.assetUrl |
URL (String) | The assetUrl is the API URL of the uploaded asset.If the input location of the archive is a custom file system folder, then assetUrl isnull . |
result.assetDetails |
Asset (Object) | The assetDetails object is the asset representation of the asset.If the input location of the archive is a custom file system folder, then assetDetails is null . |
error |
Array of Objects | The error reported by the upload task. This is null unless status is failed . |
error.value |
Enum (String) | Symbolic name of the type of error that has occurred when uploading this asset. Can be used programmatically to distinguish between errors.
|
error.message |
String | Human-readable description of error that has occurred that can be displayed to a user. This attribute is null if the asset has been uploaded successfully and status is not failed .This attribute is not present if status is done . |
Response: 403 Forbidden
On a forbidden request, the status response is:
403 Forbidden
This error will occur if the <UPLOAD_ID>
does not exist OR belongs to a different user.
Using the Upload API for Version Control
The Version Control system allows an existing asset to be replaced with a new revision by setting the destination
to the URL of the asset in the initial upload request. The uploaded asset will be added as the latest revision and replace all the metadata with the contents of the uploaded file.