# Apex Actions

This guide documents the Apex Actions (Invocable Methods) provided by the Fours Salesforce App. These actions can be used in Salesforce Flows, Process Builder, and other automation tools to integrate co-sell workflows.

## Overview

Fours provides six Apex Actions for co-sell automation:

| Action | Description | Supported Partners |
| ------ | ----------- | ------------------ |
| Check If Opportunity Shared | Check if an opportunity has been shared as a referral | AWS, Azure, GCP |
| Co-Sell | Share an opportunity with a cloud partner (waits for completion, backfills to Salesforce) | AWS, Azure, GCP |
| Co-Sell (Fire and Forget) | Share an opportunity without waiting for completion | AWS, Azure, GCP |
| Process Inbound Co-Sell Referral | Accept or decline an inbound referral (Azure referral or AWS engagement invitation) | Azure, AWS |
| Update Referral Linkage | Link a referral to an opportunity and/or an account | AWS, Azure, GCP, Fours |
| Update AWS Referral | Update business fields on an AWS co-sell referral | AWS only |

### How an action reaches the cloud partner

Every action runs the same three-system path: your Flow calls an Apex method in the managed package, the package calls Fours over HTTP, and Fours talks to the cloud partner. Where the actions differ is what happens *after* Fours accepts the request — whether the Flow waits for the partner, and whether a Salesforce record comes back in the same run.

```d2
direction: down

group: "Your Salesforce org" {
  flow: "Flow / Process Builder\nAction element"
  apex: "Fours Apex invocable method"
  perm: "Permission check\nWrite_AWS_Referral /\nWrite_Azure_Referral /\nWrite_GCP_Referral" { shape: diamond }
  record: "Referral__c record\nlinked to the Opportunity"

  flow -> apex: "inputs"
  apex -> perm
}

denied: "Success = false\nMessage explains why"
suger: "Fours\ncreate / update workflow"
partner: "Cloud partner\nAWS Partner Central (ACE)\nMicrosoft Partner Center\nGoogle Cloud Partner Network"
wait: "Does this action wait?" { shape: diamond }
ff: "No Salesforce record.\nLook the referral up in Fours\nby the returned Referral ID"

group.perm -> denied: "no permission,\nor a value failed validation"
group.perm -> suger: "HTTPS callout"
suger -> partner: "submits the referral"
suger -> wait
wait -> group.record: "Co-Sell — polls ~12s,\nthen backfills"
wait -> ff: "Co-Sell (Fire and Forget) —\nreturns immediately"
partner -> group.record: "later inbound sync\nfills in status and scores"
```

:::note
Because every action performs an HTTP callout, place it on the **Run Asynchronously** path of a record-triggered Flow. `Success = true` means Fours accepted the request — for the fire-and-forget and AWS-accept paths it does **not** mean the partner has finished processing it.
:::

---

## Check If Opportunity Shared

**API Name:** `CosellCheckSharedOppApexAction`

**Label:** Check If Opportunity Shared

**Description:** Check if an opportunity has already been shared as a co-sell referral with a specific partner or any partner.

### Supported Partners

AWS, Azure, GCP, or ANY (checks all partners)

### Inputs

| Parameter | Type | Required | Description |
| --------- | ---- | :------: | ----------- |
| Opportunity ID | Id | Yes | Salesforce Opportunity ID to check |
| Partner | String | No | Partner to check: `AWS`, `AZURE`, `GCP`, or `ANY`. Defaults to `ANY` if not specified |

### Outputs

| Parameter | Type | Description |
| --------- | ---- | ----------- |
| Success | Boolean | Whether the operation completed successfully |
| Has Been Shared | Boolean | Whether the opportunity has been shared with the specified partner |
| Message | String | Descriptive message about the result |

### Example Use Cases

- Prevent duplicate referral submissions by checking if an opportunity was already shared
- Conditionally show/hide co-sell buttons based on sharing status
- Validate before triggering co-sell automation

---

## Co-Sell (Share Opportunity)

**API Name:** `CosellShareReferralAsyncApexAction`

**Label:** Co-Sell

