Skip to content

Career portfolios

A Career Portfolio is a person’s résumé on United Pet Club: a headline and About, skills from the UPC skill catalogue with a comfort level, the breeds they work with, roles, education, awards and work samples. It exists for person profiles only. Its owner decides, section by section, who can see it, so the same request returns more or less depending on who asks.

All reads on this page are anonymous and return the platform envelope { data, resultCode } with camelCase fields. The {locale} segment is a BCP-47 code such as en-US (see Localized names); names are localized with an en-US fallback. Ids are UUIDs.

Base path through the gateway: /api/profile/v1/{locale}/Profile.

MethodPathReturns
GET/profiles/{profileId}/career-portfolioThe portfolio as the caller may see it. 404 when the profile is not an active person profile.
GET/profiles/{profileId}/portfolio-itemsThe work samples. On a person profile this is the portfolio’s Work section: a caller who may not see it gets an empty list.
GET/skills/catalogThe skill catalogue: active categories with their skills.
GET/career-portfolio/lookupsNames for the codes used in the portfolio: employment types, degree types, award kinds, work settings and job titles.
GET/profiles/{profileId}/awardsA profile’s awards on their own, for a person, business or place profile. On a person profile it follows the Awards section’s audience. Only public statuses for anyone but the profile itself.

Find a person’s profileId from their handle with GET …/Profile/profiles/by-handle/{handle}.

A call without a member’s session receives the public view: the sections the person shared with everyone. Treat that as the most you can show; never try to infer what is hidden.

  • viewer is PUBLIC for such a call.
  • A section the caller may not see comes back as an empty list plus an entry in lockedSections ({ code, audience }, audience being MEMBERS or NETWORK). Show a “visible to members” note there, or nothing. A section the person keeps to themselves is simply absent, with no entry.
  • When the person made the whole portfolio private, the response is { profileId, isPublic: false, isHidden: true } with every list empty and no headline. Show nothing but a “this portfolio is private” note.
  • isOpenToWork is always present, so you can show an “Open to work” badge even when the hiring details are not shared (hiring is then null).
  • workVisible says whether the Work section (the portfolio-items read) is visible to the caller.

Section codes: ABOUT, SKILLS, BREEDS, EXPERIENCE, EDUCATION, AWARDS, WORK, HIRING.

{
"data": {
"profileId": "0199…",
"isPublic": true,
"isHidden": false,
"viewer": "PUBLIC",
"isOpenToWork": true,
"headline": "Groomer for doodles and anxious dogs",
"about": "Calm, unhurried grooms. I specialize in nervous dogs and doodle trims.",
"sectionAudiences": null,
"lockedSections": [{ "code": "EXPERIENCE", "audience": "MEMBERS" }],
"skills": [
{
"id": "0199…", "skillCode": "GRM_SCISSORING", "name": "Hand scissoring",
"categoryCode": "GROOMING_TECHNIQUE", "categoryName": "Grooming techniques",
"categoryIconSvg": "<svg …>", "skillIconSvg": "<svg …>",
"experienceLevel": 5, "sinceYear": 2016, "lastUsedYear": null, "years": 10,
"isHighlighted": true, "description": null, "displayOrder": 1
}
],
"breeds": [
{ "id": "0199…", "speciesCode": "CAT", "speciesName": "Cat", "breedCode": null, "breedName": null,
"experienceLevel": 3, "sinceYear": null, "note": null, "isHighlighted": false, "displayOrder": 2 }
],
"experience": [],
"education": [
{
"id": "0199…", "qualification": "Grooming diploma", "degreeTypeCode": "CERTIFICATE",
"institution": { "profileId": null, "handle": null, "name": "Lakeshore Grooming Academy",
"isLinked": false, "isVerified": false },
"startDate": "2014-01-01", "endDate": "2014-08-01", "isCompleted": true, "description": null
}
],
"awards": [],
"workVisible": true,
"hiring": null
},
"resultCode": "…"
}

