Lolly
Abrir la app

Off-boarding a DAM

Nota: Lolly Work

This guide covers the optional organisation service. Configuration, console and API paths refer to your Work instance. The packaged console keeps its release-matched copy of this guide.

Leaving a DAM is a provider-level motion, not a per-asset rescue: federate → pin → materialize → cutover. This page is the playbook for every DAM kind this deploy can federate, plus the per-vendor readiness table. Per-platform setup is one guide per kind, in the provider guides; the mechanism is documented in catalog.md.

What the exit is

The exit streams a federated source's bytes into the instance's own BlobStore, checksums every format, sniffs each one for an embedded Content Credential, and mints an instance asset (inst/<id>) stamped with its origin (provider, providerKind, remoteId, filename, sourceUpdatedAt, materializedAt). Afterwards the bytes sit in a store you own and every format carries a checksum and size - the integrity verification and offline-pin parity a federated ext/* entry cannot have while its bytes still live upstream. It runs off the driver interface, so every kind below uses the same two commands.

Bytes land in blobs.driver (pg by default, zero moving parts; s3 for MinIO/Ceph/S3). Set it before the first materialize, not after.

Pin early, cut over when ready

Materialize is the first thing to do once federation works, not the last step of an exit project:

Canto and Image Relay: which driver do you exit through?

Canto acquired Image Relay in September 2024 and is moving that customer base onto its own platform, which splits one set of customers across two APIs. The exit driver follows the tenant, not the logo:

Where the tenant lives todayExit driverNotes
Legacy Image Relay (api.imagerelay.com still serves the tenant)imagerelayExit now, before the vendor's migration adds a second hop
Migrated to Canto (tenant is <name>.canto.com)cantoSame motion, new driver. Folder structure arrives as Canto albums; a custom expiry field carried into a Canto custom field just renames the mapping.availabilityFields entry
Mid-migration (both alive)both, imagerelay firstThe legacy tenant holds the copy your organization curated; use canto for assets born after the migration. The exit is idempotent per (provider, remoteId), so running both is safe
Native Canto customer (never Image Relay)cantoThe ordinary legacy-DAM motion, at contract end

Exposure slices, approval gating and the availability-field mapping are lolly-side, so re-pointing them at a new provider entry is an edit, not a rebuild.

Exit readiness by kind

One row per DAM kind this repo ships. "What imports" is the governance a driver reads off the source (availability window origin per imported availability windows, plus how approval maps); everything downstream - lifecycle fold, holds, exposure, provenance - is lolly-side and identical for every kind.

Every driver here is built from public documentation and fixture-tested with an injected fetch. "Live confirmation open" means the field names have not been checked against a real tenant, which happens on a customer's own tenant under their own contract. Four kinds carry a live-verify runbook for that pass, each with a table saying which constant to edit when a guess turns out wrong: canto, imagerelay, intelligencebank, acquia-dam. The additional passes are Cloudinary and Orange Logic. Run the relevant pass before committing an exit schedule.

KindDriverWhat importsOriginal bytes outExit verdict
brandfolderPublic v4 API, bearer keyNative window (availability_start / availability_end) and a native approved boolean; Brandfolder sections → sectionsYes. The attachment's signed storage URL is re-fetched per request and streamed, host-pinned to brandfolder.com / bfldr.comExit target, one of the two priority off-boarding paths of record. Live confirmation of the availability field names open
imagerelayPublic v2 API, OAuth2 (BYOT)No native window: availability comes from mapping.availabilityFields naming custom-metadata keys. No approval signal at all, so approval stays a lolly-side decision. Folder → section; deletions are reported positively and droppedYes. download_url re-fetched signed per request, host-pinned to imagerelay.com; the driver self-caps at 5 req/sExit target, the urgent one. Canto owns the platform now - check the fork matrix above first, because your tenant may already be on the other side of the migration. Live confirmation of the endpoint paths and field names open
cantoREST v1, OAuth2 (BYOT, App ID + Secret issued by Canto to the tenant owner)Availability from mapping.availabilityFields read off the record's custom-field bag; approval states → approved via options.approvedStates (default ["approved"]); albums and folders → sectionsExpected yes: api_binary streams the original per scheme, host-pinned to the canto family. Whether it returns the true original rather than a rendition is on the driver's LIVE-VERIFY checklistExit target. Nothing in this driver has been confirmed against a Canto tenant yet, so run the runbook before committing a schedule
acquia-damWiden v2 API, bearer tokenNative window (release_date → available from, expiration_date → available until); asset status → approved via options.approvedStatuses (default ["active"]); categories → sectionsYes. The embeds.original URL (or the download link) re-fetched per request, host-pinned to widencollective.com / widencdn.netExit target, and the least mapping work of the set - governance arrives native, so no custom-field wiring. Live confirmation of the field names open
intelligencebankv3 Graph API only. Login handshake, single sealed credentialNative window (publish_date → available from, expiry_date or review_date → available until); workflow state → approved via options.approvedStates (absent leaves approval unfiltered); folder and category → sectionsYes. The resource download_url re-fetched per request, host-pinned to the intelligencebank.com family (a custom-domain tenant needs its host added)Exit target. Governance-rich, so an exit keeps most of what the DAM knew. Live confirmation of the login response and field names open
optimizely-cmpCMP DAM API v3, OAuth2 (BYOT); the only kind with a publish-out arm, opt-in per provider (options.publish)expires_at → available until (CMP models expiry, no release date); is_public and not is_archived → approved; folder and labels → sectionsThe signed download_url streams per request, so the exit path would runNot an exit target. The CMS owns these assets: read-only federate-in plus publish-out of lolly-generated exports. Do not run cutover against it
cloudinarySearch/Admin and signed asset-ID download; sealed API key/secretOptional configured approval tag and metadata dates; dynamic asset folderOriginal download by asset ID, with selected-version preflight and no transformConditional exit target. Prove original checksums and separately export required rights/proof/history data before scheduling migration
orange-logicSearch v4; client credentials or bearerConfigured approval/dates, folders and tags; explicit rendition file rolesUnsupported. Public renditions only; protected original contract unavailableCutover refused. Materialised renditions do not establish an original-preserving exit

The non-DAM kinds (webdav, s3, git, dropbox, gdrive, o365, penpot) are storage and design sources, not brand libraries held under someone else's contract. They have no off-boarding story and are absent from the table deliberately. webdav earns its own note: a Nextcloud or an Apache mod_dav mount is a server the organization runs itself, so there is no vendor to leave, and it is far more often the destination of an exit than the start of one. Materialize works against it like any other source, and its driver is fixture-verified with its own server-day pass in providers/webdav-live-verify.md.

Running an exit

lw providers add acme-ir --kind imagerelay --label "Image Relay" --options '{"folderId":"…"}'
lw providers credential acme-ir    # prompts: an API key, or the sealed OAuth blob for the OAuth kinds
lw providers enable acme-ir        # owner-only
lw providers sync acme-ir          # federate: confirm what arrives before copying anything
lw providers health acme-ir        # and keep watching this one for the rest of the exit

lw providers materialize acme-ir                     # pin everything, early
lw providers materialize acme-ir --section "Logos"   # or one section
lw providers materialize acme-ir --remote-id 12345   # or one asset

lw providers cutover acme-ir       # when the upstream has gone quiet

materialize needs catalog.provider.manage (admin) and reports how many assets were copied, how many the exposure slice skipped, how many carry an embedded credential, and any per-asset failures. A materialized asset keeps its ext/* identity and its federated entry: only the bytes change hands, served from the local copy. The identity moves at cutover.

cutover is owner-gated (catalog.provider.credential). It moves the identity from ext/ to inst/, migrates the lifecycle row (holds included), the credential detection and any asset-specific grants, and writes aliases so every old /catalog/ext/… URL - the ones baked into already-rendered SVGs, signed links and live sessions - keeps resolving. It then disables a db-managed provider; a config-managed one is turned off by removing it from instance.json. Deleting the provider afterwards deletes nothing, because the copies are instance-owned now.