**Description:** Share an opportunity with a co-sell partner. This action creates a new referral in the cloud partner's system and backfills the referral record to Salesforce.

### Supported Partners

AWS, Azure, GCP

### Inputs

| Parameter | Type | Required | Description |
| --------- | ---- | :------: | ----------- |
| Partner | String | Yes | Cloud partner: `AWS`, `AZURE`, or `GCP` |
| Opportunity ID | Id | No | Salesforce Opportunity ID to share. Required unless **Referral Payload (JSON)** is provided |
| Referral Payload (JSON) | String | No | A complete referral payload as a JSON string (the same envelope used by the [Create a Referral API](/cosell/cosell-referral-api/)). When provided, the field-mapping preview is skipped and this payload is submitted as-is |

### Outputs

| Parameter | Type | Description |
| --------- | ---- | ----------- |
| Success | Boolean | Whether the referral was created successfully |
| Message | String | Error message if the operation failed |
| Referral ID | Id | Salesforce Referral record ID (if successful) |
| External Referral ID | String | Fours Referral ID (returned even on partial failure) |

### Behavior

1. Validates user has write permission for the specified partner
2. Builds the referral payload — from the supplied **Referral Payload (JSON)** if provided, otherwise from the opportunity via field mapping
3. Creates the referral in the cloud partner's system via Fours
4. Polls for workflow completion (up to ~12 seconds)
5. Backfills the referral record to Salesforce

:::note
Provide **either** an Opportunity ID or a Referral Payload (JSON). If both are set, the payload is submitted and the Opportunity ID is used only to link the referral to that CRM record. This action performs HTTP callouts and may take several seconds to complete, so on a record-triggered Flow place it on the **Run Asynchronously** path. The external referral ID is returned even if later steps fail, allowing for manual recovery.
:::

---

## Co-Sell (Fire and Forget)

**API Name:** `CosellShareReferralAction`

**Label:** Co-Sell (Fire and Forget)

**Description:** Share an opportunity with a co-sell partner **without waiting** for the create to finish. The action returns as soon as the create workflow starts — it does not poll for completion or backfill a Salesforce `Referral__c` record. Use it when you don't need the result in the same Flow run.

### Supported Partners

AWS, Azure, GCP

### Inputs

| Parameter | Type | Required | Description |
| --------- | ---- | :------: | ----------- |
| Partner | String | Yes | Cloud partner: `AWS`, `AZURE`, or `GCP` |
| Opportunity ID | Id | No | Salesforce Opportunity ID to share. Required unless **Referral Payload (JSON)** is provided |
| Referral Payload (JSON) | String | No | A complete referral payload as a JSON string (the same envelope used by the [Create a Referral API](/cosell/cosell-referral-api/)). When provided, the field-mapping preview is skipped and this payload is submitted as-is |

### Outputs

| Parameter | Type | Description |
| --------- | ---- | ----------- |
| Success | Boolean | Whether the create workflow started successfully |
| Message | String | Error message if the operation failed |
| Referral ID | String | Fours Referral ID — the external referral ID (no Salesforce record is created) |
| Partner | String | The partner the referral was shared with |

### Behavior

1. Validates user has write permission for the specified partner
2. Builds the referral payload — from the supplied **Referral Payload (JSON)** if provided, otherwise from the opportunity via field mapping
3. Creates the referral in the cloud partner's system via Fours and returns immediately

:::note
Provide **either** an Opportunity ID or a Referral Payload (JSON) (same precedence as **Co-Sell**). Unlike **Co-Sell**, this action does not wait for the partner or backfill a Salesforce record — `Success = true` means the create workflow was submitted, not that the partner accepted it. Look the referral up in Fours by the returned Referral ID to confirm the final status. On a record-triggered Flow, place it on the **Run Asynchronously** path (it performs an HTTP callout).
:::

---

## Process Inbound Co-Sell Referral

**API Name:** `CosellProcessInboundReferralApexAction`

**Label:** Process Inbound Co-Sell Referral

**Description:** Accept or decline an inbound co-sell referral from a cloud partner.