The example is a public view of a test portfolio with ids and icons shortened. Codes such as CERTIFICATE are named by the lookups read.

FieldMeaning
experienceLevelComfort level 1–5: 1 Learning, 2 With guidance, 3 Works independently, 4 Experienced, 5 Can teach it. The same scale for skills and breeds.
sinceYear, lastUsedYear, yearsYears the person gave; years = (lastUsedYear or the current year) − sinceYear, never negative, null without sinceYear.
isHighlightedShown at the top: up to 6 skills, up to 8 breeds (as tiles).
breedCode: nullThe row covers every breed of the species (“All breeds”).
speciesCodeThe species code used across the platform (DOG, CAT, HORSE, …).
employer / institution / issuerA reference: either a UPC profile (isLinked: true, with its current name, handle and isVerified; link to it) or plain text (isLinked: false, name only; do not link). A reference to a profile that has since closed comes back as plain text.
jobTitle, jobTitleCodejobTitle is the display text (localized when the title is from the catalogue); jobTitleCode is set only for catalogue titles.
startDate, endDate, …ISO yyyy-MM-dd, always the first of a month. isCurrent: true roles have no endDate.
sectionAudiencesAlways null unless the caller is the owner.
awards[].statusCode, awards[].sourceCodeSince 2026-10-09. See Award statuses.

Since 2026-10-09 every award carries a statusCode and a sourceCode, in the portfolio’s awards list and in GET /profiles/{profileId}/awards.

sourceCodeMeaning
SELFRecorded by the profile itself, naming who gave it (issuer: a UPC profile or plain text).
ISSUERGiven by the business profile in issuer.
statusCodeMeaningIn a public read
CONFIRMEDThe issuer confirmed it, or the profile accepted an award a business gave it. Show a “Confirmed” mark.Yes
SELF_REPORTEDRecorded by the profile; the issuer has not confirmed it (a plain-text issuer can’t). Label it “Not confirmed”.Yes
PENDINGWaiting for the issuer to confirm, or for the profile to accept a business’s award.No
DECLINEDThe issuer declined it, or the profile declined a business’s award.No
WITHDRAWNThe business took back an award it gave.No

Anyone but the profile itself only ever receives CONFIRMED and SELF_REPORTED awards; never treat a missing award as proof of anything. Awards recorded before 2026-10-09 are all SELF / SELF_REPORTED. An award whose issuer profile has since closed while it was pending comes back as SELF_REPORTED.

"awards": [
{
"id": "0199…", "title": "Best in Show", "awardKindCode": "COMPETITION",
"issuer": { "profileId": "0199…", "handle": "groomexpo", "name": "Groom Expo",
"isLinked": true, "isVerified": false },
"awardedDate": "2025-03-01", "description": null,
"statusCode": "CONFIRMED", "sourceCode": "SELF"
}
]

The example is illustrative, with ids shortened. GET /profiles/{profileId}/awards returns a flatter row: { id, awardKindCode, title, issuerName, issuerProfileId, awardedDate, description, statusCode, sourceCode }.

These routes are on the same base path (/api/profile/v1/{locale}/Profile). Unlike the reads above they are not anonymous: the caller must act as {profileId} with permission to edit it. A call without a signed-in identity is 401; a caller who may not edit the profile is 403. {profileId} is the side you act for: the profile the award is on (recipient) or the profile that gave or is named as the giver (issuer).

