Skip to main content
ShiftUp already offers out-of-the-box context providers for common standard objects related to Accounts, custom objects directly related to Accounts, Pricebooks, and other top-level objects used as catalogs. Some scenarios need more control over the data retrieved or shared with ShiftUp. For these advanced use cases, custom Apex context providers let you apply business logic, shape the results, and connect to external services. This guide assumes you know Apex, SOQL, and Salesforce deployment.

When to use a custom provider

Use custom Apex when your requirements go beyond the built-in providers’ configuration options:

How it works

Shift Up AI Engine Data 2026 10 13 042611

1. Understand the contract

ShiftUp provides the tvggs.GSAiContextProvider interface to discover custom providers and understand what they supply. The interface defines the provider’s identity, the label and description shown in the UI, and the behavior for fetching data to send to the AI engine. Its description also guides the ShiftUp AI agent on how to interpret and use that data. Implement tvggs.GSAiContextProvider in a global class with a no-argument constructor. A provider runs when it is available (via theisAvailable method), enabled on the requested seller profile, and active for the request (via the isActive method).
MethodPurpose
getProviderName()Stable, org-unique identifier used in saved configuration and request filters.
getLabel()Display name for admins.
getDescription()Describes the data, scope, and limits, and gives the ShiftUp AI agent instructions on how to interpret and use the data.
isAvailable()Controls whether the provider is available in the org. Use it for a controlled rollout, or return true when no availability restriction is needed.
isActive(sellerProfileInfo, request)Whether it applies to this request; ShiftUp already checks seller profile enablement.
getContext(sellerProfileInfo, request, response)Retrieve data and populate the response supplied by ShiftUp.
ShiftUp passes sellerProfileInfo and request to both isActive() and getContext(). It also supplies response to getContext() for your provider to populate.

sellerProfileInfo: the seller profile configuration

Type: tvggs.GSAiContextInfo.SellerProfileInfo. Provides the identity and saved configuration of the seller profile requesting context. Treat this information as read-only input. Example of saved settings your provider might receive from getConfigOptions(), shown as JSON. Object names and IDs are illustrative:
Here, enabledProviderNames lists the providers already enabled on the seller profilepricebookIds lists its selected pricebooks, and dynamicCtxProviderConfig reflects its saved Account Custom Objects settings. Do not modify this map to change settings; configuration changes belong in the seller profile’s settings and its only passed along for informational purposes.

request: the context being requested

Type: tvggs.GSAiContextInfo.GSAiContextInfoRequest. Its properties are read-only inputs to your provider.

response: the context your provider returns

Type: tvggs.ProviderRecordContextResponse. ShiftUp creates this output container with your provider’s name and description. Populate it with selected data or report a failure; ShiftUp handles returning it to the AI engine. A tvggs.RecordContextResult groups context for one requested account plan ID. It contains named lists of Salesforce sObjects (contextRecords), custom data values (contextData), or both. Custom data values use tvggs.RecordContextResult.RecordValue and tvggs.RecordContextResult.FieldValue to represent calculated metrics or facts from external systems. Below is an example of what the context provider response for Account subscription data will look like, the first account plan has two subscriptions and the second has one. All three reference the same product package. Each addResult() supplies one account plan’s subscriptions; addReferences('ProductPackages', packages) supplies the shared package details once. Custom objects and IDs are illustrative; SObject serialization metadata is omitted:
Each subscription’s Product_Package__c matches the reference record’s Id. Custom data values can also carry that package ID, so the same supporting record can serve multiple context records. Using references can help reduce the size of the response payload. References contain sObjects; your provider must query and supply them. Collect and deduplicate each named list before calling addReferences(); another call with the same name replaces the list.

2. Implement a provider

Choose the example that matches where your data lives: Salesforce objects or an external API.

Example 1: Subscriptions stored in Salesforce

This example returns selected fields from up to 20 subscriptions per Account, most recently modified first, and includes each referenced product package once. Explicit field selection keeps other subscription details out of this provider’s response. The sample assumes this custom schema. Adapt the API names to your org before compiling: Here, request.recordIds contains tvggs__AI_Account_Plan__c IDs. Resolve each plan’s tvggs__Account__c to query its subscriptions, then associate each result with the original plan ID. These custom objects and fields belong to your org, so they do not use ShiftUp’s tvggs__ prefix. Create AcmeSubscriptionContextProvider.cls, replacing Acme with your company’s prefix before deployment:
Key implementation details:
  • Scope: Retrieves up to 20 subscriptions per Account, ordered by most recently modified. Only Accounts directly associated with the requested plans are queried.
  • Shared references: A Set<Id> collects unique product package IDs across subscriptions. A single bulk query retrieves the packages, and addReferences() makes them available across all results without duplication.
  • Security: Context providers execute using the ShiftUp Integration User. WITH USER_MODE enforces that user’s Salesforce sharing, object permissions, and field-level security. See Salesforce’s Apex security guidance.
  • Results: Accounts with no visible subscriptions return an empty list. Inaccessible Account Plans and Accounts are skipped, while inaccessible product package records are omitted from references. Missing object or field permissions cause the provider to return an error.
  • Data selection: Only the selected fields are included. Other enabled context providers may independently expose additional fields from the same records.
  • Package boundary: Use the supplied response object. RecordContextResult exposes global constructors, but ProviderRecordContextResponse and SellerProfileInfo cannot be instantiated directly from subscriber Apex.