### Supported Partners

**Azure and AWS.** For AWS the action processes a pending **engagement invitation** — the inbound form an AWS-originated referral arrives in. GCP inbound referrals must still be processed through the partner portal or the Fours UI.

### Inputs

| Parameter | Type | Required | Description |
| --------- | ---- | :------: | ----------- |
| Partner | String | Yes | `AZURE` or `AWS` |
| Referral ID | String | Yes | Fours Referral ID (not the Salesforce record ID) |
| Decision | String | Yes | `ACCEPT` or `DECLINE` |
| Reason | String | No | Reason for declining (only applicable when declining). For AWS this is passed to Partner Central, which requires a single line of at most 80 characters |

### Outputs

| Parameter | Type | Description |
| --------- | ---- | ----------- |
| Success | Boolean | Whether the operation completed successfully |
| Message | String | Error message if the operation failed |

### Behavior

**Azure — when accepting:**
- Updates the referral status to `Active` with substatus `Accepted`
- Invokes the pre-acceptance webhook (if configured)

**Azure — when declining:**
- Updates the referral status to `Closed` with substatus `Declined`
- Sets the status reason with the provided decline reason

**AWS — when accepting:**
- Accepts the engagement invitation in AWS Partner Central (ACE)
- Returns as soon as AWS takes the request. AWS creates the opportunity asynchronously, so the referral's status and opportunity fields are filled in shortly afterwards by the inbound sync, not by this action

**AWS — when declining:**
- Rejects the engagement invitation in AWS Partner Central and sets the referral to `REJECTED`
- The decline reason is recorded on the Fours referral and sent on to AWS Partner Central. When no reason is given, AWS is sent `Other`
- If auto-delete is enabled for the org, the linked CRM records are removed

**AWS — when AWS refuses the accept or decline:**
- The action returns `Success = false` with the error in `Message`
- The failure is also recorded on the referral: the invitation's status badge reads **Update Failed:** followed by the reason until a later accept or decline succeeds. The referral's own status is not changed

:::warning
For AWS the invitation must still be **pending**. If it has already been accepted, rejected or expired — or if the referral has already progressed to a full AWS opportunity — the action returns `Success = false` with the reason, and nothing is changed.
:::

:::note
GCP is not supported: passing `GCP` returns `Success = false` with `only AWS and Azure are supported`.
:::

---

## Update Referral Linkage

**API Name:** `CosellUpdateReferralLinkageApexAction`

**Label:** Update Referral Linkage

**Description:** Link a referral to a Salesforce opportunity, an account, or both. Use it to associate a referral with CRM records by hand, or to correct the linkage after the referral was created.

### Supported Partners

AWS, Azure, GCP, Fours (partner-to-partner co-sell)

### Inputs

| Parameter | Type | Required | Description |
| --------- | ---- | :------: | ----------- |
| Partner | String | Yes | Partner: `AWS`, `AZURE`, `GCP`, or `SUGER`. Case-insensitive |
| Referral ID | String | Yes | Fours Referral ID to update |
| Opportunity ID | Id | No | Salesforce Opportunity ID to link. **At least one of Opportunity ID or Account ID is required** |
| Account ID | Id | No | Salesforce Account ID to link. **At least one of Opportunity ID or Account ID is required** |

### Outputs

| Parameter | Type | Description |
| --------- | ---- | ----------- |
| Success | Boolean | Whether the linkage was updated successfully |
| Message | String | `No changes; skipped.` when the referral already holds the IDs you supplied; the reason on failure; otherwise empty |

:::note
**A blank ID is left alone, not cleared.** Leaving Opportunity ID or Account ID empty means *keep whatever Fours already holds* — it does not unlink. That is what makes an account-only link safe: it cannot wipe an opportunity the referral is already attached to. To remove a link, use **Unlink** on the referral's Account card in Salesforce (and confirm **Clear Account**); this action cannot unlink.
:::

### Behavior

