Skip to content

tRPC API

This page lists the whole server API and the conventions a new procedure must follow. The request path through the process is in Architecture; the access rules as an administrator sees them are in Roles and access.

A procedure is a zod-validated query or mutation behind predicates

Section titled “A procedure is a zod-validated query or mutation behind predicates”

server/src/trpc/index.ts exports router, publicProcedure, middleware and authMiddleware. A procedure in server/src/trpc/router/<domain>.ts follows this skeleton:

findById: publicProcedure
.use(authMiddleware(userApproved))
.input(z.string().uuid())
.query(async ({ input, ctx }) => {
const collection = await userCollectionsQuery(ctx.user)
.andWhere('collection.id = :id', { id: input })
.getOne()
if (!collection) {
throw new TRPCError({ code: 'NOT_FOUND', message: 'Collection not found.' })
}
return formatCollection(collection)
}),

ctx is { req, res, user }; user is the User entity loaded from the JWT or null. Each router file is export default router({ ... }), mounted under its key in router/index.ts, which also declares the only top-level procedure, env. Routers return plain objects built by local format* helpers, never raw entities. Renaming a procedure is a client change too, since the client is compiled against AppRouter.

authMiddleware(...predicates) throws UNAUTHORIZED when ctx.user is null or any predicate returns false. authMiddleware() with no predicate only requires a logged-in user.

PredicatePasses when
userApproveduser.approved && user.emailVerified
userMemberrole is admin, manager or member (not guest)
userManagerOrAdminrole is admin or manager
userAdminrole is admin

Per-object rules (own region for managers, collection.canEdit(user), visibility through userCollectionsQuery) are enforced inside the procedure body, not by the middleware.

Errors carry a code, and validation errors carry field errors

Section titled “Errors carry a code, and validation errors carry field errors”

Procedures throw TRPCError with one of four codes: UNAUTHORIZED (only from authMiddleware), NOT_FOUND, BAD_REQUEST and FORBIDDEN. When zod rejects the input, tRPC raises BAD_REQUEST with a ZodError cause, and the errorFormatter in trpc/index.ts rewrites it to message: 'Invalid request.' and adds data.fieldErrors (the result of error.cause.flatten().fieldErrors, an object of field name to array of messages).

On the client, extractErrors(error) in client/src/services/server.ts returns { message, fieldErrors } with the first message of each field, ready for a form.

  • Base URL: VITE_API_ENDPOINT, default http://localhost:3000/trpc.
  • A query is GET /trpc/<router>.<procedure>?input=<JSON>; a mutation is POST /trpc/<router>.<procedure> with the JSON input as body. Nested routers use dots: collection.invitation.create. The httpLink sends one request per call, no batching.
  • The authorization header carries the raw JWT, with no Bearer prefix. The token is what user.login returns (or what a ?dam_token= link sets), signed with APP_SECRET, valid 180 days.
  • There is no OpenAPI document. Types flow from AppRouter (export type AppRouter = typeof appRouter in router/index.ts) to the client through RouterInput and RouterOutput, inferRouterInputs<AppRouter> and inferRouterOutputs<AppRouter> in client/src/services/server.ts.

Auth is the predicate list passed to authMiddleware; public means no middleware, login means authMiddleware() with no predicate.