RecordContextResult supports two data formats: Map<String, List<SObject>> for Salesforce records and Map<String, List<tvggs.RecordContextResult.RecordValue>> for calculated or external data. The next example demonstrates the latter.

Example 2: Subscriptions fetched from a bulk external API

If subscriptions and product packages live in an external billing service, use this provider instead of the Salesforce subscription example. It resolves each account plan to its Account, fetches subscriptions for all those Accounts in one bulk API call through Salesforce, and returns selected fields as contextData.

Configure the connection and Account mapping

We recommend Salesforce’s recommendations on external callouts from Apex through Named credentials is followed.
  1. Create an External Credential for the service. For an API key, use a principal authentication parameter referenced by a custom header such as X-API-Key. See Salesforce’s API-key header examples.
  2. Link it to a Named Credential named Acme_Subscriptions_API with the service’s HTTPS base URL. Grant External Credential Principal Access to the user executing context retrieval and complete authentication. See Salesforce’s credential setup guide.
  3. Create or substitute an Account text field, Subscription_Account_Id__c, marked as an External ID. Populate it with the billing service’s account identifier and grant the executing user read access. The sample skips Accounts without this value.
Salesforce supplies the credential when Apex uses the callout: endpoint. Your API key stays in Salesforce’s credential configuration; ShiftUp receives the selected subscription data. See Named Credential callouts.

Example API contract

Assume the service provides a read-only bulk lookup endpoint. These endpoint and field names are illustrative; adapt them to your service before deployment:
The request contains only an ids list, populated from Account.Subscription_Account_Id__c:
A successful response contains the matching external accounts, with up to 20 subscriptions each, most recently modified first. Each entry’s id is the external account ID; response order does not matter. The provider skips account plans whose external ID is absent from the response. A matched account with no subscriptions returns an empty array:

Complete provider

Create AcmeExternalSubscriptionContextProvider.cls. This example accepts distinct plan IDs, collects their Accounts’ external IDs, and sends them in one bulk call. It maps the returned subscriptions back to each account plan.
  • Output: returns subscriptions and package details in each plan’s contextData. Plans with missing or unmatched external IDs are skipped.
  • Mapping and access: matches service IDs to Subscription_Account_Id__c. Queries enforce Salesforce access; configure external permissions in the service.
  • Limits and failures: one bulk call fetches up to 20 subscriptions per external account. Accounts with no subscriptions return an empty list; failed calls report a provider error.

3. Enable and verify

  1. Deploy your class, class metadata, and tests to a sandbox.
  2. Confirm Enable Context Providers is on in ShiftUp’s app settings. Edit the seller profile’s Context Providers tab, enable Account Subscriptions or External Account Subscriptions, and save.
  3. In Application Setup → Context Console, select the seller profile, provider, and account plan, then inspect the result. The console requires CRM context availability, the ShiftUp admin custom permission (AI_Guided_Selling_Admin_User), and View All Data. Validate runtime access under the actual integration user too.
You can also POST this JSON to /services/apexrest/tvggs/context/info using an authenticated Salesforce REST client and Content-Type: application/json. Replace the placeholder IDs:
For this test request, ShiftUp handles provider selection through options.providers; your custom provider does not need to read it. The value contains provider identifiers, not display labels, and only narrows the seller profile’s enabled providers. Check each provider’s success, errorMessage, and result record IDs even when the outer request succeeds. To test the external example, change options.providers to AcmeExternalSubscriptions and use a plan whose Account has Subscription_Account_Id__c populated with a billing account ID.

Best practices and testing

AreaPractice
Stable contractKeep getProviderName() and output keys stable. Keep constructors and availability checks lightweight. Avoid a Test prefix for production provider classes.
Read-only behaviorContext retrieval is read-only: avoid Salesforce DML, including DML-based logging, and use read-only operations when calling external services.
Resource budgetsBulk query, cap records and text, and measure CPU, heap, queries, rows, and callout time with all relevant providers enabled. Governor-limit failures cannot be recovered by a broad exception handler.
Data selectionReturn only relevant, permitted fields. Keep secrets and authentication headers out of code, context responses, and logs. Bind query values and keep external endpoints under your control.
Meaning and failuresInclude source IDs, timestamps, units, and calculation definitions. A limited list is not a total. Report unavailable data as an error rather than a zero metric; avoid retry loops during context assembly.
Account hierarchyCustom code must implement any hierarchy expansion explicitly and honor the org’s Disable account hierarchy support policy.
Test multiple Accounts sharing a product package, deduplicated references, restricted permissions, empty/invalid IDs, and volume limits. For the external provider, use HttpCalloutMock to cover multiple external IDs in one call, deduplicated IDs, missing external IDs, partial or empty results, reordered results, empty subscriptions, unexpected IDs, failed calls, timeouts, and malformed or oversized responses. Verify real credential permissions separately in a sandbox. For isolated tests, typed JSON.deserialize() can create fixtures for the package wrappers whose constructors are not global. Also verify the enabled provider through the context endpoint. If a provider is missing, check its interface, constructor, and isAvailable(); if it does not run, check profile enablement, its identifier, and isActive().