1. Rejects an unsupported partner, and rejects a request that supplies **neither** Opportunity ID nor Account ID — `opportunityId or accountId is required` — before spending a callout
2. Validates the user has write permission for the specified partner
3. Fetches the referral from Fours
4. Returns `Success = true` with `No changes; skipped.` when the referral already holds the IDs you supplied. An ID you did not supply never counts as a difference, so re-running an account-only link is a no-op rather than a write
5. Writes `salesforceOpportunityId` and `salesforceAccountId` — only the ones you supplied
6. For Azure **with** an Opportunity ID, also updates `externalReferenceId` in the Microsoft referral set, so the link reaches Microsoft Partner Center

:::note
Linking an account on its own takes the CRM-linkage-only path for every partner, Azure included: there is nothing for Microsoft to mirror, so the referral is not put in front of the partner update validator. An Azure link that carries an Opportunity ID behaves as before.
:::

## Update AWS Referral Fields

**API Name:** `CosellUpdateAwsReferralApexAction`

**Label:** Update AWS Referral

**Description:** Update business fields on an existing AWS co-sell referral. Only the fields you supply are changed — leave a field out and it keeps its current value. A referral belonging to another partner is refused.

### Supported Partners

AWS

### Inputs

| Parameter | Type | Required | Description |
| --------- | ---- | :------: | ----------- |
| Referral ID | String | Yes | Fours Referral ID to update |
| APN Programs | String | No | Semicolon-separated APN program names, e.g. `Migration Acceleration Program;Well-Architected`. Values must come from the AWS APN Program picklist. `Model Provider PARC` is accepted only on a referral AWS originated. Pass an **empty value** to remove every program |
| Next Step | String | No | Next step on the opportunity, up to 255 characters |
| Target Close Date | Date | No | Must be a future date (after today, UTC). AWS freezes this once the opportunity is Launched |
| Customer Business Problem | String | No | The customer's situation and the problem they need solved, 20 to 2,000 characters |
| Additional Comments | String | No | Free-text notes on the opportunity, up to 255 characters |

### Outputs

| Parameter | Type | Description |
| --------- | ---- | ----------- |
| Success | Boolean | Whether the referral was updated |
| Message | String | The fields updated on success, or the reason it was refused |
| APN Programs | String | The full program list on the referral after the update, semicolon-separated |
| Referral Status | String | The referral status as it stood **before** this update, e.g. `ACTIVE` or `ACTION_REQUIRED`. Fours recomputes the status while saving, and the push to AWS can change it again after that — re-read the referral if a Flow must branch on the new value |

### Behavior

1. Validates that at least one field was supplied — every field is optional, but a request that sets none is refused rather than costing two callouts to write the referral back unchanged — and that the user has write permission for the partner
2. Validates every supplied value **before any callout** and **refuses the whole update** if one fails: APN Programs must all be on the AWS APN Program picklist (an unrecognized value is rejected by AWS and blocks every later change to that referral), Target Close Date must be in the future, and the text fields must be within the lengths above. Fours would otherwise keep the old value or truncate silently, so it is caught here instead
3. Fetches the referral from Fours
4. Refuses a referral that belongs to another partner — the caller passes only a referral ID, so the referral's own partner decides
5. Refuses the update unless AWS has the opportunity at `Approved` or `Action Required`; AWS accepts changes in no other review status
6. Refuses the update when the opportunity is `Launched` or `Closed Lost`, because AWS does not accept changes after close
7. Refuses `Model Provider PARC` unless AWS originated the referral — AWS rejects it on a partner-originated opportunity, and that rejection blocks every later change to the referral. It stays editable on an inbound referral
8. Applies only the supplied fields and sends the complete referral back to Fours, which pushes it to AWS asynchronously

APN Programs replace the full list rather than adding to it — AWS treats the field as a set. Send every program the referral should end up with.

**Clearing APN Programs.** Pass an empty value (in Flow, the **Blank Value (Empty String)** resource) to remove every program. This is the only field that can be cleared — the others are left untouched when blank, because AWS either requires them or enforces a minimum length. Note the current limitation: the empty set is stored in Fours, but it does **not** yet reach AWS, so the programs can reappear on the next inbound sync. Clearing at AWS is tracked separately.

