Scope: the APIs of Portfolio Server 4.x — the REST API (primary), the SOAP API (deprecated) and server-side JavaScript scripting. Portfolio is self-hosted: every API call goes to the customer's own server, there is no vendor cloud endpoint. The documentation still lives on Extensis' doc host (doc.extensis.com) after the 2026 transfer of the product to Portfolio DAM, LLC (Axle AI) S10 S11.
At a glance
/v1/auth/login with user name + password RSA-encrypted with the server's public key (GET/v1/auth/public-key) → session ID S1; (2) API Token — a pseudo-user created in Portfolio Administration whose token is used in place of a session ID S12. Both are passed as the session query parameter S1. No OAuthstartingIndex + pageSize in the asset query body; default page size 1,000 when omitted; response gives totalNumberOfAssets S1Authentication
- GET
/v1/auth/public-key→{modulusBase16, exponent}(RSA public key). - Encrypt the password with that key, Base64-encode it.
- POST
/v1/auth/loginwith JSON{"userName": "…", "encryptedPassword": "…"}→{"session": "<id>"}. Errors:401 InvalidCredentials;500 LicenseCountExceeded | UnexpectedError | OperationTimedOut. - Pass
?session=<id>on every call (66 of 73 operations declare it; the unauthenticated ones are/_api_version,/v1/auth/login,/v1/auth/public-key,/v1/server/status,/v1/videoExtensions,/v1/audioExtensions,/v1/branding-settings). - POST
/v1/auth/logoutends the session.
The SOAP API uses the same pattern (getRSAPublicEncryptionKey → login → session ID) S4 S5.
API Token S12
- Created in Portfolio Administration: Users → Add → Add New API Token, choosing a role to apply in all catalogs; per-catalog roles can be changed afterwards.
- The token value is used instead of a session ID, so no login call is needed and the connection never times out.
- Directory-service (AD) accounts cannot log in to Portfolio Administration; administration needs native accounts S13.
Transport: HTTPS on 9443 requires a custom (non-self-signed) certificate configured by the administrator S13.
Core resources
Paths are relative to {server}:8090/api. Inventory parsed from the spec S1.
| Resource | Endpoints | Notes |
|---|---|---|
| Auth & server | GET/_api_version; GET/v1/auth/public-key; POST/v1/auth/login; POST/v1/auth/logout; GET/v1/server/status; GET/v1/branding-settings; GET/v1/videoExtensions, GET/v1/audioExtensions | Server status lists server errors |
| Catalogs | GET/v1/catalog; GET/v1/catalog/{catalog_id}/properties/{property_list}; GET/v1/catalog/{catalog_id}/taskPreset | Properties: Smart Keywords settings, exclusions, cataloging options, ingest config |
| Metadata schema | GET/v1/catalog/{catalog_id}/field-definition; POST/v1/catalog/{catalogId}/field-definition/{fieldDefinitionId}/predefined-value; POST/DELETE…/nested-value | Read field definitions; edit predefined and nested (hierarchical) values. No field creation endpoint |
| Asset search | POST/v1/catalog/{catalog_id}/asset (also GET with a JSON body) | assetQuery with term, fields, sortOptions, paging; see Search |
| Asset metadata | POST/v1/catalog/{catalog_id}/asset/updateFieldValues | Add/remove/append/replace by query; optional embed into files |
| Asset deletion | DELETE/v1/catalog/{catalog_id}/asset (JSON body with a query) | Removes catalog entries |
| Binaries & renditions | GET…/asset/{asset_id}/thumbnail (version), …/preview, …/_content, …/_video (range); GET…/asset/{asset_ids}/_original, …/asset/_original?query=; GET…/asset/{asset_ids}/_derivative, …/asset/_derivative?query=&imageOptions= | Multiple assets → ZIP; async=true → job ID |
| Uploads | POST/v1/catalog/{catalog_id}/upload; POST…/watchfolder/{watchfolder_id}/upload; POST/v1/catalog/{catalog_id}/importData | Multipart; ingestFields for metadata at upload; importData loads a metadata file as a job |
| Galleries | GET/POST/v1/catalog/{catalog_id}/galleries; POST/PUT/v1/catalog/{catalog_id}/gallery; DELETE…/gallery/{gallery_id}; POST/DELETE…/gallery/{gallery_id}/_assets | Types saved / smart; access publicAccess / privateAccess / lockedAccess |
| Watch folders | GET/POST/v1/catalog/{catalog_id}/watchfolder; GET/PUT/DELETE…/watchfolder/{watchfolder_id}; GET…/_details; POST…/_sync; POST…/subfolder; PUT/DELETE…/subfolder/_delete; POST…/watchfolder/subfolder/_move | Sync modes manual, whenChangesAreFound, onSchedule, atSpecificTime |
| Versioning & locking | POST/v1/catalog/{catalog_id}/checkout, …/checkin (comment), …/revert; GET…/asset/{asset_id}/checkout; GET…/asset/{asset_id}/versions; GET…/asset/{asset_id}/diff/left/{versionId_left}/right/{versionId_right}; POST…/lock, …/unlock | Vault catalogs only; lock failures reported per ID (ASSET_CHECKED_OUT, ASSET_LOCKED, NOT_FOUND) |
| Jobs (conversion / batch) | POST/v1/job/run; GET/v1/job; GET/v1/job/_status, GET/v1/job/{job_ids}/_status; GET/v1/job/{job_id}/_errorDetails; PUT/v1/job/{job_ids}/_cancel | Job = asset query + tasks (e.g. convert with output format, colour mode, quality) |
| Reports | GET/v1/report/asset-uploads, asset-downloads, asset-deletions, asset-most-downloaded, asset-most-previewed, file-types, search-terms, keyword-statistics, netpublish-usage, netpublish-download-frequency | Params catalog_id, startingDate, endingDate, locale, includeHeader, format |
| Audit | GET/v1/audit/asset/assetDeletions?catalogId=&sinceDate= | sinceDate in Unix seconds |
Not exposed in the documented REST API S1: user/role/group administration, catalog creation, field creation, directory-service and licence settings, NetPublish site management, Smart Keyword execution. The SOAP WSDL additionally covers presets, view settings, user preferences, ingest config, server features and addAssetsByPath S4.
Data model
S1 unless noted.
- Catalog —
catalogId(GUID-style string),name,details[]. Access and roles are granted per catalog S15. - Asset —
assetId(string; numeric IDs appear in examples),locked, andattributes[]: a list of{name, values[]}pairs keyed by field name (e.g.Filename,File Size,Keywords), i.e. every field is multi-valued in transport. The fields returned are those listed in the query'sfieldsarray, otherwise a default set. The internal asset ID is also exported asRIDS45. - Field definition —
id,systemName,internalName,localizedNames[], numerictype,multivalued,hierarchical,hasPredefinedValues,predefinedValues[],nestedPredefinedValues[],usePredefinedOnly,maxLength,precision,defaultValues[],searchable,sortable,embeddable,extractable,systemField. - Gallery —
galleryId,name,owner,type(saved/smart),accessType, andsavedFind(anAssetQueryTerm, so smart galleries are stored queries). - Watch folder —
watchFolderId,path, sync settings; folder paths use a Portfolio path syntax (::FileServer:Volume:folder) in SOAP examples S7. - Job —
jobId,catalogId,description,status∈retrieving,queued,inProgress,complete,canceled, timestamps, details. - Errors —
{faultCode, message}; fault codes includeInvalidCredentials,InvalidParameters,ItemNotFound,LicenseCountExceeded,UnexpectedError,OperationTimedOut.
Search & querying
| Aspect | Behaviour |
|---|---|
| Query shape | JSON assetQuery: optional galleryId (search inside a gallery), term (tree of AssetQueryTerm with operator, fieldName, values[], subqueries[]), fields[], sortOptions {field, order}, startingIndex, pageSize S1 |
| Operators | Boolean operatorAnd / operatorOr; assetsById / assetsNotHavingId; equalValue, notEqualValue, containsValue, startsWithValue, endsWithValue and their negations; lessThan…, greaterThan… (incl. or-equal); nullValue / notNullValue; dates inPast, inFuture, inNextDays, inPreviousDays, today, thisMonth, thisYear; valueCountEquals/GreaterThan/LessThan; matchUnparsedQueryString; intersectsGeometry S1 |
| Sorting | One field, asc / desc S1 |
| Clustering | Results may include clusters[] and totalNumberOfClusteredAssets (map-view clustering) S1 S35 |
| Facets | Not available in the REST API (Unknown in UI) |
| Page size | Default 1,000; maximum and deep-paging limits Not public S1 |
| Date semantics | Searches on date fields always include the time component, even if hidden in the UI S32 |
Uploads & binaries
- Upload:
multipart/form-dataPOST to/v1/catalog/{catalog_id}/upload(fieldspath,file,ingestFields) or to a specific watch folder (…/watchfolder/{id}/upload, plusfilename) S1. Single request per file; chunked/resumable upload is not documented. The SOAP-era servlet POST/FileTransfer/upload(fieldssessionId,catalogId,destinationFolder,filename,action=upload|customPreview,Filedata) returns the new asset ID and also sets custom previews S7. - Download: original (
_content,_original), preview, thumbnail (optionally a givenversion), video stream with HTTPRange(source=preview|original) S1. - On-the-fly conversion:
_derivativewithimageOptionsJSON (outputFileFormat,colorModeRGB/CMYK/Grayscale,resizeWidth/resizeHeight,compressionQuality1–100,sourcepreview/original,videoFrameTime,resolution,compressionType,endian); single asset → streamed file, several → ZIP,async=true→ job ID S1. Extra output formats need a Media Engine licence S25. - Batch jobs: POST
/v1/job/runwith an asset query and task list (e.g.convertwith settings), then poll/_statusand fetch results; client-side jobs are retained 24 h S39 S1. - No signed/expiring public URLs or CDN; all binary access goes through the authenticated server S1.
Events & webhooks
- No webhook subscription API and no outbound HTTP events in the REST or SOAP specs S1 S4.
- Server-side scripting (alternative): JavaScript files in
Portfolio Server/data/custom-scriptsdeclaretriggerPoints(onCataloged,onMetadataUpdated,onGalleryChanged,onJobInitialize,onJobEachAsset,onJobFinalize), an optional cronscheduleInfo, required permissions and a client menu label; a globalportfolioobject exposes server functions S8. Since 4.0.1, Java reflection from scripts is blocked (CVE-2022-24252 fix) and can only be re-enabled by disabling that protection S41. The new owner lists scripting as "no longer actively supported" S10. - Built-in non-API notifications: daily "query to email" digest, email on gallery changes/backups S10 S40.
Limits & quirks
- Self-hosted endpoints: base URL, ports and TLS depend on each installation; default ports 8090 (HTTP) / 9443 (HTTPS) are shared by Portfolio Web and the API S14. Exposing the server to the Internet is possible but "not necessarily recommended"; VPN is the advised pattern S37 S80.
- Session in the query string:
session=(or the API Token) travels as a URL parameter on every call, so it can end up in proxy/server logs S1 Unverified (observation from the spec, not a vendor statement). - GET with a JSON body: GET
/v1/catalog/{id}/assetand DELETE/v1/catalog/{id}/assettake their query in the request body; prefer POST…/assetfor search S1. - Spec inconsistency: prose examples write the query term key as
field, while the schema names itfieldNameS1 Unverified (test against your server). - Stale references: several operations link to the SOAP docs for details (
externalDocs→soap_assets_AssetSEIService.html), and many responses are typed only asJSONObject/default responseS1. - Licensed connections: logins count against licensed user connections (
LicenseCountExceeded); API Tokens don't S12 S1. - Versioning/locking endpoints only work on Vault catalogs S1 S26.
- Rate limits / payload limits: Not public.
- Doc ownership: the API reference is still hosted by Extensis (
doc.extensis.com) and API support links still point to an Extensis form after the sale S11 Unverified (possible future move).
SDKs, tools & code samples
| Library | Language | Maintainer | Status |
|---|---|---|---|
dam-client.jar, assetSEIService-cs, assetSEIService-php, assetSEIService-vb (SOAP) | Java, C#, PHP, VB | Extensis (sample libraries) | Dated 2013–2015; SOAP deprecated S6 |
| Server-side scripting examples | JavaScript | Extensis docs | Example scripts page S8 |
jelmore49/extensis-portfolio-api-samples | PHP (SOAP + REST samples) | Community | Last push 2022 S74 |
jamesgrace/* (REST reports-to-email, Uppy uploader, scripting and backup samples) | Python, HTML/JS, Bash, Windows batch | Community | 2021 S74 |
tomasc/extensis_portfolio, wags/PortfolioRestToken | Ruby, C# | Community | 2016–2017, inactive S74 |
No npm/PyPI/NuGet/Maven package from the vendor was found; the Extensis GitHub organisation holds no Portfolio SDK S75.
Changelog of the API
| Date | Change | Source |
|---|---|---|
| 2026-03 | Portfolio 4.1.1 beta (Tomcat 9.0.115, Azul JRE 1.8.0_481); no API changes announced | S52 |
| 2022 | 4.0.1: scripting engine loses Java reflection access (CVE-2022-24252); file-upload RCE flaws fixed | S65 S41 |
| 2018-11-21 | Current public REST OpenAPI document (file date) | S1 |
| 2014-09-19 | SOAP WSDL file date; SOAP later marked deprecated in favour of REST | S4 S11 |