MethodPathSideDoes
POST/profiles/{profileId}/awardsrecipientRecord an award: { title, awardKindCode, issuerProfileId?, issuerName?, awardedDate?, description? } (an issuer id or a name, at least one). A linked issuer is asked to confirm (PENDING); a plain-text one saves SELF_REPORTED. 201
PUT/profiles/{profileId}/awards/{awardId}recipientEdit a recorded award (same body). Changing a CONFIRMED award asks the issuer again. 403 on an award a business gave; 409 on one the issuer declined
DELETE/profiles/{profileId}/awards/{awardId}recipientRemove any award from the profile
GET/profiles/{profileId}/awards/receivedrecipientEvery award on the profile, every status, except business awards it declined
POST/profiles/{profileId}/awards/{awardId}/request-confirmationrecipientAsk the linked issuer of a SELF_REPORTED award to confirm it → PENDING
POST/profiles/{profileId}/awards/{awardId}/acceptrecipientAccept a PENDING award a business gave → CONFIRMED
POST/profiles/{profileId}/awards/{awardId}/declinerecipientDecline it → DECLINED
GET/profiles/{profileId}/award-confirmationsissuerAwards others recorded naming this profile, waiting for its answer
POST/profiles/{profileId}/award-confirmations/{awardId}/confirmissuer→ CONFIRMED
POST/profiles/{profileId}/award-confirmations/{awardId}/declineissuerOptional body { noteText } (at most 300 characters, shown to the member) → DECLINED
GET/profiles/{profileId}/given-awardsissuerAwards this business gave, every status
POST/profiles/{profileId}/given-awardsissuerGive an award: { recipientProfileId, awardKindCode, titleLocaleDefault, descriptionLocaleDefault?, awardedDate? } → PENDING. Business profiles only. 201
PUT/profiles/{profileId}/given-awards/{awardId}issuerEdit it (same body without the recipient); changing an accepted award asks the recipient again
DELETE/profiles/{profileId}/given-awards/{awardId}issuerWithdraw it: a pending award is deleted, an accepted one becomes WITHDRAWN

Every write except DELETE …/awards/{awardId} returns the award’s id and its status now: { "id": "0199…", "statusCode": "PENDING" } (a withdrawal answers WITHDRAWN whether the row was deleted or kept). The three GET lists return awards with id, profileId, awardKindCode, awardKindName, titleLocaleDefault, descriptionLocaleDefault, awardedDate, sourceCode, statusCode, requestedAtUtc, decidedAtUtc, decisionNoteText, recipient, issuer (references like issuer above, issuer null for a plain-text giver), issuerNameLocaleDefault and canEdit.

Rules and refusals:

  • Awards are recorded on person, business and place profiles; only a business profile gives them, to any active profile but itself.
  • Two profiles run by the same owners never ask each other: recording saves SELF_REPORTED, asking is 400, giving is 403.
  • A profile holds at most 30 awards (declined and withdrawn ones don’t count), and a business can have at most 3 pending awards for the same recipient; past either limit the write is 403.
  • An action on an award that is no longer in the status it needs (someone answered first) is 409: read the list again.
  • 400 for a missing title, a title over 200 or a description over 1,000 characters, or an unknown awardKindCode (names from /career-portfolio/lookups). 404 for an award that isn’t on (or doesn’t name) {profileId}.

GET /skills/catalog returns { categories: [ { code, name, iconSvg, displayOrder, professionalKindCodes, skills: [ { code, name, speciesCode, iconSvg } ] } ] }: 24 categories and 493 skills, localized into every platform locale. speciesCode is set only for species-specific skills; professionalKindCodes lists the professions for which a category comes first (empty = everyone). iconSvg is a self-contained square SVG string; render it as an image (for example a data:image/svg+xml URI), not as inline markup.

Skill codes are permanent: a code never changes meaning. A retired skill drops out of the catalogue, but existing portfolio rows keep their skillCode and name.

GET /career-portfolio/lookups returns { employmentTypes, degreeTypes, awardKinds, workSettings, jobTitles }. Each item is { code, name }; job titles add professionalKindCode. Work settings are SALON, MOBILE, IN_HOME, CLINIC, KENNEL, FARM and REMOTE. Cache lookups and the catalogue for an hour or more; they change only with platform releases.

The public view of a portfolio is safe to cache briefly (a few minutes): it is the same for every anonymous caller. Do not cache a response fetched with a member’s session under the same key as the public view.

Editing a portfolio is not part of the partner surface: the writes require the person’s own session acting as their profile (the award routes above included). Organization profiles have no Career Portfolio; use portfolio-items for their work samples and awards for their awards.