Each request in a Flow collection gets its own result, so one bad row does not stop the others.

---

## Permission Requirements

All Apex Actions require appropriate co-sell permissions:

| Action | Required Permission |
| ------ | ------------------- |
| Check If Opportunity Shared | None (read-only check) |
| Co-Sell | `Write_AWS_Referral`, `Write_Azure_Referral`, or `Write_GCP_Referral` (based on partner) |
| Co-Sell (Fire and Forget) | `Write_AWS_Referral`, `Write_Azure_Referral`, or `Write_GCP_Referral` (based on partner) |
| Process Inbound Referral | `Write_Azure_Referral` or `Write_AWS_Referral` (based on partner) |
| Update Referral Linkage | `Write_AWS_Referral`, `Write_Azure_Referral`, `Write_GCP_Referral`, or `Write_Suger_Referral` (based on partner) |
| Update Referral Fields | `Write_AWS_Referral` |

Users with the `Create_Referral` permission have write access to all partners.

---

## Using Apex Actions in Flows

To use these actions in a Salesforce Flow:

1. Add an **Action** element to your flow
2. Search for the action by its label (e.g., "Co-Sell" or "Check If Opportunity Shared")
3. Configure the input parameters
4. Store the output values in flow variables for decision logic or display

### Example Flow: Auto-Share Opportunity

```text
1. [Trigger] Opportunity Stage = "Closed Won"
2. [Action] Check If Opportunity Shared (Partner: AWS)
3. [Decision] Has Been Shared = false?
   - Yes → [Action] Co-Sell (Partner: AWS)
   - No → [End]
4. [Decision] Success = true?
   - Yes → [Update Record] Set custom field "AWS Referral Created" = true
   - No → [Create Task] "Review failed AWS co-sell submission"
```

---

## Error Handling

All actions return a `Success` boolean and `Message` string. Common error scenarios:

| Error | Cause | Resolution |
| ----- | ----- | ---------- |
| "unsupported partner: X" | Invalid partner value | Use `AWS`, `AZURE`, or `GCP` — plus `SUGER` on Update Referral Linkage. Match the spelling exactly: only Update Referral Linkage and Update AWS Referral normalize the case for you |
| "opportunityId or accountId is required" | Update Referral Linkage called with both IDs empty | Supply an Opportunity ID, an Account ID, or both |
| "only AWS and Azure are supported" | Using Process Inbound with a partner other than `AZURE` or `AWS` | Use `AZURE` or `AWS`, or process through the partner portal |
| "Engagement invitation not found" | Process Inbound on an AWS referral that is not an inbound engagement invitation | Only AWS-originated inbound referrals carry an invitation to accept |
| "Engagement invitation is not pending, current status: X" | The AWS invitation was already accepted, rejected or has expired | Nothing to do — check the referral's current state in Fours |
| Permission denied | User lacks required custom permission | Assign appropriate permission set |
| "Referral not found" | Invalid referral ID | Verify the Fours referral ID is correct |
| "No response from Suger" | API connectivity issue | Check Fours integration status |
| "No fields supplied. Set at least one of: …" | Update Referral Fields called with every field empty | Set at least one field; a blank string counts as empty |
| "Not a valid value for APN Programs: X" | A program name not on the AWS APN Program picklist | Use the picklist values exactly; the whole update is refused, nothing was sent |
| "… must be a future date" / "… must be at least/most N characters" | A value Fours would silently drop or truncate | Fix the value; nothing was sent |
| "Opportunity is Launched. AWS does not accept updates after close…" | Referral is Launched or Closed Lost | No update is possible after close |
| "… failed to create (status CREATE_FAILED) and cannot be edited via update …" | Update AWS Referral was called on a referral whose creation failed (`CREATE_FAILED`); Fours refuses updates to it | Resubmit the referral instead: open it in Salesforce and use **Re-submit** on its detail page, which sends it through create again under the same referral ID |

:::tip
Always check the `Success` output before proceeding with downstream logic. Store the `Message` output for debugging failed operations.
:::
