The Resume API provides endpoints for creating, reading, updating, and deleting resumes. All endpoints use the ORPC protocol and return typed responses.
Authentication
Most endpoints require authentication using the protectedProcedure middleware. Public endpoints are explicitly marked below.
Error Handling
All endpoints may throw ORPCError with the following codes:
NOT_FOUND - Resource not found
FORBIDDEN - Access denied
INTERNAL_SERVER_ERROR - Server error
healthCheck
Simple health check endpoint to verify the API is running.
Authentication: Not required (public)
Response
Returns "OK" if the service is healthy
Example
const status = await client . healthCheck ()
// Returns: "OK"
packages/api/src/routers/index.ts
healthCheck : publicProcedure . handler (() => {
return 'OK'
})
helloWorld
Simple test endpoint that returns a greeting message.
Authentication: Not required (public)
Response
Example
const message = await client . helloWorld ()
// Returns: "Hello World"
packages/api/src/routers/index.ts
helloWorld : publicProcedure . handler (() => {
return 'Hello World'
})
listResumes
Retrieves all resumes belonging to the authenticated user, ordered by most recently updated.
Authentication: Required (protected)
Response
Array of resume objects Unique resume identifier (UUIDv7)
Resume data conforming to ResumeSchema. See Types for full schema. Whether the resume is publicly accessible
URL to resume thumbnail image
Number of times the resume has been viewed (public only)
Number of times the resume has been downloaded (public only)
Example
const resumes = await client . listResumes ()
// Returns array of resumes ordered by updatedAt (descending)
Source
packages/api/src/routers/index.ts
listResumes : protectedProcedure . handler ( async ({ context }) => {
const currentUser = context . session . user
const resumes = await db . query . resume . findMany ({
where : ({ userEmail }, { eq }) => eq ( userEmail , currentUser . email ),
orderBy : ({ updatedAt }, { desc }) => desc ( updatedAt ),
})
return resumes . map (( resume ) => ({
... resume ,
data: resume . data ? ResumeSchema . parse ( resume . data ) : null ,
}))
})
getResumeById
Retrieves a specific resume by ID. Only returns resumes owned by the authenticated user.
Authentication: Required (protected)
The resume ID to retrieve
Response
Resume object with all fields (see listResumes for structure)
Errors
NOT_FOUND - Resume does not exist or does not belong to user
Example
const resume = await client . getResumeById ({
id: '01H2XMPL3K7N8Q9R'
})
Source
packages/api/src/routers/index.ts
getResumeById : protectedProcedure
. input ( z . object ({ id: z . string () }))
. handler ( async ({ context , input }) => {
const { id : resumeId } = input
const currentUser = context . session . user
const resume = await db . query . resume . findFirst ({
where : ({ id , userEmail }, { eq , and }) =>
and ( eq ( id , resumeId ), eq ( userEmail , currentUser . email )),
})
if ( ! resume ) {
throw new ORPCError ( 'NOT_FOUND' )
}
return {
... resume ,
data: resume . data ? ResumeSchema . parse ( resume . data ) : null ,
}
})
getResumeBySlug
Retrieves a resume by its unique slug. This endpoint is public but enforces privacy rules.
Authentication: Optional (public)
The resume slug to retrieve
Response
Resume object with all fields. The views counter is automatically incremented for public resumes.
Behavior
Public resumes: Accessible to anyone, view count incremented
Private resumes: Only accessible to the owner
Throws FORBIDDEN if trying to access someone else’s private resume
Errors
NOT_FOUND - Resume with slug does not exist
FORBIDDEN - Resume is private and user is not the owner
Example
const resume = await client . getResumeBySlug ({
slug: 'john-doe-software-engineer'
})
Source
packages/api/src/routers/index.ts
getResumeBySlug : publicProcedure
. input ( z . object ({ slug: z . string () }))
. handler ( async ({ context , input }) => {
const { slug : resumeSlug } = input
const queriedResume = await db . query . resume . findFirst ({
where : ({ slug }, { eq }) => eq ( slug , resumeSlug ),
})
if ( ! queriedResume ) {
throw new ORPCError ( 'NOT_FOUND' )
}
if (
! queriedResume . isPublic &&
queriedResume . userEmail !== context . session ?. user . email
) {
throw new ORPCError ( 'FORBIDDEN' )
}
// Only increment views for public resumes
if ( queriedResume . isPublic ) {
await db
. update ( resume )
. set ({ views: queriedResume . views + 1 })
. where ( eq ( resume . id , queriedResume . id ))
}
return {
... queriedResume ,
data: queriedResume . data
? ResumeSchema . parse ( queriedResume . data )
: null ,
}
})
createResume
Creates a new resume with example data in the specified language and template.
Authentication: Required (protected)
Language for example data. Available languages:
en - English
es - Spanish
fr - French
de - German
ja - Japanese
pt - Portuguese
zh - Chinese
Resume template. Defaults to awesome.
awesome
modern
professional
bold
Response
The created resume object with generated ID and slug
Behavior
Generates a UUIDv7 for the resume ID
Creates a unique slug based on user email and resume name
Initializes resume with example data for the selected language
Sets the specified template in the config
Errors
INTERNAL_SERVER_ERROR - Failed to create resume
Example
const newResume = await client . createResume ({
name: 'Software Engineer Resume' ,
language: 'en' ,
template: 'modern'
})
Source
packages/api/src/routers/index.ts
createResume : protectedProcedure
. input (
z . object ({
name: z . string (),
language: z . enum ( Object . keys ( exampleResumes ) as [ string , ... string []]),
template: TemplateSchema . optional (),
}),
)
. handler ( async ({ context , input }) => {
const { name , language , template = 'awesome' } = input
const exampleResume =
exampleResumes [ language as keyof typeof exampleResumes ]
const initialData = {
... exampleResume ,
config: {
... exampleResume . config ,
template ,
},
}
const currentUser = context . session . user
const [ createdResume ] = await db
. insert ( resume )
. values ({
id: uuidv7 (),
name ,
userEmail: currentUser . email ,
data: initialData ,
slug: uniqueSlug ( currentUser . email , name ),
})
. returning ()
if ( ! createdResume ) {
throw new ORPCError ( 'INTERNAL_SERVER_ERROR' )
}
return createdResume
})
updateResume
Updates the complete resume data.
Authentication: Required (protected)
Complete resume data conforming to ResumeValidationSchema. See Types for full schema.
Response
The resume object before update (for reference)
Behavior
Validates input data against ResumeValidationSchema
Updates updatedAt timestamp automatically
Only updates resumes owned by the authenticated user
Errors
NOT_FOUND - Resume does not exist or does not belong to user
Example
await client . updateResume ({
id: '01H2XMPL3K7N8Q9R' ,
data: {
config: { /* ... */ },
personalInfo: { /* ... */ },
sections: [ /* ... */ ]
}
})
Source
packages/api/src/routers/index.ts
updateResume : protectedProcedure
. input ( z . object ({ id: z . string (), data: ResumeValidationSchema }))
. handler ( async ({ context , input }) => {
const { id : resumeId , data } = input
const currentUser = context . session . user
const queriedResume = await db . query . resume . findFirst ({
where : ({ id , userEmail }, { eq , and }) =>
and ( eq ( id , resumeId ), eq ( userEmail , currentUser . email )),
})
if ( ! queriedResume ) {
throw new ORPCError ( 'NOT_FOUND' )
}
await db
. update ( resume )
. set ({ data , updatedAt: new Date () })
. where ( eq ( resume . id , resumeId ))
return queriedResume
})
updateResumeName
Updates only the resume name.
Authentication: Required (protected)
Response
The resume object before update
Behavior
Updates only the name field
Updates updatedAt timestamp automatically
Does not modify the slug
Errors
NOT_FOUND - Resume does not exist or does not belong to user
Example
await client . updateResumeName ({
id: '01H2XMPL3K7N8Q9R' ,
name: 'Updated Resume Name'
})
Source
packages/api/src/routers/index.ts
updateResumeName : protectedProcedure
. input ( z . object ({ id: z . string (), name: z . string () }))
. handler ( async ({ context , input }) => {
const { id : resumeId , name } = input
const currentUser = context . session . user
const queriedResume = await db . query . resume . findFirst ({
where : ({ id , userEmail }, { eq , and }) =>
and ( eq ( id , resumeId ), eq ( userEmail , currentUser . email )),
})
if ( ! queriedResume ) {
throw new ORPCError ( 'NOT_FOUND' )
}
await db
. update ( resume )
. set ({ name , updatedAt: new Date () })
. where ( eq ( resume . id , resumeId ))
return queriedResume
})
updateResumePublicStatus
Toggles whether a resume is publicly accessible.
Authentication: Required (protected)
Whether the resume should be public
Response
The updated resume object with new isPublic status
Behavior
Updates isPublic flag
Updates updatedAt timestamp automatically
When public, resume becomes accessible via getResumeBySlug to anyone
When private, only owner can access
Errors
NOT_FOUND - Resume does not exist or does not belong to user
Example
const updatedResume = await client . updateResumePublicStatus ({
id: '01H2XMPL3K7N8Q9R' ,
isPublic: true
})
Source
packages/api/src/routers/index.ts
updateResumePublicStatus : protectedProcedure
. input ( z . object ({ id: z . string (), isPublic: z . boolean () }))
. handler ( async ({ context , input }) => {
const { id : resumeId , isPublic } = input
const currentUser = context . session . user
const queriedResume = await db . query . resume . findFirst ({
where : ({ id , userEmail }, { eq , and }) =>
and ( eq ( id , resumeId ), eq ( userEmail , currentUser . email )),
})
if ( ! queriedResume ) {
throw new ORPCError ( 'NOT_FOUND' )
}
await db
. update ( resume )
. set ({ isPublic , updatedAt: new Date () })
. where ( eq ( resume . id , resumeId ))
const updatedResume = await db . query . resume . findFirst ({
where : ({ id }, { eq }) => eq ( id , resumeId ),
})
return updatedResume !
})
deleteResume
Permanently deletes a resume.
Authentication: Required (protected)
Response
Always returns true on successful deletion
Errors
NOT_FOUND - Resume does not exist or does not belong to user
Example
await client . deleteResume ({
id: '01H2XMPL3K7N8Q9R'
})
// Returns { success: true }
Source
packages/api/src/routers/index.ts
deleteResume : protectedProcedure
. input ( z . object ({ id: z . string () }))
. handler ( async ({ context , input }) => {
const { id : resumeId } = input
const currentUser = context . session . user
const queriedResume = await db . query . resume . findFirst ({
where : ({ id , userEmail }, { eq , and }) =>
and ( eq ( id , resumeId ), eq ( userEmail , currentUser . email )),
})
if ( ! queriedResume ) {
throw new ORPCError ( 'NOT_FOUND' )
}
await db . delete ( resume ). where ( eq ( resume . id , resumeId ))
return { success: true }
})
duplicateResume
Creates a copy of an existing resume with a new ID and name.
Authentication: Required (protected)
Response
The newly created resume copy with all data
Behavior
Creates new resume with new UUIDv7 ID
Copies all resume data and thumbnail URL
Generates new unique slug
Names the copy as “[Original Name] copy” or “[Original Name] copy N” if duplicates exist
Increments suffix number to avoid naming conflicts
Errors
NOT_FOUND - Original resume does not exist or does not belong to user
INTERNAL_SERVER_ERROR - Failed to create duplicate
Example
const duplicated = await client . duplicateResume ({
id: '01H2XMPL3K7N8Q9R'
})
// Returns new resume with name like "Software Engineer Resume copy"
Source
packages/api/src/routers/index.ts
duplicateResume : protectedProcedure
. input ( z . object ({ id: z . string () }))
. handler ( async ({ context , input }) => {
const currentUser = context . session . user
const originalResume = await db . query . resume . findFirst ({
where : ({ id , userEmail }, { eq , and }) =>
and ( eq ( id , input . id ), eq ( userEmail , currentUser . email )),
})
if ( ! originalResume ) {
throw new ORPCError ( 'NOT_FOUND' )
}
const existingNames = new Set (
(
await db . query . resume . findMany ({
where : ({ userEmail }, { eq }) => eq ( userEmail , currentUser . email ),
columns: { name: true },
})
). map (( entry ) => entry . name ),
)
const baseName = ` ${ originalResume . name } copy`
let candidateName = baseName
let suffix = 2
while ( existingNames . has ( candidateName )) {
candidateName = ` ${ baseName } ${ suffix } `
suffix += 1
}
const [ duplicatedResume ] = await db
. insert ( resume )
. values ({
id: uuidv7 (),
name: candidateName ,
userEmail: currentUser . email ,
data: originalResume . data ,
slug: uniqueSlug ( currentUser . email , candidateName ),
thumbnailUrl: originalResume . thumbnailUrl ,
})
. returning ()
if ( ! duplicatedResume ) {
throw new ORPCError ( 'INTERNAL_SERVER_ERROR' )
}
return {
... duplicatedResume ,
data: duplicatedResume . data
? ResumeSchema . parse ( duplicatedResume . data )
: null ,
}
})
uploadThumbnail
Uploads a thumbnail image for a resume.
Authentication: Required (protected)
Resume ID to upload thumbnail for
Base64 encoded image data (including data URI prefix like data:image/png;base64,...)
Response
URL to the uploaded thumbnail in MinIO/S3 storage
Behavior
Strips data URI prefix from base64 string
Converts base64 to buffer
Uploads to MinIO/S3 storage
Updates resume with thumbnail URL
Updates updatedAt timestamp
Errors
NOT_FOUND - Resume does not exist or does not belong to user
Example
const result = await client . uploadThumbnail ({
resumeId: '01H2XMPL3K7N8Q9R' ,
thumbnail: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUg...'
})
// Returns { thumbnailUrl: 'https://s3.example.com/...' }
Source
packages/api/src/routers/index.ts
uploadThumbnail : protectedProcedure
. input (
z . object ({
resumeId: z . string (),
thumbnail: z . string (), // base64 encoded image data
}),
)
. handler ( async ({ context , input }) => {
const { resumeId , thumbnail } = input
const currentUser = context . session . user
// Verify resume belongs to user
const queriedResume = await db . query . resume . findFirst ({
where : ({ id , userEmail }, { eq , and }) =>
and ( eq ( id , resumeId ), eq ( userEmail , currentUser . email )),
})
if ( ! queriedResume ) {
throw new ORPCError ( 'NOT_FOUND' )
}
// Convert base64 to buffer
const base64Data = thumbnail . replace ( / ^ data:image \/ \w + ;base64,/ , '' )
const buffer = Buffer . from ( base64Data , 'base64' )
// Upload thumbnail to MinIO
const thumbnailUrl = await uploadThumbnailToS3 ( resumeId , buffer )
// Update resume with thumbnail URL
await db
. update ( resume )
. set ({ thumbnailUrl , updatedAt: new Date () })
. where ( eq ( resume . id , resumeId ))
return { thumbnailUrl }
})
setDownloadCount
Increments the download counter for a public resume.
Authentication: Optional (public)
Resume ID to increment download count for
Response
The resume object (before increment)
Behavior
Public resumes: Download count is incremented
Private resumes: Only accessible to owner, count not incremented
Throws FORBIDDEN if trying to access someone else’s private resume
Errors
NOT_FOUND - Resume does not exist
FORBIDDEN - Resume is private and user is not the owner
Example
await client . setDownloadCount ({
id: '01H2XMPL3K7N8Q9R'
})
Source
packages/api/src/routers/index.ts
setDownloadCount : publicProcedure
. input ( z . object ({ id: z . string () }))
. handler ( async ({ context , input }) => {
const { id : resumeId } = input
const queriedResume = await db . query . resume . findFirst ({
where : ({ id }, { eq }) => eq ( id , resumeId ),
})
if ( ! queriedResume ) {
throw new ORPCError ( 'NOT_FOUND' )
}
if (
! queriedResume . isPublic &&
queriedResume . userEmail !== context . session ?. user . email
) {
throw new ORPCError ( 'FORBIDDEN' )
}
// Only increment downloads for public resumes
if ( queriedResume . isPublic ) {
await db
. update ( resume )
. set ({ downloads: queriedResume . downloads + 1 })
. where ( eq ( resume . id , resumeId ))
}
return queriedResume
})