Portfolio Process API (PAPI) v1.4.0
Scroll down for code samples, example requests and responses. Select a language for code samples from the tabs above or the mobile navigation menu.
A Process API (PAPI) that combines, transforms and filters the portfolio’s Salesforce data, the same records the System API (SAPI) serves, into responses shaped for a client. PAPI reads them in process, not through SAPI over HTTP (section 3).
Deprecation of version 1
Version 1 is deprecated and has a sunset date. Use version 2 (X-API-Version: v2, documented in portfolio-papi-v2.yaml), which returns public keys only. The sunset date is 30 days after version 2 reaches production.
- Before the sunset date: every version 1 response carries a
Deprecationheader (RFC 9745), aLinkheader pointing at the version 2 reference,https://ryanbumstead.com/api-lab/reference?version=v2, and, once the sunset date is set, aSunsetheader (RFC 8594) with that date. The body is unchanged. - After the sunset date: every version 1 call returns
410 Gonein the standard error envelope witherrorCodeGONE, a short message and the link to the version 2 reference. No data is returned.
1. Authentication Strategy
- Callers → PAPI: OAuth 2.0 Client Credentials issued by the Portfolio API External Client App;
Authorization: Bearer <token>on every request (ADR-027). The platform rejects unauthenticated calls before Apex executes. - Site (API Lab):
c-api-testercalls an Apex proxy that obtains its token through thePortfolio_APINamed Credential; no secret reaches the browser. - External evaluators:
POST /auth/tokenvends a short-lived token (ADR-028). It is the only unauthenticated path, and the only path served on the Experience Cloud site domain (see itsserversentry). Every other path is called on My Domain with the Bearer token. - PAPI → SAPI: in-process Apex in the same transaction, not HTTP. See section 3 and ADR-025 for why fan out callouts were rejected.
2. Versioning Strategy
- Pattern: Header-based versioning takes precedence.
- Header:
X-API-Version: v1. A missing header resolves tov1; any other value returns400 BAD_REQUESTwith messageUnsupported X-API-Version(ADR-026). - Correlation: a missing
X-Request-Idis replaced by a generated UUID, and a present but non-UUID value returns400 BAD_REQUEST. On My Domain the platform injects its own 32-character hex id when the caller sends none, and PAPI treats that value as missing. Both headers are on every response (ApiRequestContext.applyRequestRules): the platform echoes whateverX-Request-Idthe request carries, its own included, and Apex sets the header only when it generated the value and the request carried none, so the header appears once (ADR-026). When the platform injected its id, the header carries that id and an error body’scorrelationIdcarries the generated UUID. Send your own UUID to get one predictable id. - URL: Version segments removed from base path to align with SAPI ADR.
3. Rate Limiting & Aggregation Math
- SAPI Limit: 120 requests/minute.
- PAPI work per request: PAPI never calls SAPI over HTTP. It reads the same records in process, in the same Apex transaction, through
QueryDefinitions(ADR-033). One/profile/fullrequest runs nine reads (owner, roles, highlights, skills, role skills, projects, certifications, education, testimonials) and builds one response. - PAPI Limit: 15 requests/minute (Standard Tier) against SAPI’s 120. One PAPI request runs about eight data reads in one transaction, the same records about eight SAPI requests would return. Eight data reads are not eight HTTP requests, but the lower limit keeps one caller’s PAPI load in step with what the same data costs through SAPI (ADR-025).
- Why not HTTP (ADR-025): making the reads HTTP requests to SAPI was rejected. The site’s API Lab reaches PAPI through the
Portfolio_APINamed Credential, a callout into this same org, and a request that arrived as a same org callout may not call out again (ADR-015), so the fan out would fail on the site’s own path. Each SAPI request would also be an inbound REST request counted against the org’s daily API request allocation (15,000 on this Developer Edition), the callouts would run one after another inside the 120 second callout time a transaction allows, and they would all run as the one shared evaluator session, spending the SAPI limit every evaluator shares. So PAPI and SAPI stay distinct logical layers: PAPI reads the same records in process, through the query engine (QueryDefinitions) and request framework (ApiRequestContext) SAPI also uses, with its own query definitions and its own transformer (PAPITransformer). The deliberate exception is/health, which calls SAPI’s/sapi/v1/healththroughPortfolio_APIto prove the credential works. - Enforcement:
RateWindowLimiter(ADR-025), oneAPI_Rate_Window__crow per caller key per fixed UTC calendar minute. The caller key is{tier}:{SHA-256 hex of the session id}, or{tier}:{user id}when the session id is null. Tiers:papi15,papi-ai(/ai/*) 10. Every token fromPOST /auth/tokenis the same session, so all evaluators share one key per tier and one limit (SAPI 120, PAPI 15,/ai/*10 per minute, ADR-028).POST /auth/tokenis also limited to 15 requests per minute per email and source IP.
4. Caching Strategy (Aggregation Logic)
Note: each section of a PAPI response takes the cache policy its SAPI endpoint sends for the same data (PAPITransformer), and the response takes the strictest of its sections.
- Rule: Strictest policy wins.
- If any section’s resource is
no-store(e.g., Contact) → PAPI response isno-store. - If any section’s resource is
private→ PAPI response isprivate. - Otherwise → PAPI response is
publicwith the minimummax-age.
- If any section’s resource is
5. Identifiers (ADR-038)
Each record in a response carries a stable public key, for example prj_8f3k2m9qtz, beside id. Use key. The id field is the Salesforce Id, kept in v1 and deprecated. PAPI takes no Id parameters. SAPI takes the key wherever it takes an Id.
6. Field Mapping (Transformation Layer)
| Object | SAPI Field | PAPI Field | Logic |
| :— | :— | :— | :— |
| Contact | name | fullName | Rename |
| Experience | name | role | Semantic Rename |
| Certification | earnedDate | dateEarned | JS Convention |
| Project | dateCompleted | completionDate | JS Convention |
| Project | heroImageUrl | heroImageUrl | Passthrough |
Base URLs:
-
https://{domain}/services/apexrest/papi/v1
-
domain - The org’s My Domain host. Default: ryan-bumstead-dev-ed.develop.my.salesforce.com
- ryan-bumstead-dev-ed.develop.my.salesforce.com
-
Email: Ryan Bumstead Web: Ryan Bumstead License: MIT
Authentication
-
oAuth2 authentication. OAuth 2.0 Client Credentials flow issued by the Portfolio API External Client App (ADR-027). Tokens run as the read-only integration user. External evaluators obtain a short-lived token from
POST /auth/token(ADR-028) instead of holding the consumer secret. Every evaluator receives the same session, so all evaluators share one rate limit per tier.-
Flow: clientCredentials
-
Token URL = https://ryan-bumstead-dev-ed.develop.my.salesforce.com/services/oauth2/token
-
| Scope | Scope Description |
|---|---|
| api | Access Salesforce REST APIs as the integration user |
Default
servers__auth_token
Code samples
const headers = {
'Authorization':'Bearer {access-token}'
};
fetch('https://{domain}/services/apexrest/papi/v1/auth/token',
{
method: 'SERVERS',
headers: headers
})
.then(function(res) {
return res.json();
}).then(function(body) {
console.log(body);
});
SERVERS /auth/token
Responses
|Status|Meaning|Description|Schema| |—|—|—|—|
Orchestration
Endpoints that combine several reads into one response
getFullPortfolio
Code samples
const headers = {
'Accept':'application/json',
'X-Request-Id':'497f6eca-6276-4993-bfeb-53cbbbba6f08',
'X-API-Version':'v1',
'Authorization':'Bearer {access-token}'
};
fetch('https://{domain}/services/apexrest/papi/v1/profile/full',
{
method: 'GET',
headers: headers
})
.then(function(res) {
return res.json();
}).then(function(body) {
console.log(body);
});
GET /profile/full
Get Complete Portfolio State
The “Hydration” endpoint. Returns the entire portfolio state in a single call.
How it is built: nine reads in process, in one Apex transaction, through QueryDefinitions, not SAPI calls over HTTP (see the description’s section 3). The reads are the owner, roles, highlights, skills (at most 100), the skills of each role, projects, certifications, education and approved testimonials. Each read returns its records whole, so there is no paging.
Partial failure: each section is read on its own. A section that fails comes back empty, the response is still 200 OK, X-Data-Integrity is partial and integrityNote names the section.
Caching: the strictest cache policy of the sections read.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
| X-Request-Id | header | string(uuid) | false | Correlation ID. When missing, or when it is the platform’s own 32-character hex id, a generated UUID is used as the correlation id. A present but non-UUID value returns 400 BAD_REQUEST (ADR-026). |
| X-API-Version | header | string | false | API Version. Missing resolves to v1; any other value returns 400 BAD_REQUEST with message ‘Unsupported X-API-Version’ (ADR-026). |
Example responses
200 Response
{
"config": {
"linkedInUrl": "https://linkedin.com/in/ryanbumstead",
"calendlyUrl": "https://example.com/schedule",
"githubUrl": "https://example.com/source",
"trailheadUrl": "https://example.com/trailhead",
"personalWebsiteUrl": "https://ryanbumstead.com",
"careerObjective": "Driving digital transformation through architecture."
},
"contactInfo": {
"id": "0035e00000B2O3DAAV",
"fullName": "Ryan Bumstead",
"title": "Platform Architect",
"linkedIn": "https://linkedin.com/in/ryanbumstead",
"github": "https://example.com/source",
"trailhead": "https://example.com/trailhead",
"portfolioUrl": "https://ryanbumstead.com",
"bio": "Specializing in API-led connectivity..."
},
"summary": "Salesforce Platform Architect with nearly a decade on the platform, focused on higher education",
"skills": [
{
"id": "a075e00000B2O3DAAV",
"name": "Apex",
"displayName": "Salesforce Apex",
"category": "Backend",
"proficiency": 5,
"iconName": "utility:code",
"colorHex": "#0070d2"
},
{
"id": "a075e00000B2O3DAAX",
"name": "LWC",
"displayName": "Lightning Web Components",
"category": "Frontend",
"proficiency": 5,
"iconName": "standard:lightning_component",
"colorHex": "#00A1E0"
}
],
"experience": [
{
"id": "a025e00000B2O3DAAV",
"employer": "Salesforce",
"role": "Senior Technical Architect",
"dateRange": "Jan 2023 - Present",
"isCurrent": true,
"isRemote": true,
"skillsUsed": [
"Apex",
"LWC",
"Event Bus"
],
"highlights": [
"Designed multi-cloud architectures",
"Led DevOps transformation"
]
}
],
"projects": [
{
"id": "a015e00000B2O3DAAV",
"title": "Portfolio Site",
"challenge": "Display skills dynamically",
"solution": "Built on LWR with SAPI/PAPI architecture",
"businessValue": "Increased interview rate by 40%",
"status": "Live – In Production",
"completionDate": "2025-12-01",
"heroImageUrl": "https://example.com/images/hero.png",
"liveUrl": "https://example.com/portfolio",
"repositoryUrl": "https://example.com/source",
"technologies": [
"LWR",
"Apex"
],
"assets": []
}
],
"certifications": [
{
"id": "a105e00000B2O3DAAV",
"name": "Certified Application Architect",
"issuer": "Salesforce",
"dateEarned": "2024-05-15",
"skillsUsed": [
"Security"
]
}
],
"education": [
{
"id": "a115e00000B2O3DAAV",
"school": "Creighton University",
"degree": "B.S. Computer Science",
"fieldOfStudy": "Computer Science",
"year": "2018",
"gpa": 3.8
}
],
"testimonials": [
{
"id": "a055e00000B2O3DAAV",
"author": "Jane Doe",
"title": "VP of Engineering",
"relationship": "Manager",
"quote": "Ryan is a beast at architecture.",
"vibe": "Professional",
"avatarUrl": "https://example.com/images/avatar.jpg"
}
],
"integrityNote": "complete"
}
400 Response
{
"httpStatus": 400,
"errorCode": "BAD_REQUEST",
"message": "Invalid persona value.",
"correlationId": "123e4567-e89b-12d3-a456-426614174000",
"retryable": false
}
401 Response
{
"httpStatus": 401,
"errorCode": "UNAUTHORIZED",
"message": "Missing or invalid Bearer token",
"correlationId": "123e4567-e89b-12d3-a456-426614174000",
"retryable": false
}
500 Response
{
"httpStatus": 500,
"errorCode": "INTERNAL_SERVER_ERROR",
"message": "Failed to aggregate profile data. Partial SAPI failure.",
"correlationId": "123e4567-e89b-12d3-a456-426614174000",
"retryable": true,
"details": {
"failedSapiCalls": [
{
"endpoint": "/experience-skills",
"error": "Connection timeout"
}
]
}
}
503 Response
{
"httpStatus": 503,
"errorCode": "SERVICE_UNAVAILABLE",
"message": "Downstream SAPI is currently unavailable",
"correlationId": "123e4567-e89b-12d3-a456-426614174000",
"retryable": true
}
Responses
| Status | Meaning | Description | Schema |
|---|---|---|---|
| 200 | OK | The complete portfolio payload | FullPortfolioPayload |
| 400 | Bad Request | Invalid request parameters | Error |
| 401 | Unauthorized | Missing, expired, or invalid Bearer token (rejected by the platform before Apex executes) | Error |
| 500 | Internal Server Error | Internal PAPI error or SAPI communication failure | Error |
| 503 | Service Unavailable | PAPI or downstream SAPI is unavailable | Error |
Response Headers
| Status | Header | Type | Format | Description |
|---|---|---|---|---|
| 200 | X-Request-Id | string | uuid | Correlation ID for distributed tracing. A valid caller-supplied value is echoed by the platform; Apex sets the header only when it generated the value. |
| 200 | Cache-Control | string | The strictest policy of the sections read. The contact section makes it no-store. | |
| 200 | X-Data-Integrity | string | Indicates if the payload is complete or has partial missing data. | |
| 400 | X-Request-Id | string | uuid | Correlation ID for distributed tracing. A valid caller-supplied value is echoed by the platform; Apex sets the header only when it generated the value. |
| 401 | X-Request-Id | string | uuid | Correlation ID for distributed tracing. A valid caller-supplied value is echoed by the platform; Apex sets the header only when it generated the value. |
| 500 | X-Request-Id | string | uuid | Correlation ID for distributed tracing. A valid caller-supplied value is echoed by the platform; Apex sets the header only when it generated the value. |
| 503 | X-Request-Id | string | uuid | Correlation ID for distributed tracing. A valid caller-supplied value is echoed by the platform; Apex sets the header only when it generated the value. |
| 503 | Retry-After | integer | Seconds until service may be available |
getProfileSummary
Code samples
const headers = {
'Accept':'application/json',
'X-Request-Id':'497f6eca-6276-4993-bfeb-53cbbbba6f08',
'X-API-Version':'v1',
'Authorization':'Bearer {access-token}'
};
fetch('https://{domain}/services/apexrest/papi/v1/profile/summary',
{
method: 'GET',
headers: headers
})
.then(function(res) {
return res.json();
}).then(function(body) {
console.log(body);
});
GET /profile/summary
Get Employment Profile Only
Reads the owner, roles, skills, certifications and education in process, in one
Apex transaction, and attaches each role’s skills to it. The same records SAPI’s
/contacts, /experience, /experience-skills, /skills, /certifications
and /education serve, without calling them over HTTP.
Use Case: Resume page, profile cards
Caching: Cache-Control is fixed at max-age=300, private by decision, an explicit exception to
strictest-policy-wins.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
| X-Request-Id | header | string(uuid) | false | Correlation ID. When missing, or when it is the platform’s own 32-character hex id, a generated UUID is used as the correlation id. A present but non-UUID value returns 400 BAD_REQUEST (ADR-026). |
| X-API-Version | header | string | false | API Version. Missing resolves to v1; any other value returns 400 BAD_REQUEST with message ‘Unsupported X-API-Version’ (ADR-026). |
Example responses
200 Response
{
"contactInfo": {
"id": "0035e00000B2O3DAAV",
"key": "con_7c5j3w9sfa",
"fullName": "Ryan Bumstead",
"title": "Platform Architect",
"linkedIn": "https://linkedin.com/in/ryanbumstead",
"github": "https://example.com/source",
"trailhead": "https://example.com/trailhead",
"portfolioUrl": "https://ryanbumstead.com",
"bio": "Specializing in API-led connectivity..."
},
"summary": "Salesforce Platform Architect with nearly a decade on the platform, focused on higher education",
"skills": [
{
"id": "a075e00000B2O3DAAV",
"key": "skl_3m9x5b2rkt",
"name": "Apex",
"displayName": "Salesforce Apex",
"category": "Backend",
"proficiency": 5,
"iconName": "utility:code",
"colorHex": "#0070d2",
"categoryOrder": 10
}
],
"experience": [
{
"id": "a025e00000B2O3DAAV",
"key": "exp_4h7n2c8wpd",
"employer": "Salesforce",
"role": "Senior Technical Architect",
"dateRange": "Jan 2023 - Present",
"isCurrent": true,
"isRemote": true,
"skillsUsed": [
"LWC"
],
"highlights": [
"Implemented CI/CD pipelines using GitHub Actions"
]
}
],
"certifications": [
{
"id": "a105e00000B2O3DAAV",
"key": "crt_6q2v8d4nhz",
"name": "Certified Application Architect",
"issuer": "Salesforce",
"dateEarned": "2024-05-15",
"skillsUsed": [
"Security"
]
}
],
"education": [
{
"id": "a115e00000B2O3DAAV",
"key": "edu_6b4c8j2rvq",
"school": "Creighton University",
"degree": "B.S. Computer Science",
"fieldOfStudy": "Computer Science",
"year": "2018",
"gpa": 3.8
}
],
"integrityNote": "complete"
}
400 Response
{
"httpStatus": 400,
"errorCode": "BAD_REQUEST",
"message": "Invalid persona value.",
"correlationId": "123e4567-e89b-12d3-a456-426614174000",
"retryable": false
}
401 Response
{
"httpStatus": 401,
"errorCode": "UNAUTHORIZED",
"message": "Missing or invalid Bearer token",
"correlationId": "123e4567-e89b-12d3-a456-426614174000",
"retryable": false
}
500 Response
{
"httpStatus": 500,
"errorCode": "INTERNAL_SERVER_ERROR",
"message": "Failed to aggregate profile data. Partial SAPI failure.",
"correlationId": "123e4567-e89b-12d3-a456-426614174000",
"retryable": true,
"details": {
"failedSapiCalls": [
{
"endpoint": "/experience-skills",
"error": "Connection timeout"
}
]
}
}
503 Response
{
"httpStatus": 503,
"errorCode": "SERVICE_UNAVAILABLE",
"message": "Downstream SAPI is currently unavailable",
"correlationId": "123e4567-e89b-12d3-a456-426614174000",
"retryable": true
}
Responses
| Status | Meaning | Description | Schema |
|---|---|---|---|
| 200 | OK | A unified profile object | ProfileSummary |
| 400 | Bad Request | Invalid request parameters | Error |
| 401 | Unauthorized | Missing, expired, or invalid Bearer token (rejected by the platform before Apex executes) | Error |
| 500 | Internal Server Error | Internal PAPI error or SAPI communication failure | Error |
| 503 | Service Unavailable | PAPI or downstream SAPI is unavailable | Error |
Response Headers
| Status | Header | Type | Format | Description |
|---|---|---|---|---|
| 200 | X-Request-Id | string | uuid | Correlation ID for distributed tracing. A valid caller-supplied value is echoed by the platform; Apex sets the header only when it generated the value. |
| 200 | X-Data-Integrity | string | none | |
| 200 | Cache-Control | string | none | |
| 400 | X-Request-Id | string | uuid | Correlation ID for distributed tracing. A valid caller-supplied value is echoed by the platform; Apex sets the header only when it generated the value. |
| 401 | X-Request-Id | string | uuid | Correlation ID for distributed tracing. A valid caller-supplied value is echoed by the platform; Apex sets the header only when it generated the value. |
| 500 | X-Request-Id | string | uuid | Correlation ID for distributed tracing. A valid caller-supplied value is echoed by the platform; Apex sets the header only when it generated the value. |
| 503 | X-Request-Id | string | uuid | Correlation ID for distributed tracing. A valid caller-supplied value is echoed by the platform; Apex sets the header only when it generated the value. |
| 503 | Retry-After | integer | Seconds until service may be available |
getFeaturedProjects
Code samples
const headers = {
'Accept':'application/json',
'X-Request-Id':'497f6eca-6276-4993-bfeb-53cbbbba6f08',
'X-API-Version':'v1',
'Authorization':'Bearer {access-token}'
};
fetch('https://{domain}/services/apexrest/papi/v1/projects/featured',
{
method: 'GET',
headers: headers
})
.then(function(res) {
return res.json();
}).then(function(body) {
console.log(body);
});
GET /projects/featured
Get Featured Projects (Enriched)
Retrieves projects marked as ‘Featured’ with nested Skills and Assets.
Reads, in process in one Apex transaction: the featured projects, then their skills and their assets, each in one query for every project at once.
Use Case: Home page project showcase, portfolio gallery
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
| X-Request-Id | header | string(uuid) | false | Correlation ID. When missing, or when it is the platform’s own 32-character hex id, a generated UUID is used as the correlation id. A present but non-UUID value returns 400 BAD_REQUEST (ADR-026). |
| X-API-Version | header | string | false | API Version. Missing resolves to v1; any other value returns 400 BAD_REQUEST with message ‘Unsupported X-API-Version’ (ADR-026). |
Example responses
200 Response
[
{
"id": "a015e00000B2O3DAAV",
"key": "prj_8f3k2m9qtz",
"title": "Portfolio Site",
"challenge": "Display skills dynamically to recruiters",
"solution": "Built on LWR with SAPI/PAPI architecture",
"businessValue": "Increased interview rate by 40%",
"status": "Live – In Production",
"completionDate": "2025-12-01",
"heroImageUrl": "https://example.com/images/hero.png",
"liveUrl": "https://example.com/portfolio",
"repositoryUrl": "https://example.com/source",
"technologies": [
"Apex"
],
"pillarLabel": "Pillar A: Business Architecture",
"assets": [
{
"type": "Image",
"url": "https://example.com/images/diagram.png",
"caption": "System Architecture Diagram"
}
]
}
]
400 Response
{
"httpStatus": 400,
"errorCode": "BAD_REQUEST",
"message": "Invalid persona value.",
"correlationId": "123e4567-e89b-12d3-a456-426614174000",
"retryable": false
}
401 Response
{
"httpStatus": 401,
"errorCode": "UNAUTHORIZED",
"message": "Missing or invalid Bearer token",
"correlationId": "123e4567-e89b-12d3-a456-426614174000",
"retryable": false
}
500 Response
{
"httpStatus": 500,
"errorCode": "INTERNAL_SERVER_ERROR",
"message": "Failed to aggregate profile data. Partial SAPI failure.",
"correlationId": "123e4567-e89b-12d3-a456-426614174000",
"retryable": true,
"details": {
"failedSapiCalls": [
{
"endpoint": "/experience-skills",
"error": "Connection timeout"
}
]
}
}
503 Response
{
"httpStatus": 503,
"errorCode": "SERVICE_UNAVAILABLE",
"message": "Downstream SAPI is currently unavailable",
"correlationId": "123e4567-e89b-12d3-a456-426614174000",
"retryable": true
}
Responses
| Status | Meaning | Description | Schema |
|---|---|---|---|
| 200 | OK | A list of enriched project records | Inline |
| 400 | Bad Request | Invalid request parameters | Error |
| 401 | Unauthorized | Missing, expired, or invalid Bearer token (rejected by the platform before Apex executes) | Error |
| 500 | Internal Server Error | Internal PAPI error or SAPI communication failure | Error |
| 503 | Service Unavailable | PAPI or downstream SAPI is unavailable | Error |
Response Schema
Status Code 200
| Name | Type | Required | Restrictions | Description |
|---|---|---|---|---|
| anonymous | [EnrichedProject] | false | none | [Project enriched with Skills and Assets] |
| » id | string | true | none | Deprecated, use key. |
| » key | string | false | read-only | Stable public key of the record. Use it in place of id. It is set once and never changes. |
| » title | string¦null | false | none | none |
| » challenge | string | false | none | none |
| » solution | string | false | none | none |
| » businessValue | string | false | none | none |
| » status | string¦null | false | none | Project Status (e.g., Live, Archived) |
| » completionDate | string(date)¦null | false | none | Mapped from SAPI dateCompleted |
| » heroImageUrl | string(uri) | false | none | URL of the main display image. Matches SAPI field name. |
| » liveUrl | string(uri) | false | none | none |
| » repositoryUrl | string(uri) | false | none | none |
| » technologies | [string] | false | none | Skill names from ProjectSkill junction |
| » pillarLabel | string¦null | false | none | The strategic pillar and pillar description (e.g. Pillar A: Business Architecture) |
| » assets | [object] | false | none | Images, videos, or links related to the project |
| »» type | string | true | none | none |
| »» url | string(uri) | true | none | none |
| »» caption | string | false | none | none |
Enumerated Values
| Property | Value |
|---|---|
| type | Image |
| type | Video |
| type | Document |
| type | Link |
Response Headers
| Status | Header | Type | Format | Description |
|---|---|---|---|---|
| 200 | X-Request-Id | string | uuid | Correlation ID for distributed tracing. A valid caller-supplied value is echoed by the platform; Apex sets the header only when it generated the value. |
| 200 | X-Total-Count | integer | none | |
| 200 | X-Data-Integrity | string | none | |
| 200 | X-Integrity-Note | string | Present only when partial; names the failed sub-queries, e.g. “partial: project-skills unavailable”. | |
| 200 | Cache-Control | string | none | |
| 400 | X-Request-Id | string | uuid | Correlation ID for distributed tracing. A valid caller-supplied value is echoed by the platform; Apex sets the header only when it generated the value. |
| 401 | X-Request-Id | string | uuid | Correlation ID for distributed tracing. A valid caller-supplied value is echoed by the platform; Apex sets the header only when it generated the value. |
| 500 | X-Request-Id | string | uuid | Correlation ID for distributed tracing. A valid caller-supplied value is echoed by the platform; Apex sets the header only when it generated the value. |
| 503 | X-Request-Id | string | uuid | Correlation ID for distributed tracing. A valid caller-supplied value is echoed by the platform; Apex sets the header only when it generated the value. |
| 503 | Retry-After | integer | Seconds until service may be available |
Transformation
Endpoints that transform SAPI data into new formats
generateResume
Code samples
const headers = {
'Accept':'application/json',
'X-Request-Id':'497f6eca-6276-4993-bfeb-53cbbbba6f08',
'X-API-Version':'v1',
'Authorization':'Bearer {access-token}'
};
fetch('https://{domain}/services/apexrest/papi/v1/resume/generate',
{
method: 'GET',
headers: headers
})
.then(function(res) {
return res.json();
}).then(function(body) {
console.log(body);
});
GET /resume/generate
Generate Plain Text Resume
Generates an ATS-optimized plain text resume.
Reads, in process in one Apex transaction: the owner, roles, highlights (by persona), skills, certifications and education. The same records SAPI serves, without calling it.
Transformation Logic:
- Formats dates as “MMM YYYY - MMM YYYY” or “MMM YYYY - Present”
- Lists the skills linked to the printed roles, each once, grouped by category in category order and strongest first within a category
- Prints up to 10 roles, current first
- Prints at most 5 highlights per role, in sort order, as stored
Persona Filtering:
Admin: System Admin highlights onlyDeveloper: Development highlights onlyArchitect: Architecture/Design highlights only- Skills follow the same persona: a skill prints when its persona tags include the persona, and a skill with no persona tag prints under every persona. With no persona, every linked skill prints. The site’s resume page uses the same rule.
Persona values:
Admin,DeveloperorArchitect. Any other value returns 400 BAD_REQUEST.- If persona is omitted, all highlights are returned (no filtering)
Response format:
- When the first media type in the
Acceptheader istext/plain, a 200 answer is the generated text alone, withContent-Type: text/plain; charset=UTF-8. Parameters after a semicolon are ignored and the header name matches in any case. - With no
Acceptheader,*/*,application/jsonor any other first type, the answer is the JSON object below. - Every other response header, the rate count and the cache directive are the same in both forms. Errors are always JSON, whatever
Acceptsays.
For example, with your token in TOKEN and the instance URL in INSTANCE_URL:
curl -H "Authorization: Bearer $TOKEN" -H "Accept: text/plain" "$INSTANCE_URL/services/apexrest/papi/v1/resume/generate?persona=Architect"
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
| X-Request-Id | header | string(uuid) | false | Correlation ID. When missing, or when it is the platform’s own 32-character hex id, a generated UUID is used as the correlation id. A present but non-UUID value returns 400 BAD_REQUEST (ADR-026). |
| X-API-Version | header | string | false | API Version. Missing resolves to v1; any other value returns 400 BAD_REQUEST with message ‘Unsupported X-API-Version’ (ADR-026). |
| persona | query | string | false | Target audience for the resume. Omit for all highlights and all skills. With a persona, skills tagged for it and every untagged skill are kept. |
Detailed descriptions
persona: Target audience for the resume. Omit for all highlights and all skills. With a persona, skills tagged for it and every untagged skill are kept.
Note: Highlights are filtered by their persona tag in process.
Enumerated Values
| Parameter | Value |
|---|---|
| persona | Admin |
| persona | Developer |
| persona | Architect |
Example responses
200 Response
{
"integrityNote": "complete",
"generatedText": "Ryan Bumstead\nSalesforce Platform Architect\n\nEXPERIENCE\n\nSalesforce | Senior Technical Architect\nJan 2023 - Present\n- Designed multi-cloud solutions...\n- Led DevOps transformation...\n",
"persona": "Architect",
"generatedAt": "2019-08-24T14:15:22Z"
}
"Ryan Bumstead Salesforce Platform Architect\nEXPERIENCE\nSalesforce | Senior Technical Architect Jan 2023 - Present - Designed multi-cloud solutions... - Led DevOps transformation..."
400 Response
{
"httpStatus": 400,
"errorCode": "BAD_REQUEST",
"message": "Invalid persona value.",
"correlationId": "123e4567-e89b-12d3-a456-426614174000",
"retryable": false
}
401 Response
{
"httpStatus": 401,
"errorCode": "UNAUTHORIZED",
"message": "Missing or invalid Bearer token",
"correlationId": "123e4567-e89b-12d3-a456-426614174000",
"retryable": false
}
500 Response
{
"httpStatus": 500,
"errorCode": "INTERNAL_SERVER_ERROR",
"message": "Failed to aggregate profile data. Partial SAPI failure.",
"correlationId": "123e4567-e89b-12d3-a456-426614174000",
"retryable": true,
"details": {
"failedSapiCalls": [
{
"endpoint": "/experience-skills",
"error": "Connection timeout"
}
]
}
}
503 Response
{
"httpStatus": 503,
"errorCode": "SERVICE_UNAVAILABLE",
"message": "Downstream SAPI is currently unavailable",
"correlationId": "123e4567-e89b-12d3-a456-426614174000",
"retryable": true
}
Responses
| Status | Meaning | Description | Schema |
|---|---|---|---|
| 200 | OK | The formatted resume text, as a JSON object by default, or as plain text when the first media type in Accept is text/plain |
string |
| 400 | Bad Request | Invalid request parameters | Error |
| 401 | Unauthorized | Missing, expired, or invalid Bearer token (rejected by the platform before Apex executes) | Error |
| 500 | Internal Server Error | Internal PAPI error or SAPI communication failure | Error |
| 503 | Service Unavailable | PAPI or downstream SAPI is unavailable | Error |
Response Headers
| Status | Header | Type | Format | Description |
|---|---|---|---|---|
| 200 | X-Request-Id | string | uuid | Correlation ID for distributed tracing. A valid caller-supplied value is echoed by the platform; Apex sets the header only when it generated the value. |
| 200 | X-Data-Integrity | string | none | |
| 200 | Cache-Control | string | none | |
| 400 | X-Request-Id | string | uuid | Correlation ID for distributed tracing. A valid caller-supplied value is echoed by the platform; Apex sets the header only when it generated the value. |
| 401 | X-Request-Id | string | uuid | Correlation ID for distributed tracing. A valid caller-supplied value is echoed by the platform; Apex sets the header only when it generated the value. |
| 500 | X-Request-Id | string | uuid | Correlation ID for distributed tracing. A valid caller-supplied value is echoed by the platform; Apex sets the header only when it generated the value. |
| 503 | X-Request-Id | string | uuid | Correlation ID for distributed tracing. A valid caller-supplied value is echoed by the platform; Apex sets the header only when it generated the value. |
| 503 | Retry-After | integer | Seconds until service may be available |
Logic
Endpoints that apply business rules and filtering
getTestimonialFeed
Code samples
const headers = {
'Accept':'application/json',
'X-Request-Id':'497f6eca-6276-4993-bfeb-53cbbbba6f08',
'X-API-Version':'v1',
'Authorization':'Bearer {access-token}'
};
fetch('https://{domain}/services/apexrest/papi/v1/testimonials/feed',
{
method: 'GET',
headers: headers
})
.then(function(res) {
return res.json();
}).then(function(body) {
console.log(body);
});
GET /testimonials/feed
Get Curated Testimonials
Retrieves testimonials with “Vibe Control” logic applied.
Reads, in process: approved testimonials only (Approved__c = true), at most 200.
Filtering Logic:
professional: Filters result set to only include records whererelationshipIN (‘Manager’, ‘Client’, ‘Peer’) ANDvibe= ‘Professional’.casual: Returns all approved testimonials, regardless of relationship type or vibe (includes ‘Casual’ vibe and ‘Fan’ relationships).
Use Case: About page testimonials section
mode is matched case-insensitively (the VibeMode values Professional and Casual are accepted);
any other value returns 400 BAD_REQUEST. Ordered by sort order, then newest first; at most 200.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
| X-Request-Id | header | string(uuid) | false | Correlation ID. When missing, or when it is the platform’s own 32-character hex id, a generated UUID is used as the correlation id. A present but non-UUID value returns 400 BAD_REQUEST (ADR-026). |
| X-API-Version | header | string | false | API Version. Missing resolves to v1; any other value returns 400 BAD_REQUEST with message ‘Unsupported X-API-Version’ (ADR-026). |
| mode | query | string | false | Filter testimonials by vibe |
Enumerated Values
| Parameter | Value |
|---|---|
| mode | professional |
| mode | casual |
Example responses
200 Response
[
{
"id": "a055e00000B2O3DAAV",
"key": "tst_2y7a5d9wmh",
"author": "Jane Doe",
"title": "VP of Engineering",
"relationship": "Manager",
"quote": "Ryan is a beast at architecture",
"vibe": "Professional",
"avatarUrl": "https://example.com/images/avatar.jpg"
}
]
400 Response
{
"httpStatus": 400,
"errorCode": "BAD_REQUEST",
"message": "Invalid persona value.",
"correlationId": "123e4567-e89b-12d3-a456-426614174000",
"retryable": false
}
401 Response
{
"httpStatus": 401,
"errorCode": "UNAUTHORIZED",
"message": "Missing or invalid Bearer token",
"correlationId": "123e4567-e89b-12d3-a456-426614174000",
"retryable": false
}
500 Response
{
"httpStatus": 500,
"errorCode": "INTERNAL_SERVER_ERROR",
"message": "Failed to aggregate profile data. Partial SAPI failure.",
"correlationId": "123e4567-e89b-12d3-a456-426614174000",
"retryable": true,
"details": {
"failedSapiCalls": [
{
"endpoint": "/experience-skills",
"error": "Connection timeout"
}
]
}
}
503 Response
{
"httpStatus": 503,
"errorCode": "SERVICE_UNAVAILABLE",
"message": "Downstream SAPI is currently unavailable",
"correlationId": "123e4567-e89b-12d3-a456-426614174000",
"retryable": true
}
Responses
| Status | Meaning | Description | Schema |
|---|---|---|---|
| 200 | OK | A filtered list of testimonials | Inline |
| 400 | Bad Request | Invalid request parameters | Error |
| 401 | Unauthorized | Missing, expired, or invalid Bearer token (rejected by the platform before Apex executes) | Error |
| 500 | Internal Server Error | Internal PAPI error or SAPI communication failure | Error |
| 503 | Service Unavailable | PAPI or downstream SAPI is unavailable | Error |
Response Schema
Status Code 200
| Name | Type | Required | Restrictions | Description |
|---|---|---|---|---|
| anonymous | [EnrichedTestimonial] | false | none | none |
| » id | string | true | none | Deprecated, use key. |
| » key | string | false | read-only | Stable public key of the record. Use it in place of id. It is set once and never changes. |
| » author | string | true | none | none |
| » title | string | false | none | none |
| » relationship | string | false | none | none |
| » quote | string | true | none | none |
| » vibe | string | false | none | none |
| » avatarUrl | string(uri) | false | none | none |
Enumerated Values
| Property | Value |
|---|---|
| relationship | Manager |
| relationship | Peer |
| relationship | Client |
| relationship | Recruiter |
| relationship | Fan |
| vibe | Professional |
| vibe | Casual |
Response Headers
| Status | Header | Type | Format | Description |
|---|---|---|---|---|
| 200 | X-Request-Id | string | uuid | Correlation ID for distributed tracing. A valid caller-supplied value is echoed by the platform; Apex sets the header only when it generated the value. |
| 200 | X-Total-Count | integer | none | |
| 200 | Cache-Control | string | none | |
| 400 | X-Request-Id | string | uuid | Correlation ID for distributed tracing. A valid caller-supplied value is echoed by the platform; Apex sets the header only when it generated the value. |
| 401 | X-Request-Id | string | uuid | Correlation ID for distributed tracing. A valid caller-supplied value is echoed by the platform; Apex sets the header only when it generated the value. |
| 500 | X-Request-Id | string | uuid | Correlation ID for distributed tracing. A valid caller-supplied value is echoed by the platform; Apex sets the header only when it generated the value. |
| 503 | X-Request-Id | string | uuid | Correlation ID for distributed tracing. A valid caller-supplied value is echoed by the platform; Apex sets the header only when it generated the value. |
| 503 | Retry-After | integer | Seconds until service may be available |
generateAiContent
Code samples
const inputBody = '{
"type": "CoverLetter",
"context": {
"jobDescription": "We are seeking a Senior Salesforce Architect...",
"companyName": "Acme Corp",
"role": "Senior Technical Architect",
"skills": [
"Apex",
"LWC",
"Integration Architecture"
],
"experienceSummary": "Nearly a decade on the Salesforce platform"
},
"tone": "Professional",
"maxLength": 500,
"provider": "Agentforce"
}';
const headers = {
'Content-Type':'application/json',
'Accept':'application/json',
'X-Request-Id':'497f6eca-6276-4993-bfeb-53cbbbba6f08',
'X-API-Version':'v1',
'Authorization':'Bearer {access-token}'
};
fetch('https://{domain}/services/apexrest/papi/v1/ai/generate',
{
method: 'POST',
body: inputBody,
headers: headers
})
.then(function(res) {
return res.json();
}).then(function(body) {
console.log(body);
});
POST /ai/generate
Generate AI Content
Generates content through PAPIAiGenerate.generate, which calls the one provider chain,
ContentGenerationController.dispatch.
Provider chain:
- Agentforce, only when its switch (
AI_Provider_Config__mdtrecordAgentforce) is on. It ships on and runs a Prompt Builder template as the caller. It has a 20 second timeout and an hourly budget (Hourly_Budget__c, 120 calls per UTC hour). Past the budget its attempt isbudget_exhaustedand the chain moves on. - Gemini 2.5 Flash (
gemini-2.5-flash, the model set on theGeminirecord) through the Gemini API (Gemini_APINamed Credential), with a 60 second timeout. The code default model isgemma-4-31b-it, used only when the record names none. - Local template (
Local), CoverLetter only.
A CoverLetter prompt also carries facts from featured projects and experience highlights,
chosen by keywords from role and jobDescription. No email address, phone number or
owner field is ever included. The site’s cover letter page calls this endpoint with
provider set to Agentforce through the Portfolio_API Named Credential, and runs
Gemini and the template in process when it gets no letter, because a request that arrives
as a callout from the same org may not call out again (ADR-015). Outside callers use it
with a Bearer token.
Asking for Agentforce only: send provider set to Agentforce. Only Agentforce is
tried, with no Gemini and no template. A failure is 503 ALL_PROVIDERS_UNAVAILABLE with
the message “Agentforce currently unavailable”, or “Agentforce hourly budget used” when
the budget is spent. A CoverLetter asked of Agentforce uses the site’s cover letter prompt
and comes back whole unless maxLength is sent.
Rate limit: 10 requests per minute (ai tier). Upstream, the Gemini API key allowed Gemini 2.5 Flash
5 requests per minute and 20 requests per day when it was read on 2026-10-20, a recorded reading and not a live one. When the Gemini API
refuses or fails, CoverLetter falls back to the local template and the other types answer
503 ALL_PROVIDERS_UNAVAILABLE.
Validation: type and tone must be enum values, provider only Agentforce, maxLength an integer 100 to 1000;
CoverLetter requires context.companyName and context.role. Content longer than maxLength is cut.
Body parameter
{
"type": "CoverLetter",
"context": {
"jobDescription": "We are seeking a Senior Salesforce Architect...",
"companyName": "Acme Corp",
"role": "Senior Technical Architect",
"skills": [
"Apex",
"LWC",
"Integration Architecture"
],
"experienceSummary": "Nearly a decade on the Salesforce platform"
},
"tone": "Professional",
"maxLength": 500,
"provider": "Agentforce"
}
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
| X-Request-Id | header | string(uuid) | false | Correlation ID. When missing, or when it is the platform’s own 32-character hex id, a generated UUID is used as the correlation id. A present but non-UUID value returns 400 BAD_REQUEST (ADR-026). |
| X-API-Version | header | string | false | API Version. Missing resolves to v1; any other value returns 400 BAD_REQUEST with message ‘Unsupported X-API-Version’ (ADR-026). |
| body | body | object | true | none |
| » type | body | string | true | The type of content to generate |
| » context | body | object | true | Contextual data for generation |
| »» jobDescription | body | string | false | Full text of job posting (for cover letters) |
| »» companyName | body | string | false | none |
| »» role | body | string | false | none |
| »» skills | body | [string] | false | Skills to emphasize |
| »» experienceSummary | body | string | false | Brief professional summary |
| » tone | body | string | false | Desired tone of generated content |
| » maxLength | body | integer | false | Maximum character count |
| » provider | body | string | false | Optional. When sent, only Agentforce is tried: no Gemini and no template, and a |
Detailed descriptions
» provider: Optional. When sent, only Agentforce is tried: no Gemini and no template, and a
failure is 503 ALL_PROVIDERS_UNAVAILABLE.
Enumerated Values
| Parameter | Value |
|---|---|
| » type | CoverLetter |
| » type | BioSummary |
| » type | SkillDescription |
| » tone | Professional |
| » tone | Enthusiastic |
| » tone | Technical |
| » tone | Casual |
| » provider | Agentforce |
Example responses
200 Response
{
"content": "Dear Hiring Manager,\n\nI am writing to express my strong interest in the Senior Technical Architect position at Acme Financial...\n",
"provider": "Gemini",
"model": "gemini-2.5-flash",
"type": "CoverLetter",
"tone": "Professional",
"generatedAt": "2019-08-24T14:15:22Z",
"fallbackOccurred": false,
"latencyMs": 1240,
"attempts": [
{
"provider": "Agentforce",
"outcome": "skipped_off",
"ms": 0
},
{
"provider": "Gemini",
"outcome": "served",
"ms": 1236
}
],
"promptShown": "Write a cover letter for Ryan Bumstead in a professional tone, no longer than 500 characters."
}
400 Response
{
"httpStatus": 400,
"errorCode": "BAD_REQUEST",
"message": "Invalid persona value.",
"correlationId": "123e4567-e89b-12d3-a456-426614174000",
"retryable": false
}
401 Response
{
"httpStatus": 401,
"errorCode": "UNAUTHORIZED",
"message": "Missing or invalid Bearer token",
"correlationId": "123e4567-e89b-12d3-a456-426614174000",
"retryable": false
}
429 Response
{
"httpStatus": 429,
"errorCode": "RATE_LIMIT_EXCEEDED",
"message": "Rate limit exceeded. Retry after 60 seconds.",
"correlationId": "123e4567-e89b-12d3-a456-426614174000",
"retryable": true
}
500 Response
{
"httpStatus": 500,
"errorCode": "INTERNAL_SERVER_ERROR",
"message": "Failed to aggregate profile data. Partial SAPI failure.",
"correlationId": "123e4567-e89b-12d3-a456-426614174000",
"retryable": true,
"details": {
"failedSapiCalls": [
{
"endpoint": "/experience-skills",
"error": "Connection timeout"
}
]
}
}
503 Response
{
"httpStatus": 503,
"errorCode": "ALL_PROVIDERS_UNAVAILABLE",
"message": "All AI providers currently unavailable",
"correlationId": "123e4567-e89b-12d3-a456-426614174000",
"retryable": true
}
Responses
| Status | Meaning | Description | Schema |
|---|---|---|---|
| 200 | OK | Successfully generated content | Inline |
| 400 | Bad Request | Invalid request parameters | Error |
| 401 | Unauthorized | Missing, expired, or invalid Bearer token (rejected by the platform before Apex executes) | Error |
| 429 | Too Many Requests | Rate limit exceeded (ai tier, 10 requests per minute) | Error |
| 500 | Internal Server Error | Internal PAPI error or SAPI communication failure | Error |
| 503 | Service Unavailable | All AI providers unavailable | Error |
Response Schema
Status Code 200
| Name | Type | Required | Restrictions | Description |
|---|---|---|---|---|
| » content | string | true | none | The generated text content |
| » provider | string | true | none | Provider that served the request |
| » model | string | false | none | Model identifier (local-template when the local template served it) |
| » type | string | false | none | The requested content type, echoed |
| » tone | string | false | none | The applied tone, echoed |
| » generatedAt | string(date-time) | true | none | none |
| » fallbackOccurred | boolean | false | none | True when the local template served the request or an earlier provider failed |
| » latencyMs | integer(int64) | true | none | Time the provider chain took, in milliseconds |
| » attempts | [object] | true | none | Every provider the chain considered, in order, with what happened |
| »» provider | string | true | none | none |
| »» outcome | string | true | none | served wrote the content, skipped_off was switched off or unavailable,skipped_open_circuit was passed over because its circuit breaker is open,budget_exhausted is Agentforce past its hourly budget,failed threw or returned no usable text. |
| »» ms | integer(int64) | true | none | Time spent on this provider, in milliseconds |
| » promptShown | string¦null | true | none | The prompt sent to the provider that served, as built by Apex, including the portfolio facts for a CoverLetter. Null when the local template served, because it uses no prompt. |
Enumerated Values
| Property | Value |
|---|---|
| provider | Agentforce |
| provider | Gemini |
| provider | Local |
| provider | Agentforce |
| provider | Gemini |
| provider | Local |
| outcome | served |
| outcome | skipped_off |
| outcome | skipped_open_circuit |
| outcome | budget_exhausted |
| outcome | failed |
Response Headers
| Status | Header | Type | Format | Description |
|---|---|---|---|---|
| 200 | X-Request-Id | string | uuid | Correlation ID for distributed tracing. A valid caller-supplied value is echoed by the platform; Apex sets the header only when it generated the value. |
| 200 | X-AI-Provider | string | Which provider served the request | |
| 200 | X-Generation-Time-Ms | integer | Time taken to generate content in milliseconds | |
| 200 | Cache-Control | string | none | |
| 400 | X-Request-Id | string | uuid | Correlation ID for distributed tracing. A valid caller-supplied value is echoed by the platform; Apex sets the header only when it generated the value. |
| 401 | X-Request-Id | string | uuid | Correlation ID for distributed tracing. A valid caller-supplied value is echoed by the platform; Apex sets the header only when it generated the value. |
| 429 | X-Request-Id | string | uuid | Correlation ID for distributed tracing. A valid caller-supplied value is echoed by the platform; Apex sets the header only when it generated the value. |
| 429 | Retry-After | integer | none | |
| 500 | X-Request-Id | string | uuid | Correlation ID for distributed tracing. A valid caller-supplied value is echoed by the platform; Apex sets the header only when it generated the value. |
Monitoring
Health checks and observability
getHealth
Code samples
const headers = {
'Accept':'application/json',
'Authorization':'Bearer {access-token}'
};
fetch('https://{domain}/services/apexrest/papi/v1/health',
{
method: 'GET',
headers: headers
})
.then(function(res) {
return res.json();
}).then(function(body) {
console.log(body);
});
GET /health
PAPI Health Check
Deep health check (PAPIHealth.getHealth). Reads SAPI health in-process (SAPIHealth.buildPayload),
exercises the Portfolio_API Named Credential with one GET to /sapi/v1/health (credential is UP
only on HTTP 200), and reports presence of the Jira_API, GitHub_API and Gemini_API Named
Credentials without calling them. status is DOWN when SAPI is down, DEGRADED when the credential is
down or any Named Credential is not CONFIGURED, otherwise UP. Always returns 200.
Example responses
200 Response
{
"status": "UP",
"checkType": "DEEP",
"timestamp": "2019-08-24T14:15:22Z",
"dependencies": {
"sapi": "UP",
"credential": "UP",
"jira": "CONFIGURED",
"github": "CONFIGURED",
"gemini": "CONFIGURED"
},
"details": {
"property1": "string",
"property2": "string"
}
}
Responses
| Status | Meaning | Description | Schema |
|---|---|---|---|
| 200 | OK | Service is healthy | HealthStatus |
Response Headers
| Status | Header | Type | Format | Description |
|---|---|---|---|---|
| 200 | X-Request-Id | string | uuid | Correlation ID for distributed tracing. A valid caller-supplied value is echoed by the platform; Apex sets the header only when it generated the value. |
| 200 | Cache-Control | string | none |
Access
Evaluator token vending (ADR-028)
requestEvaluatorToken
Code samples
const inputBody = '{
"email": "reviewer@example.com",
"purpose": "Evaluating API contract parity"
}';
const headers = {
'Content-Type':'application/json',
'Accept':'application/json',
'X-Request-Id':'497f6eca-6276-4993-bfeb-53cbbbba6f08',
'X-API-Version':'v1'
};
fetch('https://{domain}/services/apexrest/papi/v1/auth/token',
{
method: 'POST',
body: inputBody,
headers: headers
})
.then(function(res) {
return res.json();
}).then(function(body) {
console.log(body);
});
POST /auth/token
Request an evaluator access token
Vends a short-lived OAuth 2.0 Bearer token so external reviewers can call the SAPI and PAPI
without holding the External Client App consumer secret (ADR-028). PAPIAuthToken.requestToken
hands the attempt to TokenExchangeService.requestToken, which validates it, applies the limits,
then runs the client-credentials exchange server-side through the Portfolio_API_Token Named
Credential (TokenExchangeService.exchangeToken). Tokens run as the read-only integration user
and end with its session, after 30 minutes without use. While that session is valid, every
exchange returns the same token, so all evaluators share one token and one rate limit per tier
(SAPI 120, PAPI 15, /ai/* 10 per minute). The response has no expires_in: the token
endpoint reports no expiry, and the session’s end depends on use.
- 400 BAD_REQUEST: the body is not a JSON object of strings;
emailis blank, longer than 254 characters or not an address;purposeis blank or longer than 280 characters; or a header rule fromApiRequestContextfails. - 429 RATE_LIMIT_EXCEEDED with
Retry-After: the PAPI tier limit (15 per minute per caller key), or 15 requests per minute per email and source IP (keypapi-token:{SHA-256 hex of lowercase(email)|sourceIp}). On the email and IP limit,X-RateLimit-LimitandX-RateLimit-Remainingdescribe that limit. - 503 SERVICE_UNAVAILABLE (retryable): the daily cap is reached (
Retry-Afteris the seconds to the next midnight in the running user’s time zone; the cap counts audit rows with outcomeIssuedcreated today, fromPortfolio_Config__mdt.Token_Daily_Cap__c, default 50); the credential is missing or not granted (“Token issuance is not configured.”); or the token endpoint fails (“Token issuance is temporarily unavailable.”). - Every attempt writes one
API_Access_Request__crow. Every response isno-store, no-cache.
This is the only path served on the Experience Cloud site domain, not on My Domain:
POST https://ryan-bumstead-dev-ed.develop.my.site.com/portfoliovforcesite/services/apexrest/papi/v1/auth/token.
Body parameter
{
"email": "reviewer@example.com",
"purpose": "Evaluating API contract parity"
}
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
| X-Request-Id | header | string(uuid) | false | Correlation ID. When missing, or when it is the platform’s own 32-character hex id, a generated UUID is used as the correlation id. A present but non-UUID value returns 400 BAD_REQUEST (ADR-026). |
| X-API-Version | header | string | false | API Version. Missing resolves to v1; any other value returns 400 BAD_REQUEST with message ‘Unsupported X-API-Version’ (ADR-026). |
| body | body | TokenRequest | true | none |
Example responses
200 Response
{
"access_token": "string",
"token_type": "Bearer",
"instance_url": "https://ryan-bumstead-dev-ed.develop.my.salesforce.com",
"docs": "https://rdbumstead.github.io/salesforce-platform-architect-portfolio/api/oas/salesforce-sapi"
}
400 Response
{
"httpStatus": 400,
"errorCode": "BAD_REQUEST",
"message": "Invalid persona value.",
"correlationId": "123e4567-e89b-12d3-a456-426614174000",
"retryable": false
}
429 Response
{
"httpStatus": 429,
"errorCode": "RATE_LIMIT_EXCEEDED",
"message": "Rate limit exceeded. Retry after 42 seconds.",
"correlationId": "123e4567-e89b-12d3-a456-426614174000",
"retryable": true
}
503 Response
{
"httpStatus": 503,
"errorCode": "SERVICE_UNAVAILABLE",
"message": "Downstream SAPI is currently unavailable",
"correlationId": "123e4567-e89b-12d3-a456-426614174000",
"retryable": true
}
Responses
| Status | Meaning | Description | Schema |
|---|---|---|---|
| 200 | OK | Token issued | TokenResponse |
| 400 | Bad Request | Invalid request parameters | Error |
| 429 | Too Many Requests | Rate limit exceeded (ADR-025, ADR-028) | Error |
| 503 | Service Unavailable | PAPI or downstream SAPI is unavailable | Error |
Response Headers
| Status | Header | Type | Format | Description |
|---|---|---|---|---|
| 200 | X-Request-Id | string | uuid | Correlation ID for distributed tracing. A valid caller-supplied value is echoed by the platform; Apex sets the header only when it generated the value. |
| 200 | Cache-Control | string | none | |
| 400 | X-Request-Id | string | uuid | Correlation ID for distributed tracing. A valid caller-supplied value is echoed by the platform; Apex sets the header only when it generated the value. |
| 429 | X-Request-Id | string | uuid | Correlation ID for distributed tracing. A valid caller-supplied value is echoed by the platform; Apex sets the header only when it generated the value. |
| 429 | Retry-After | integer | Seconds left in the current calendar minute, or 1 when the counter row stayed locked past the platform wait. | |
| 503 | X-Request-Id | string | uuid | Correlation ID for distributed tracing. A valid caller-supplied value is echoed by the platform; Apex sets the header only when it generated the value. |
| 503 | Retry-After | integer | Seconds until service may be available |
Schemas
Error
{
"httpStatus": 500,
"errorCode": "INTERNAL_SERVER_ERROR",
"message": "An unexpected error occurred",
"correlationId": "123e4567-e89b-12d3-a456-426614174000",
"retryable": true,
"details": {}
}
Properties
| Name | Type | Required | Restrictions | Description |
|---|---|---|---|---|
| httpStatus | integer | true | none | none |
| errorCode | string | true | none | none |
| message | string | true | none | none |
| correlationId | string(uuid) | true | none | none |
| retryable | boolean | true | none | Indicates if the client should retry the request |
| details | object | false | none | Additional error context (optional) |
HealthStatus
{
"status": "UP",
"checkType": "DEEP",
"timestamp": "2019-08-24T14:15:22Z",
"dependencies": {
"sapi": "UP",
"credential": "UP",
"jira": "CONFIGURED",
"github": "CONFIGURED",
"gemini": "CONFIGURED"
},
"details": {
"property1": "string",
"property2": "string"
}
}
Properties
| Name | Type | Required | Restrictions | Description |
|---|---|---|---|---|
| status | string | false | none | none |
| checkType | string | false | none | none |
| timestamp | string(date-time) | false | none | none |
| dependencies | object | false | none | none |
| » sapi | string | false | none | none |
| » credential | string | false | none | UP only when a GET through the Portfolio_API Named Credential to /sapi/v1/health returns HTTP 200. |
| » jira | string | false | none | Presence of the Jira_API Named Credential; UNKNOWN when NamedCredential cannot be read. |
| » github | string | false | none | Presence of the GitHub_API Named Credential. |
| » gemini | string | false | none | Presence of the Gemini_API Named Credential. |
| details | object | false | none | Diagnostic per dependency, e.g. credential: “HTTP 401” or an exception type name. |
| » additionalProperties | string | false | none | none |
Enumerated Values
| Property | Value |
|---|---|
| status | UP |
| status | DOWN |
| status | DEGRADED |
| checkType | SHALLOW |
| checkType | DEEP |
| sapi | UP |
| sapi | DOWN |
| credential | UP |
| credential | DOWN |
| jira | CONFIGURED |
| jira | MISSING |
| jira | UNKNOWN |
| github | CONFIGURED |
| github | MISSING |
| github | UNKNOWN |
| gemini | CONFIGURED |
| gemini | MISSING |
| gemini | UNKNOWN |
FullPortfolioPayload
{
"config": {
"linkedInUrl": "https://linkedin.com/in/ryanbumstead",
"calendlyUrl": "https://example.com/schedule",
"githubUrl": "https://example.com/source",
"trailheadUrl": "https://example.com/trailhead",
"personalWebsiteUrl": "https://ryanbumstead.com",
"careerObjective": "Driving digital transformation through architecture.",
"jiraProjectKey": "SPAP",
"gitHubRepoPath": "rdbumstead/salesforce-portfolio"
},
"contactInfo": {
"id": "0035e00000B2O3DAAV",
"key": "con_7c5j3w9sfa",
"fullName": "Ryan Bumstead",
"title": "Platform Architect",
"linkedIn": "https://linkedin.com/in/ryanbumstead",
"github": "https://example.com/source",
"trailhead": "https://example.com/trailhead",
"portfolioUrl": "https://ryanbumstead.com",
"bio": "Specializing in API-led connectivity..."
},
"summary": "Salesforce Platform Architect with nearly a decade on the platform, focused on higher education",
"skills": [
{
"id": "a075e00000B2O3DAAV",
"key": "skl_3m9x5b2rkt",
"name": "Apex",
"displayName": "Salesforce Apex",
"category": "Backend",
"proficiency": 5,
"iconName": "utility:code",
"colorHex": "#0070d2",
"categoryOrder": 10
}
],
"experience": [
{
"id": "a025e00000B2O3DAAV",
"key": "exp_4h7n2c8wpd",
"employer": "Salesforce",
"role": "Senior Technical Architect",
"dateRange": "Jan 2023 - Present",
"isCurrent": true,
"isRemote": true,
"skillsUsed": [
"LWC"
],
"highlights": [
"Implemented CI/CD pipelines using GitHub Actions"
]
}
],
"projects": [
{
"id": "a015e00000B2O3DAAV",
"key": "prj_8f3k2m9qtz",
"title": "Portfolio Site",
"challenge": "Display skills dynamically to recruiters",
"solution": "Built on LWR with SAPI/PAPI architecture",
"businessValue": "Increased interview rate by 40%",
"status": "Live – In Production",
"completionDate": "2025-12-01",
"heroImageUrl": "https://example.com/images/hero.png",
"liveUrl": "https://example.com/portfolio",
"repositoryUrl": "https://example.com/source",
"technologies": [
"Apex"
],
"pillarLabel": "Pillar A: Business Architecture",
"assets": [
{
"type": "Image",
"url": "https://example.com/images/diagram.png",
"caption": "System Architecture Diagram"
}
]
}
],
"certifications": [
{
"id": "a105e00000B2O3DAAV",
"key": "crt_6q2v8d4nhz",
"name": "Certified Application Architect",
"issuer": "Salesforce",
"dateEarned": "2024-05-15",
"skillsUsed": [
"Security"
]
}
],
"education": [
{
"id": "a115e00000B2O3DAAV",
"key": "edu_6b4c8j2rvq",
"school": "Creighton University",
"degree": "B.S. Computer Science",
"fieldOfStudy": "Computer Science",
"year": "2018",
"gpa": 3.8
}
],
"testimonials": [
{
"id": "a055e00000B2O3DAAV",
"key": "tst_2y7a5d9wmh",
"author": "Jane Doe",
"title": "VP of Engineering",
"relationship": "Manager",
"quote": "Ryan is a beast at architecture",
"vibe": "Professional",
"avatarUrl": "https://example.com/images/avatar.jpg"
}
],
"integrityNote": "complete"
}
The master payload for initial app load
Properties
| Name | Type | Required | Restrictions | Description |
|---|---|---|---|---|
| config | PortfolioConfig | true | none | Public configuration. The owner’s email and phone are never returned. |
| contactInfo | ContactDetail | true | none | Simplified contact information for the header. No email or phone is returned. |
| summary | string | false | none | Auto-generated professional summary |
| skills | [SkillSummary] | true | none | [Lightweight skill object for list views] |
| experience | [EnrichedExperience] | true | none | [Work Experience enriched with related Skills and Highlights] |
| projects | [EnrichedProject] | true | none | [Project enriched with Skills and Assets] |
| certifications | [EnrichedCertification] | true | none | [Certification with associated skills] |
| education | [SimpleEducation] | true | none | [Educational background] |
| testimonials | [EnrichedTestimonial] | true | none | none |
| integrityNote | string | false | none | “complete”, or “partial: |
ProfileSummary
{
"contactInfo": {
"id": "0035e00000B2O3DAAV",
"key": "con_7c5j3w9sfa",
"fullName": "Ryan Bumstead",
"title": "Platform Architect",
"linkedIn": "https://linkedin.com/in/ryanbumstead",
"github": "https://example.com/source",
"trailhead": "https://example.com/trailhead",
"portfolioUrl": "https://ryanbumstead.com",
"bio": "Specializing in API-led connectivity..."
},
"summary": "Salesforce Platform Architect with nearly a decade on the platform, focused on higher education",
"skills": [
{
"id": "a075e00000B2O3DAAV",
"key": "skl_3m9x5b2rkt",
"name": "Apex",
"displayName": "Salesforce Apex",
"category": "Backend",
"proficiency": 5,
"iconName": "utility:code",
"colorHex": "#0070d2",
"categoryOrder": 10
}
],
"experience": [
{
"id": "a025e00000B2O3DAAV",
"key": "exp_4h7n2c8wpd",
"employer": "Salesforce",
"role": "Senior Technical Architect",
"dateRange": "Jan 2023 - Present",
"isCurrent": true,
"isRemote": true,
"skillsUsed": [
"LWC"
],
"highlights": [
"Implemented CI/CD pipelines using GitHub Actions"
]
}
],
"certifications": [
{
"id": "a105e00000B2O3DAAV",
"key": "crt_6q2v8d4nhz",
"name": "Certified Application Architect",
"issuer": "Salesforce",
"dateEarned": "2024-05-15",
"skillsUsed": [
"Security"
]
}
],
"education": [
{
"id": "a115e00000B2O3DAAV",
"key": "edu_6b4c8j2rvq",
"school": "Creighton University",
"degree": "B.S. Computer Science",
"fieldOfStudy": "Computer Science",
"year": "2018",
"gpa": 3.8
}
],
"integrityNote": "complete"
}
Properties
| Name | Type | Required | Restrictions | Description |
|---|---|---|---|---|
| contactInfo | ContactDetail | true | none | Simplified contact information for the header. No email or phone is returned. |
| summary | string | false | none | Calculated summary based on experience |
| skills | [SkillSummary] | true | none | [Lightweight skill object for list views] |
| experience | [EnrichedExperience] | true | none | [Work Experience enriched with related Skills and Highlights] |
| certifications | [EnrichedCertification] | true | none | [Certification with associated skills] |
| education | [SimpleEducation] | true | none | [Educational background] |
| integrityNote | string | false | none | “complete”, or “partial: |
EnrichedExperience
{
"id": "a025e00000B2O3DAAV",
"key": "exp_4h7n2c8wpd",
"employer": "Salesforce",
"role": "Senior Technical Architect",
"dateRange": "Jan 2023 - Present",
"isCurrent": true,
"isRemote": true,
"skillsUsed": [
"LWC"
],
"highlights": [
"Implemented CI/CD pipelines using GitHub Actions"
]
}
Work Experience enriched with related Skills and Highlights
Properties
| Name | Type | Required | Restrictions | Description |
|---|---|---|---|---|
| id | string | true | none | Salesforce Record ID (18-char). Deprecated, use key. |
| key | string | false | read-only | Stable public key of the record. Use it in place of id. It is set once and never changes. |
| employer | string | true | none | none |
| role | string¦null | false | none | none |
| dateRange | string | true | none | Formatted as “MMM YYYY - MMM YYYY” or “MMM YYYY - Present” |
| isCurrent | boolean¦null | false | none | none |
| isRemote | boolean¦null | false | none | none |
| skillsUsed | [string] | false | none | Skills derived from ExperienceSkill junction |
| highlights | [string] | false | none | Selected bullet points (filtered by persona if applicable) |
EnrichedProject
{
"id": "a015e00000B2O3DAAV",
"key": "prj_8f3k2m9qtz",
"title": "Portfolio Site",
"challenge": "Display skills dynamically to recruiters",
"solution": "Built on LWR with SAPI/PAPI architecture",
"businessValue": "Increased interview rate by 40%",
"status": "Live – In Production",
"completionDate": "2025-12-01",
"heroImageUrl": "https://example.com/images/hero.png",
"liveUrl": "https://example.com/portfolio",
"repositoryUrl": "https://example.com/source",
"technologies": [
"Apex"
],
"pillarLabel": "Pillar A: Business Architecture",
"assets": [
{
"type": "Image",
"url": "https://example.com/images/diagram.png",
"caption": "System Architecture Diagram"
}
]
}
Project enriched with Skills and Assets
Properties
| Name | Type | Required | Restrictions | Description |
|---|---|---|---|---|
| id | string | true | none | Deprecated, use key. |
| key | string | false | read-only | Stable public key of the record. Use it in place of id. It is set once and never changes. |
| title | string¦null | false | none | none |
| challenge | string | false | none | none |
| solution | string | false | none | none |
| businessValue | string | false | none | none |
| status | string¦null | false | none | Project Status (e.g., Live, Archived) |
| completionDate | string(date)¦null | false | none | Mapped from SAPI dateCompleted |
| heroImageUrl | string(uri) | false | none | URL of the main display image. Matches SAPI field name. |
| liveUrl | string(uri) | false | none | none |
| repositoryUrl | string(uri) | false | none | none |
| technologies | [string] | false | none | Skill names from ProjectSkill junction |
| pillarLabel | string¦null | false | none | The strategic pillar and pillar description (e.g. Pillar A: Business Architecture) |
| assets | [object] | false | none | Images, videos, or links related to the project |
| » type | string | true | none | none |
| » url | string(uri) | true | none | none |
| » caption | string | false | none | none |
Enumerated Values
| Property | Value |
|---|---|
| type | Image |
| type | Video |
| type | Document |
| type | Link |
EnrichedTestimonial
{
"id": "a055e00000B2O3DAAV",
"key": "tst_2y7a5d9wmh",
"author": "Jane Doe",
"title": "VP of Engineering",
"relationship": "Manager",
"quote": "Ryan is a beast at architecture",
"vibe": "Professional",
"avatarUrl": "https://example.com/images/avatar.jpg"
}
Properties
| Name | Type | Required | Restrictions | Description |
|---|---|---|---|---|
| id | string | true | none | Deprecated, use key. |
| key | string | false | read-only | Stable public key of the record. Use it in place of id. It is set once and never changes. |
| author | string | true | none | none |
| title | string | false | none | none |
| relationship | string | false | none | none |
| quote | string | true | none | none |
| vibe | string | false | none | none |
| avatarUrl | string(uri) | false | none | none |
Enumerated Values
| Property | Value |
|---|---|
| relationship | Manager |
| relationship | Peer |
| relationship | Client |
| relationship | Recruiter |
| relationship | Fan |
| vibe | Professional |
| vibe | Casual |
PortfolioConfig
{
"linkedInUrl": "https://linkedin.com/in/ryanbumstead",
"calendlyUrl": "https://example.com/schedule",
"githubUrl": "https://example.com/source",
"trailheadUrl": "https://example.com/trailhead",
"personalWebsiteUrl": "https://ryanbumstead.com",
"careerObjective": "Driving digital transformation through architecture.",
"jiraProjectKey": "SPAP",
"gitHubRepoPath": "rdbumstead/salesforce-portfolio"
}
Public configuration. The owner’s email and phone are never returned.
Properties
| Name | Type | Required | Restrictions | Description |
|---|---|---|---|---|
| linkedInUrl | string(uri) | false | none | none |
| calendlyUrl | string(uri) | false | none | none |
| githubUrl | string(uri) | false | none | none |
| trailheadUrl | string(uri) | false | none | none |
| personalWebsiteUrl | string(uri) | false | none | none |
| careerObjective | string | false | none | Short professional bio/objective |
| jiraProjectKey | string | false | none | Key for Jira integration |
| gitHubRepoPath | string | false | none | GitHub Owner/Repo path |
ContactDetail
{
"id": "0035e00000B2O3DAAV",
"key": "con_7c5j3w9sfa",
"fullName": "Ryan Bumstead",
"title": "Platform Architect",
"linkedIn": "https://linkedin.com/in/ryanbumstead",
"github": "https://example.com/source",
"trailhead": "https://example.com/trailhead",
"portfolioUrl": "https://ryanbumstead.com",
"bio": "Specializing in API-led connectivity..."
}
Simplified contact information for the header. No email or phone is returned.
Properties
| Name | Type | Required | Restrictions | Description |
|---|---|---|---|---|
| id | string | false | none | Deprecated, use key. |
| key | string | false | read-only | Stable public key of the record. Use it in place of id. It is set once and never changes. |
| fullName | string | true | none | none |
| title | string¦null | false | none | none |
| string(uri) | false | none | none | |
| github | string(uri) | false | none | none |
| trailhead | string(uri) | false | none | none |
| portfolioUrl | string(uri) | false | none | none |
| bio | string | false | none | The ‘Career Objective’ from Salesforce |
SkillSummary
{
"id": "a075e00000B2O3DAAV",
"key": "skl_3m9x5b2rkt",
"name": "Apex",
"displayName": "Salesforce Apex",
"category": "Backend",
"proficiency": 5,
"iconName": "utility:code",
"colorHex": "#0070d2",
"categoryOrder": 10
}
Lightweight skill object for list views
Properties
| Name | Type | Required | Restrictions | Description |
|---|---|---|---|---|
| id | string | true | none | Deprecated, use key. |
| key | string | false | read-only | Stable public key of the record. Use it in place of id. It is set once and never changes. |
| name | string | true | none | none |
| displayName | string | false | none | Prefer this over ‘name’ for UI rendering if present |
| category | string | true | none | none |
| proficiency | number | true | none | 1-5 score |
| iconName | string | false | none | SLDS Icon reference |
| colorHex | string | false | none | Brand color for the category |
| categoryOrder | number | false | none | Order to display the category in the UI |
EnrichedCertification
{
"id": "a105e00000B2O3DAAV",
"key": "crt_6q2v8d4nhz",
"name": "Certified Application Architect",
"issuer": "Salesforce",
"dateEarned": "2024-05-15",
"skillsUsed": [
"Security"
]
}
Certification with associated skills
Properties
| Name | Type | Required | Restrictions | Description |
|---|---|---|---|---|
| id | string | true | none | Deprecated, use key. |
| key | string | false | read-only | Stable public key of the record. Use it in place of id. It is set once and never changes. |
| name | string | true | none | none |
| issuer | string | true | none | none |
| dateEarned | string(date) | true | none | none |
| skillsUsed | [string] | false | none | Derived from CertificationSkill junction |
SimpleEducation
{
"id": "a115e00000B2O3DAAV",
"key": "edu_6b4c8j2rvq",
"school": "Creighton University",
"degree": "B.S. Computer Science",
"fieldOfStudy": "Computer Science",
"year": "2018",
"gpa": 3.8
}
Educational background
Properties
| Name | Type | Required | Restrictions | Description |
|---|---|---|---|---|
| id | string | true | none | Deprecated, use key. |
| key | string | false | read-only | Stable public key of the record. Use it in place of id. It is set once and never changes. |
| school | string | true | none | none |
| degree | string | true | none | none |
| fieldOfStudy | string | false | none | none |
| year | string | true | none | Graduation Year derived from Graduation Date |
| gpa | number¦null | false | none | none |
TokenRequest
{
"email": "reviewer@example.com",
"purpose": "Evaluating API contract parity"
}
Properties
| Name | Type | Required | Restrictions | Description |
|---|---|---|---|---|
| string(email) | true | none | none | |
| purpose | string | true | none | Free-text reason, stored on the audit record. |
TokenResponse
{
"access_token": "string",
"token_type": "Bearer",
"instance_url": "https://ryan-bumstead-dev-ed.develop.my.salesforce.com",
"docs": "https://rdbumstead.github.io/salesforce-platform-architect-portfolio/api/oas/salesforce-sapi"
}
Properties
| Name | Type | Required | Restrictions | Description |
|---|---|---|---|---|
| access_token | string | true | none | OAuth 2.0 Bearer token bound to the read-only integration user. It is the integration user’s current session, shared by every evaluator, and it ends after 30 minutes without use (the integration profile’s idle timeout). The response carries no expires_in, because the token endpoint reports no expiry and a fixed number would be a guess. |
| token_type | string | true | none | none |
| instance_url | string(uri) | true | none | The My Domain root, as returned by the token endpoint. Append /services/apexrest/sapi/v1/... or /services/apexrest/papi/v1/... and send the token as a Bearer credential. |
| docs | string(uri) | false | none | Link to the rendered API documentation. |
Enumerated Values
| Property | Value |
|---|---|
| token_type | Bearer |