ProcedureKindAuthPurpose
envquerypublicpasswordLessAuthentication, appName, regions list, for the login screen
ProcedureKindAuthPurpose
createmutationpublicSign-up
loginmutationpublicPassword login returning the JWT, or pushes mailer/log-in when passwordless or magicLink
sendResetPasswordEmailmutationpublicPushes mailer/password-reset
resetPasswordmutationpublicSets a new password given the email and the reset token
resendVerificationEmailmutationpublicPushes mailer/email-verification for a user id
mequeryloginCurrent user
updateProfilemutationloginOwn name, company, email
verifyEmailmutationloginConsumes ?verificationCode=
removeAccountmutationloginDeletes own account (FORBIDDEN for any other id)
findByIdqueryuserManagerOrAdminOne user (managers: own region)
updatemutationuserManagerOrAdminName, company, email, region, role, groups of a user
listqueryuserManagerOrAdminUsers (managers: own region)
approvemutationuserManagerOrAdminApproves and pushes email/user-approved
removemutationuserManagerOrAdminDeletes a user; managers cannot delete admins
ProcedureKindAuthPurpose
group.listqueryuserManagerOrAdminAll groups
group.create, group.update, group.removemutationuserAdminCRUD
group.setDefaultmutationuserAdminFlags the default group
group.moveUsersAndRegionsmutationuserAdminRe-points user_groups and regions.default_group_id from one group to another
region.listqueryuserAdminRegions
region.create, region.update, region.removemutationuserAdminCRUD
region.moveUsersmutationuserAdminMoves every user of one region to another
authorizedDomain.listqueryuserAdminDomains allowed to sign up
authorizedDomain.create, authorizedDomain.removemutationuserAdminCRUD
ProcedureKindAuthPurpose
treequeryuserApprovedCollections visible to the user, as a tree
treeAdminqueryuserAdminFull tree for the admin screen
searchqueryuserApprovedFiles matching text, asset types, product facets and scope
searchNotFoundqueryuserApprovedReturns the search terms that matched no file name in the same scope
findByIdqueryuserApprovedOne collection with files, children, invitations
lastAddedFilesqueryuserApproved10 most recent collection files, optionally under one collection
createmutationuserApprovedNew collection; public is forced to false for non-admins, so only admins create public ones
createFromAssetmutationuserAdminSynchronised collection from an asset folder; pushes collection/synchronization
createUserCollectionmutationuserApprovedPrivate collection owned by the caller
ListPrivateCollectionsqueryuserApprovedThe caller’s private collections
addItemsmutationuserApprovedDuplicates selected files or whole collections into a collection the caller can edit (duplicateCollection, duplicateFiles)
updatemutationuserApprovedName, description, public, draft, hasThumbnail, limitedToGroupIds
presignedThumbnailUploadUrlqueryuserApprovedPresigned PUT for collections/{id}-thumbnail
removeFilesmutationuserApprovedRemoves collection files
removemutationuserApprovedDeletes a collection the caller can edit
getFilesmutationuserApprovedResolves a selection (files and collections) into files, licenses and allowDirectDownload (total under 2 GB)
invitation.createmutationuserApprovedInvites an email to a collection, creating a guest user when unknown; pushes mailer/invitation
invitation.removemutationuserApprovedRevokes an invitation
invitation.getUserInvitationsqueryloginInvitations on collections the caller owns (admins: also public ones)

asset, assetType, license, favorite, download

Section titled “asset, assetType, license, favorite, download”
ProcedureKindAuthPurpose
asset.treequeryuserAdminAsset folder tree
asset.findByIdqueryuserAdminOne folder with children, files and ancestors
asset.updatemutationuserAdminSets assetTypeId and licenseId on a folder, its descendants and their files
asset.listProductViewsqueryuserApprovedDistinct productView values
assetType.listqueryuserApprovedAsset types
assetType.create, update, removemutationuserAdminCRUD
license.listqueryuserAdminLicenses
license.create, update, removemutationuserAdminCRUD
favorite.listqueryuserApproved, userMemberThe caller’s favourite collection files
favorite.add, favorite.removemutationuserApproved, userMemberToggle a favourite
download.listqueryuserApprovedThe caller’s ready and preparing downloads, plus those expired in the last month
download.createmutationuserApprovedCreates a download (FORBIDDEN above 10 GB); email type pushes download/create-archive
ProcedureKindAuthPurpose
pim.listProductsqueryuserAdminProducts by page and size, optional columnFilter
pim.compareCsvmutationuserAdminDiff of a parsed CSV against existing products
pim.importCsvmutationuserAdminUpserts products from a parsed CSV
pim.removeAllProductsmutationuserAdminDeletes every product
pim.updateProductmutationpublicReplaces one product’s metaData
productAttribute.listAvailablequeryuserAdminDistinct hstore keys found in products
productAttribute.listqueryuserAdminDeclared attributes
productAttribute.listFacetsqueryloginFacetable attributes with their distinct values
productAttribute.create, update, removemutationuserAdminCRUD
ProcedureKindAuthPurpose
menuItem.listqueryuserApprovedMenu tree, built against the collection ids the user can see
menuItem.create, update, removemutationuserAdminCRUD
menuItem.setHomemutationuserAdminFlags the home item
menuItem.updatePositionsmutationuserAdminReorders and reparents
page.listqueryuserAdminPages
page.findByIdqueryuserApprovedOne page with blocks
page.createmutationuserAdminStandalone page
page.createForCollectionmutationuserApprovedPage bound to a collection the caller can edit
page.updatemutationuserAdminRenames a page
page.removemutationuserApprovedDeletes a page the caller can edit
page.addBlock, removeBlock, updateLayout, updateBlockDatamutationuserApprovedBlock editing, checked with page.canEdit(user)
page.presignedUploadUrlqueryuserApprovedPresigned PUT (24 h) for a block’s data.s3key in the main bucket
settings.getAuthBackgroundImagequerypublicPresigned URL of settings/auth-background.webp, if present
settings.getAuthBackgroundUploadUrlqueryuserAdminPresigned PUT for the temporary background
settings.processAuthBackgroundImagemutationuserAdminConverts the temporary upload to WebP (quality 80) at settings/auth-background.webp
settings.removeAuthBackgroundImagemutationuserAdminDeletes the background