Uploading assets using the API
Note: This legacy upload API is deprecated and will be set to End of Life on January 1st, 2025. It is replaced with Upload API. Existing integrations must be rewritten to use this new Upload API.
Applications and integrations can ingest assets into FotoWeb by using the Upload API.
Assets can be added to archives either by copying or moving them from other archives or by uploading new files. The upload API also supports creating new folders and folder hierarchies and applying metadata to assets immediately upon ingestion.
Required permissions
Upload requires authentication.
- If server-to-server authentication is used, then the client can upload assets at any location where upload is supported.
- If the client is impersonating a specific user (when using user server-to-server API authentication, for example), then that user must have upload permission on the upload location.
Request URL
Assets are uploaded by making a POST request to the URL of the collection to upload to. Each request can contain one or more assets.
It is also possible to create a new folder (or a hierarchy of nested folders) and upload assets to the new folder. This is done by adding all new folders to the end of the URL as follows:
POST upload_location_url/
where
Parameter |
Description |
---|---|
upload_location_url |
URL of the collection (e.g., archive or folder) to upload to (upload location) |
Note:
- The upload location URL (
upload_location_url
) must be the URL found in thehref
attribute of the collection representation of the selected upload destination collection. Please do not hard-code upload location URLs. - To get the upload URLs of suitable upload destinations, please see Ingestion. This API can also be used to create an interface that allows the user to choose an upload destination.
- To get the upload request URL of an already known collection (with a known API URL), first request the collection representation of that collection, then use the value of the
href
attribute for the upload request.
Which collections support uploading?
To determine the upload location (which is the request URL of the upload request), you must first find a collection that supports upload.
Assets can only be uploaded to archives. Furthermore, assets can only be uploaded to certain locations within archives:
- If the archive has a custom input folder, then assets can only be uploaded to the root URL of the archive. It is not possible to upload to a specific sub folder or to create a new sub folder during upload.
- If the archive does not have a custom input folder, then assets can only be uploaded to physical folders. A physical folder is any sub folder that represents an actual folder on disk. For example, the root of an archive or the root of a union member is NOT a physical folder.
It is recommended to use the upload destination list API to discover possible upload locations and/r to check the properties of a particular collection to see if it is a valid upload location. For more information, see ingestion.
Request body
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.
Here is an example of a complete upload request for one file:
POST /fotoweb/archives/5000-Wirefeed/Incoming/ Accept: application/vnd.fotoware.upload-response+json Content-Type: multipart/form-data; boundary=WUG7zDvyA3hVq16Q authentication headers --WUG7zDvyA3hVq16Q Content-Disposition: form-data; name="folder" folder1/folder2/.../ --WUG7zDvyA3hVq16Q Content-Disposition: form-data; name="Filedata"; filename="image.jpg" Content-Type: image/jpeg binary image data --WUG7zDvyA3hVq16Q--
Requests MUST include the following header:
Accept: application/vnd.fotoware.upload-response+json
This is for backward-compatibility reasons. Omitting this exact header will invoke the old (synchronous) behavior of the upload API, which is no longer recommended, due to issues with large files and ambiguity on folder creation. Existing integrations using the old upload API will continue to work, but synchronous uploads are no longer recommended and no longer documented here. We recommend updating old upload integrations.
where:
Parameter |
Description |
---|---|
|
Boundary between parts of the request. Can be chosen freely by the client. The string is the same for all parts of the request. It should be different for each request, random and long enough to minimize the chance of it occurring in the uploaded files. HTTP client libraries usually take care of this. In the example above, "WUG7zDvyA3hVq16Q " was chosen as the boundary string. Note that the boundary string is preceded by two dashes (-- ) at the end of each part. |
filename |
The filename that the asset will have in FotoWeb after uploading. Must be specified for each part of the request that contains a file. The filename can be chosen freely and does not need to be identical to the filename of the file on the local disk on the client. HTTP client library functions should expect this as a parameter. |
Content-Type |
Ignored by FotoWeb. Should match the MIME type of the file, but can safely be set to application/octet-stream regardless of file type. |
folder1/folder2/.../ |
Hierarchy of new folders to be created at the destination. This parameter is optional. If it is not present, then all assets will be uploaded to the collection specified by the request URL. If this parameter is present, then it must contain a list of nested folders, separated by slashes, to be created in the collection specified by the requested URL. All assets will be uploaded to the innermost folder.
|
Note:
All blank lines are required, and all line breaks that are part of the request body syntax must be in CRLF (carriage-return + line-feed; "\r\n"
) format.
Update Response and Status Polling
Once the file transfer has finished, the API returns a response with status code 202 Accepted
. At this time, the upload is not finished. The server has to do further processing, such as applying metadata, doing consistency and security checks, copying the file to its storage location, and generating renditions. It is not guaranteed that all of these operations will be completed successfully. Clients should not attempt to make any API requests that rely on the existence of the uploaded assets before the upload has finished successfully.
When an upload has finished, and assuming no further changes have been made, the following guarantees are made:
- An API request to the asset returns a valid result.
- The asset is contained in the asset list of the collection containing it as long as the asset list is requested with the default sorting order and no search query on the collection.
- The asset is contained in the asset list of the collection containing it if the asset list is requested with a search query matching the asset.
- Quick renditions, previews, and thumbnails of the asset are available via the API.
The following are NOT guaranteed:
- Video renditions are NOT NECESSARILY available. A client can check the status of video renditions of an asset by requesting its API representation.
- The asset is NOT NECESSARILY returned in search results that should match it. Search indexing may be delayed.
- The asset is NOT NECESSARILY contained in the asset list of the collection containing it if the asset list is requested with a non-default sorting order.
An upload webhook, if configured, fires once an upload has finished successfully.
The Location
header of the response is a URL that can be used for polling the status of the upload.
202 Accepted Content-Type: application/vnd.fotoware.upload-response+json Location: http://FOTOWEB_HOSTNAME/fotoweb/me/background-tasks/0735ca40-3a93-11e9-ae8d-34e12de8a773
with a request body:
{ "href": "/fotoweb/me/background-tasks/0735ca40-3a93-11e9-ae8d-34e12de8a773" }
where
Attribute | Type | Description |
---|---|---|
href | URL (string) | Relative URL of the background task |
The Location
header, as well as the href
attribute is the URL of the background task, which is used in the poll requests described below. The Location
header contains an absolute URL, whereas the href
attribute contains a relative URL.
Polling the background task
To poll the status of the upload task, the client makes the following requests:
GET /fotoweb/me/background-tasks/0735ca40-3a93-11e9-ae8d-34e12de8a773 Accept: application/vnd.fotoware.upload-status+json
If the upload task exists, the following response is returned:
200 OK Content-Type: application/vnd.fotoware.upload-status.json
with the following body:
{ "job": { "status": "pending" | "inProgress" | "done" | "failed", "result": [ { "href": "/fotoweb/archives/5000-Wirefeed/Incoming/image.jpg", "done": true | false, "originalFilename": "image.jpg", "errorCode": "..." | null, "errorMessage": "..." | null, "asset": {...} | null, }, {...}, ... ] | null, "updates": { "frequency": 100, "href": "/fotoweb/me/background-tasks/0735ca40-3a93-11e9-ae8d-34e12de8a773", "type": "replace" } }, "task": { "status": "pending" | "inProgress" | "done" | "failed", "created": "2019-02-27T13:24:36.708Z", "modified": "2019-02-27T13:24:36.708Z", "href": "/fotoweb/me/background-tasks/0735ca40-3a93-11e9-ae8d-34e12de8a773", "type": "upload", } }
If the background task does not exist, the response is:
404 Not Found
If the background task belongs to a different user than the requesting one, the response is:
403 Forbidden
where
Attribute |
Type |
Description |
---|---|---|
job.status |
Enum (string) |
Status of the background task. Can have the following values:
See the discussion of error handling below. |
result |
Array of objects |
Result of the background task. List of uploaded files, their status, and their location in FotoWeb Do NOT assume that the order of entries in this array is the same as in the upload request! See the description of the This is |
result.asset |
Asset (Object) |
Full API representation of the asset. Its API URL, in This attribute may be
The attribute represents the state of the asset at the time the upload has finished. If the asset's state (e.g., its metadata) has changed since, then this attribute may be out of date. Request the asset representation of the asset using its API URL, |
result.done |
Bool |
|
result.errorCode |
String |
Symbolic name of the type of error that has occurred uploading this asset. Can be used for programmatically handling different errors in different ways. Currently unspecified, reserved for future use. This attribute is This attribute is not present if |
|
String |
Human-readable description of the error that has occurred uploading this asset. Can be displayed to a user. This attribute is This attribute is not present if |
result.href |
URL (String) | The final API URL of the uploaded asset. Can be used for making further API requests to it (e.g., assigning additional metadata). |
result.originalFilename |
String |
The original filename of an uploaded file, as given in the upload request. This allows the client to identify which file an entry in the This is the filename specified in the upload request, with no modifications. FotoWeb may change the filename during the upload to generate a compliant and unique filename. The final filename of the file after the upload can be found in |
|
Date/time (ISO 8601 string) | Time when task was created |
|
Date/time (ISO 8601 string) | Time when task status has last changed |
|
Enum (string) | This is always "upload" . |
|
Integer |
Recommended time to wait (in milliseconds) until making the next poll request. |
updates.href |
URL (string) |
URL of the background task for making the next polling request. Although the background task URL does not change in the current implementation, clients MUST use this URL to make the next poll request. |
updates.type |
Enum (string) | This is always "replace" . |
Error handling
If the job.status
attribute of an upload task result is done
, then upload of ALL files has succeeded. The location of each file can be checked in the result
attribute.
If the job.status
attribute of an upload task result is failed
, then upload of ONE OR MORE files has failed, but some files may have been uploaded successfully. The status of each file can be checked in the result
attribute. If the errorCode
attribute in an entry in result
is null
, then that file was uploaded successfully. Otherwise, errorCode
and errorMessage
contain further information about the error. Clients may retry to upload these files. The server guarantees that they were not transferred to their final storage (or have been deleted again).
If a poll request fails with a status code above 500, then clients should repeat polling with the same frequency. It is at the discretion of the client to stop polling or increase the interval in this case.
If a poll request fails with a status code of or above 400 and below 500, then clients should stop polling. Such errors are either unexpected or due to bugs in the client or server. An error message should be shown to the user, and the status of the upload is unknown.
User interface considerations
As long as an upload task is pending, the upload should be shown as in progress.
Progress bars are easy to calculate during file transfer (the initial upload request), but during polling/post-processing, it is hard to know the current status, so a progress bar should show either 100% or something close to it (which is also the maximum of the file transfer process), such as 99%.
It is not possible to abort/cancel post-processing. If the client simply stops polling, the status of the upload will be unknown.
It is OK to first complete all upload requests and poll the status of all uploads at the end. This makes most sense for a modal dialog (but it shows the status to the user only when all uploads have finished). A user interface that shows the status in real time and remains responsive all the time (background upload) should poll all the time.
Preserving the modification date/time of uploaded assets
When uploading assets via the API it may be desirable to preserve the original modification time of the assets being uploaded. To accomplish this, 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:
{ "fields": [... (patch instructions for metadata fields) ...], "attributes": [ {"key": "mt", "value": "2018-01-02T11:22:33Z"} ] }
where
Parameter |
Description |
---|---|
key |
Specifies which attribute to set
|
value |
Specifies the value of the attribute
|
Applying metadata during upload
Metadata can be applied to assets during upload. This ensures strong metadata consistency in FotoWeb archives. If a client uploaded assets 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 after the upload. Applying metadata during upload ensures that all assets in the FotoWeb systems always have fully consistent metadata.
Metadata can be applied to each asset in an upload request. This is done by adding a metadata part to the request body for each asset to which metadata shall be applied:
--WUG7zDvyA3hVq16Q Content-Disposition: name="Metadata" filename="image.jpg.metadata.json" { "fields": [ {"id": 5, "value": "Roadrunner"}, {"id": 80, "value": "Wyle E. Coyote", "action": "add"}, {"id": 25, "action": "erase"}, {"id": 25, "action": "add", "value": ["chicken", "food"]} ] }
where the Content-Disposition
header parameter must be set as follows:
Parameter |
Description |
---|---|
name |
Must always be "Metadata" . |
filename |
Must be |
For example, when applying metadata to an asset where filename
is set to "image.jpg"
in the "FileData"
part, then the "Metadata"
part must begin as follows:
--WUG7zDvyA3hVq16Q Content-Disposition: name="Metadata" filename="image.jpg.metadata.json"
Metadata patch instructions
In the JSON body, fields
is an array of metadata patch instructions where each instruction has the following parameters:
Parameter |
Description |
---|---|
id |
Numeric ID of the metadata field. This parameter is required. |
action |
Metadata action to perform, as described below. This parameter is optional. If omitted, the default action is |
value |
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:
Action |
Description |
---|---|
"add" |
Set the field to the given value or values. If the field already has a value, and the field is a bag field, then the value is added to the existing values of the field. 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 value to the existing value of the 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 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 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 following 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 |
---|---|---|
25 |
|
|
80 |
Roadrunner |
|
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 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 asset from a Fotoware user interface, a zip file will be delivered with the PDF file and the sidecar included. However, when downloading assets 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 reindexing of the asset and embed any sidecar information into the PDF.
Metadata fields that are automatically set
When uploading assets to FotoWeb, FotoWeb sets certain fields automatically. This is described here.
Full example
The following is an example of a complete upload request with metadata:
POST /fotoweb/archives/5000-Wirefeed/Incoming/ Accept: application/vnd.fotoware.upload-response+json Content-Type: multipart/form-data; boundary=WUG7zDvyA3hVq16Q authentication headers --WUG7zDvyA3hVq16Q Content-Disposition: form-data; name="Filedata"; filename="image.jpg" Content-Type: image/jpeg binary image data --WUG7zDvyA3hVq16Q Content-Disposition: name="Metadata" filename="image.jpg.metadata.json" { "fields": [ {"id": 5, "value": "Roadrunner"}, {"id": 80, "value": "Wyle E. Coyote", "action": "add"}, {"id": 25, "action": "erase"}, {"id": 25, "action": "add", "value": ["chicken", "food"]} ] } --WUG7zDvyA3hVq16Q