Add complete automation SOP system for BlackRoad ERP

This implements the "Automate The Company" initiative with comprehensive
Standard Operating Procedures for GitHub + Salesforce + Asana integration.

New directory: sop/
├── workflows/ - End-to-end process documentation
│   ├── new-client-kickoff.md - Flagship workflow from deal → repos → Asana
│   └── release-pipeline.md - Deploy → update Salesforce + Asana
├── playbooks/ - Human-friendly checklists
│   └── brenda-new-client-checklist.md - Non-technical operator guide
├── salesforce/ - Salesforce automation specifications
│   ├── flows/opp-automation-onstagechange.md - Trigger on Closed Won
│   └── orchestrations/new-client-kickoff-orchestration.md - Multi-stage process
├── integrations/ - API integration specifications
│   ├── salesforce-to-github.md - Create repos from Salesforce
│   ├── github-to-salesforce.md - Update Salesforce after deploy
│   └── salesforce-to-asana.md - Create Asana projects from Salesforce
└── templates/ - Reusable templates
    ├── github-actions/ - CI/CD workflows (ci.yml, deploy.yml, safety.yml)
    └── repo-template/ - Standard repo config (PR template, labels, branch protection)

Key Features:
- Event-driven automation (Closed Won → repos + Asana creation)
- GitHub Actions templates for CI/CD baseline
- Salesforce Flow & Orchestration specs
- Complete API integration documentation
- Operator-friendly playbooks
- Two-view approach (operator + engineer)
- No manual status syncing across systems

This provides the complete backbone for next-gen ERP automation.
This commit is contained in:
Claude
2025-11-17 08:17:51 +00:00
parent 9b137af555
commit 7cde897040
18 changed files with 6077 additions and 0 deletions

View File

@@ -0,0 +1,567 @@
# Integration: GitHub → Salesforce
**Purpose:** Enable GitHub Actions to update Salesforce records after deployments
**Direction:** GitHub calls Salesforce REST API
**Authentication:** OAuth 2.0 (Connected App) or Named Credential
**Status:** Active
**Last Updated:** 2025-11-17
---
## Overview
This integration allows GitHub Actions workflows to:
- Update Project records with deployment metadata
- Create Deployment records for audit trail
- Trigger Salesforce flows/automations
- Close tasks or update statuses
**Key Use Case:** Auto-update Salesforce when code is deployed to production
---
## Authentication Setup
### Option A: Salesforce Connected App (Recommended)
**Benefits:**
- OAuth 2.0 standard
- Refresh tokens
- IP restrictions
- Better audit trail
**Setup Steps:**
#### 1. Create Connected App in Salesforce
1. **Setup → App Manager → New Connected App**
2. **Basic Information:**
- Connected App Name: `GitHub Actions Integration`
- API Name: `GitHub_Actions_Integration`
- Contact Email: devops@blackroad.com
3. **API (Enable OAuth Settings):**
- ✅ Enable OAuth Settings
- Callback URL: `https://login.salesforce.com/services/oauth2/callback`
- Selected OAuth Scopes:
- `api` - Perform requests at any time
- `refresh_token, offline_access` - Perform requests at any time
4. **Save** and wait 2-10 minutes for Consumer Key/Secret to be generated
5. **Get Credentials:**
- Consumer Key (Client ID): `3MVG9...`
- Consumer Secret (Client Secret): `ABC123...`
#### 2. Create GitHub Secrets
In each project repository (or organization-level):
1. Go to: Settings → Secrets and variables → Actions
2. Add these secrets:
| Secret Name | Value | Description |
|-------------|-------|-------------|
| `SALESFORCE_INSTANCE_URL` | `https://your-domain.my.salesforce.com` | Salesforce instance URL |
| `SALESFORCE_CLIENT_ID` | `3MVG9...` | Connected App Consumer Key |
| `SALESFORCE_CLIENT_SECRET` | `ABC123...` | Connected App Consumer Secret |
| `SALESFORCE_USERNAME` | `integration-user@blackroad.com` | Service account username |
| `SALESFORCE_PASSWORD` | `password123` | Service account password |
| `SALESFORCE_SECURITY_TOKEN` | `XYZ789...` | Security token for user |
**Security Best Practice:** Use a dedicated integration service account, not a personal user.
#### 3. Get Access Token in GitHub Actions
**Workflow Step:**
```yaml
- name: Get Salesforce Access Token
id: sf-auth
run: |
RESPONSE=$(curl -X POST "https://login.salesforce.com/services/oauth2/token" \
-d "grant_type=password" \
-d "client_id=${{ secrets.SALESFORCE_CLIENT_ID }}" \
-d "client_secret=${{ secrets.SALESFORCE_CLIENT_SECRET }}" \
-d "username=${{ secrets.SALESFORCE_USERNAME }}" \
-d "password=${{ secrets.SALESFORCE_PASSWORD }}${{ secrets.SALESFORCE_SECURITY_TOKEN }}")
ACCESS_TOKEN=$(echo $RESPONSE | jq -r '.access_token')
INSTANCE_URL=$(echo $RESPONSE | jq -r '.instance_url')
echo "ACCESS_TOKEN=$ACCESS_TOKEN" >> $GITHUB_OUTPUT
echo "INSTANCE_URL=$INSTANCE_URL" >> $GITHUB_OUTPUT
```
---
### Option B: Salesforce REST API Endpoint (Webhook Style)
**Setup Steps:**
1. **Create Apex REST Endpoint:**
```apex
@RestResource(urlMapping='/github/webhook')
global class GitHubWebhookHandler {
@HttpPost
global static String handleDeployment() {
RestRequest req = RestContext.request;
String body = req.requestBody.toString();
Map<String, Object> payload = (Map<String, Object>) JSON.deserializeUntyped(body);
String projectKey = (String) payload.get('project_key');
String deployedAt = (String) payload.get('deployed_at');
String gitSHA = (String) payload.get('git_sha');
String deployedBy = (String) payload.get('deployed_by');
// Find Project by external ID
Project__c project = [
SELECT Id, Name
FROM Project__c
WHERE Project_Key__c = :projectKey
LIMIT 1
];
// Update Project
project.Last_Deploy_At__c = Datetime.valueOf(deployedAt);
project.Last_Deploy_SHA__c = gitSHA;
project.Last_Deploy_Actor__c = deployedBy;
project.Deploy_Status__c = 'Success';
update project;
// Create Deployment record
Deployment__c deployment = new Deployment__c(
Project__c = project.Id,
Deployed_At__c = Datetime.valueOf(deployedAt),
Git_SHA__c = gitSHA,
Deployed_By__c = deployedBy,
Status__c = 'Success'
);
insert deployment;
return JSON.serialize(new Map<String, Object>{
'success' => true,
'project_id' => project.Id,
'deployment_id' => deployment.Id
});
}
}
```
2. **Configure Site Guest User Permissions:**
- Grant access to `Project__c` and `Deployment__c` objects
- Or use API key authentication in Apex
3. **Get Endpoint URL:**
- `https://your-domain.my.salesforce.com/services/apexrest/github/webhook`
4. **Add to GitHub Secrets:**
- `SALESFORCE_WEBHOOK_URL`: Full endpoint URL
---
## API Operations
### 1. Update Project Record (Upsert by External ID)
**Endpoint:**
```
PATCH {INSTANCE_URL}/services/data/v58.0/sobjects/Project__c/Project_Key__c/{PROJECT_KEY}
```
**Headers:**
```
Authorization: Bearer {ACCESS_TOKEN}
Content-Type: application/json
```
**Payload:**
```json
{
"Last_Deploy_At__c": "2025-11-17T14:30:00Z",
"Last_Deploy_SHA__c": "a1b2c3d4e5f6",
"Last_Deploy_Branch__c": "main",
"Last_Deploy_Actor__c": "github-user",
"Deploy_Status__c": "Success",
"Environment__c": "Production",
"Backend_URL__c": "https://backend.railway.app",
"Release_Notes_URL__c": "https://github.com/org/repo/commit/a1b2c3d4e5f6"
}
```
**Response (200 OK or 201 Created):**
```json
{
"id": "a0X5e000000XYZ1EAO",
"success": true,
"errors": []
}
```
**GitHub Actions Implementation:**
```yaml
- name: Update Salesforce Project record
env:
SF_INSTANCE_URL: ${{ steps.sf-auth.outputs.INSTANCE_URL }}
SF_ACCESS_TOKEN: ${{ steps.sf-auth.outputs.ACCESS_TOKEN }}
run: |
# Extract PROJECT_KEY from repo name
REPO_NAME="${{ github.repository }}"
PROJECT_KEY=$(echo "$REPO_NAME" | sed -n 's/.*blackroad-\([A-Z0-9-]*\)-.*/\1/p')
curl -X PATCH \
"$SF_INSTANCE_URL/services/data/v58.0/sobjects/Project__c/Project_Key__c/$PROJECT_KEY" \
-H "Authorization: Bearer $SF_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"Last_Deploy_At__c": "'"$(date -u +%Y-%m-%dT%H:%M:%SZ)"'",
"Last_Deploy_SHA__c": "${{ github.sha }}",
"Last_Deploy_Branch__c": "${{ github.ref_name }}",
"Last_Deploy_Actor__c": "${{ github.actor }}",
"Deploy_Status__c": "Success",
"Environment__c": "Production",
"Release_Notes_URL__c": "https://github.com/${{ github.repository }}/commit/${{ github.sha }}"
}'
```
---
### 2. Create Deployment Record
**Endpoint:**
```
POST {INSTANCE_URL}/services/data/v58.0/sobjects/Deployment__c
```
**Headers:**
```
Authorization: Bearer {ACCESS_TOKEN}
Content-Type: application/json
```
**Payload:**
```json
{
"Name": "ACME-1042 - a1b2c3d4",
"Project_Key__c": "ACME-1042",
"Deployed_At__c": "2025-11-17T14:30:00Z",
"Git_SHA__c": "a1b2c3d4e5f6",
"Git_Branch__c": "main",
"Deployed_By__c": "github-user",
"Status__c": "Success",
"Environment__c": "Production",
"Repository__c": "blackboxprogramming/blackroad-ACME-1042-backend",
"Commit_URL__c": "https://github.com/blackboxprogramming/blackroad-ACME-1042-backend/commit/a1b2c3d4e5f6",
"Duration_Seconds__c": 120
}
```
**Response (201 Created):**
```json
{
"id": "a0Y5e000000ABC1EAO",
"success": true,
"errors": []
}
```
**GitHub Actions Implementation:**
```yaml
- name: Create Salesforce Deployment record
run: |
curl -X POST \
"$SF_INSTANCE_URL/services/data/v58.0/sobjects/Deployment__c" \
-H "Authorization: Bearer $SF_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"Name": "'"$PROJECT_KEY"' - '"${GITHUB_SHA:0:8}"'",
"Project_Key__c": "'"$PROJECT_KEY"'",
"Deployed_At__c": "'"$(date -u +%Y-%m-%dT%H:%M:%SZ)"'",
"Git_SHA__c": "${{ github.sha }}",
"Git_Branch__c": "${{ github.ref_name }}",
"Deployed_By__c": "${{ github.actor }}",
"Status__c": "Success",
"Environment__c": "Production",
"Repository__c": "${{ github.repository }}",
"Commit_URL__c": "https://github.com/${{ github.repository }}/commit/${{ github.sha }}"
}'
```
---
### 3. Query Salesforce (Optional)
**Use Case:** Get Project metadata before deploying
**Endpoint:**
```
GET {INSTANCE_URL}/services/data/v58.0/query?q=SELECT+Id,Name,Package_Type__c+FROM+Project__c+WHERE+Project_Key__c='ACME-1042'
```
**Headers:**
```
Authorization: Bearer {ACCESS_TOKEN}
```
**Response:**
```json
{
"totalSize": 1,
"done": true,
"records": [
{
"Id": "a0X5e000000XYZ1EAO",
"Name": "Acme Corp - ACME-1042",
"Package_Type__c": "OS"
}
]
}
```
---
## Required Salesforce Objects
### Deployment Custom Object
**API Name:** `Deployment__c`
**Label:** Deployment
**Record Name:** Deployment Name (Auto Number: `DEP-{0000}`)
#### Fields:
| Field API Name | Type | Length | Description |
|----------------|------|--------|-------------|
| `Project__c` | Lookup(Project__c) | N/A | Related project |
| `Project_Key__c` | Text (External ID) | 20 | For upsert operations |
| `Deployed_At__c` | DateTime | N/A | When deployment occurred |
| `Git_SHA__c` | Text | 40 | Git commit SHA |
| `Git_Branch__c` | Text | 100 | Git branch name |
| `Deployed_By__c` | Text | 100 | GitHub username |
| `Status__c` | Picklist | N/A | Success, Failed, Rollback, In Progress |
| `Environment__c` | Picklist | N/A | Staging, Production |
| `Repository__c` | Text | 255 | Full repo name (org/repo) |
| `Commit_URL__c` | URL | 255 | Link to commit |
| `Duration_Seconds__c` | Number(6,0) | N/A | Deploy duration |
| `Error_Message__c` | Long Text Area | 32768 | If deploy failed |
---
## Complete GitHub Actions Workflow
**File:** `.github/workflows/notify-salesforce.yml`
```yaml
name: Notify Salesforce After Deploy
on:
workflow_run:
workflows: ["Deploy to Production"]
types:
- completed
jobs:
notify-salesforce:
runs-on: ubuntu-latest
if: ${{ github.event.workflow_run.conclusion == 'success' }}
steps:
- name: Extract Project Key from repo name
id: project-key
run: |
REPO_NAME="${{ github.repository }}"
PROJECT_KEY=$(echo "$REPO_NAME" | sed -n 's/.*blackroad-\([A-Z0-9-]*\)-.*/\1/p')
if [ -z "$PROJECT_KEY" ]; then
echo "Warning: Could not extract project key"
PROJECT_KEY="UNKNOWN"
fi
echo "PROJECT_KEY=$PROJECT_KEY" >> $GITHUB_OUTPUT
- name: Authenticate with Salesforce
id: sf-auth
env:
SF_CLIENT_ID: ${{ secrets.SALESFORCE_CLIENT_ID }}
SF_CLIENT_SECRET: ${{ secrets.SALESFORCE_CLIENT_SECRET }}
SF_USERNAME: ${{ secrets.SALESFORCE_USERNAME }}
SF_PASSWORD: ${{ secrets.SALESFORCE_PASSWORD }}
SF_SECURITY_TOKEN: ${{ secrets.SALESFORCE_SECURITY_TOKEN }}
run: |
RESPONSE=$(curl -X POST "https://login.salesforce.com/services/oauth2/token" \
-d "grant_type=password" \
-d "client_id=$SF_CLIENT_ID" \
-d "client_secret=$SF_CLIENT_SECRET" \
-d "username=$SF_USERNAME" \
-d "password=$SF_PASSWORD$SF_SECURITY_TOKEN")
ACCESS_TOKEN=$(echo $RESPONSE | jq -r '.access_token')
INSTANCE_URL=$(echo $RESPONSE | jq -r '.instance_url')
echo "ACCESS_TOKEN=$ACCESS_TOKEN" >> $GITHUB_OUTPUT
echo "INSTANCE_URL=$INSTANCE_URL" >> $GITHUB_OUTPUT
- name: Update Salesforce Project record
if: steps.project-key.outputs.PROJECT_KEY != 'UNKNOWN'
env:
SF_INSTANCE_URL: ${{ steps.sf-auth.outputs.INSTANCE_URL }}
SF_ACCESS_TOKEN: ${{ steps.sf-auth.outputs.ACCESS_TOKEN }}
PROJECT_KEY: ${{ steps.project-key.outputs.PROJECT_KEY }}
run: |
curl -X PATCH \
"$SF_INSTANCE_URL/services/data/v58.0/sobjects/Project__c/Project_Key__c/$PROJECT_KEY" \
-H "Authorization: Bearer $SF_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"Last_Deploy_At__c": "'"$(date -u +%Y-%m-%dT%H:%M:%SZ)"'",
"Last_Deploy_SHA__c": "${{ github.sha }}",
"Last_Deploy_Branch__c": "${{ github.ref_name }}",
"Last_Deploy_Actor__c": "${{ github.actor }}",
"Deploy_Status__c": "Success",
"Environment__c": "Production",
"Release_Notes_URL__c": "https://github.com/${{ github.repository }}/commit/${{ github.sha }}"
}' || echo "Warning: Failed to update Project record"
- name: Create Deployment record
if: steps.project-key.outputs.PROJECT_KEY != 'UNKNOWN'
env:
SF_INSTANCE_URL: ${{ steps.sf-auth.outputs.INSTANCE_URL }}
SF_ACCESS_TOKEN: ${{ steps.sf-auth.outputs.ACCESS_TOKEN }}
PROJECT_KEY: ${{ steps.project-key.outputs.PROJECT_KEY }}
run: |
curl -X POST \
"$SF_INSTANCE_URL/services/data/v58.0/sobjects/Deployment__c" \
-H "Authorization: Bearer $SF_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"Name": "'"$PROJECT_KEY"' - '"${GITHUB_SHA:0:8}"'",
"Project_Key__c": "'"$PROJECT_KEY"'",
"Deployed_At__c": "'"$(date -u +%Y-%m-%dT%H:%M:%SZ)"'",
"Git_SHA__c": "${{ github.sha }}",
"Git_Branch__c": "${{ github.ref_name }}",
"Deployed_By__c": "${{ github.actor }}",
"Status__c": "Success",
"Environment__c": "Production",
"Repository__c": "${{ github.repository }}",
"Commit_URL__c": "https://github.com/${{ github.repository }}/commit/${{ github.sha }}"
}' || echo "Warning: Failed to create Deployment record"
```
---
## Error Handling
### Common Errors
| Status Code | Error | Cause | Solution |
|-------------|-------|-------|----------|
| 401 | Unauthorized | Token expired | Re-authenticate |
| 403 | Forbidden | Insufficient permissions | Check user permissions in Salesforce |
| 404 | Not Found | Project_Key__c doesn't exist | Verify project key extraction logic |
| 400 | Bad Request | Invalid field value | Check datetime format (must be ISO 8601) |
### Retry Logic
```yaml
- name: Update Salesforce (with retries)
uses: nick-invision/retry@v2
with:
timeout_minutes: 2
max_attempts: 3
command: |
curl -X PATCH ... (Salesforce API call)
```
---
## Testing
### Manual Test (curl)
```bash
# 1. Get access token
ACCESS_TOKEN=$(curl -X POST "https://login.salesforce.com/services/oauth2/token" \
-d "grant_type=password" \
-d "client_id=YOUR_CLIENT_ID" \
-d "client_secret=YOUR_CLIENT_SECRET" \
-d "username=YOUR_USERNAME" \
-d "password=YOUR_PASSWORD_AND_TOKEN" | jq -r '.access_token')
# 2. Update Project
curl -X PATCH \
"https://your-domain.my.salesforce.com/services/data/v58.0/sobjects/Project__c/Project_Key__c/TEST-1234" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"Last_Deploy_At__c": "2025-11-17T14:30:00Z",
"Deploy_Status__c": "Success"
}'
```
### GitHub Actions Test
1. Push commit to test repo
2. Trigger deploy workflow
3. Verify Salesforce Project updated
4. Check Deployment record created
5. Review GitHub Actions logs for errors
---
## Monitoring
**Track These Metrics:**
| Metric | Target | Alert Threshold |
|--------|--------|-----------------|
| Salesforce API Success Rate | > 98% | < 95% |
| Avg API Response Time | < 1s | > 3s |
| Failed Updates | < 2% | > 5% |
**Salesforce Report:** "Deployments by Status (Last 30 Days)"
---
## Security Best Practices
1. **Use Service Account:**
- Create `integration-user@blackroad.com`
- Assign minimal permissions
- Enable API-only user (no login UI)
2. **Rotate Credentials:**
- Rotate passwords every 90 days
- Use GitHub encrypted secrets
- Never commit credentials to git
3. **IP Restrictions:**
- Whitelist GitHub Actions IP ranges in Salesforce
- See: https://api.github.com/meta
4. **Audit Logging:**
- Enable Salesforce Event Monitoring
- Log all API calls
- Review monthly
---
## Related Documentation
- [Salesforce REST API Docs](https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/)
- [GitHub Actions: Deploy Workflow](../templates/github-actions/deploy.yml)
- [Workflow: Release Pipeline](../workflows/release-pipeline.md)
- [Integration: Salesforce → GitHub](./salesforce-to-github.md)
---
## Changelog
| Date | Version | Change | Author |
|------|---------|--------|--------|
| 2025-11-17 | 1.0 | Initial specification | Cece (Claude) |

View File

@@ -0,0 +1,644 @@
# Integration: Salesforce → Asana
**Purpose:** Enable Salesforce to create and manage Asana projects and tasks automatically
**Direction:** Salesforce calls Asana REST API
**Authentication:** Personal Access Token (PAT)
**Status:** Active
**Last Updated:** 2025-11-17
---
## Overview
This integration allows Salesforce Flows and Orchestrations to:
- Create Asana projects
- Add sections to projects
- Create tasks with assignments and due dates
- Update task status
- Add comments to tasks
**Key Use Case:** Auto-create Asana project board when Opportunity closes
---
## Authentication Setup
### Asana Personal Access Token (PAT)
**Setup Steps:**
1. **Generate PAT:**
- Go to: https://app.asana.com/0/my-apps
- Click "Personal access tokens"
- Click "+ New access token"
- Name: `Salesforce Integration`
- Copy token (starts with `1/...`)
2. **Store in Salesforce:**
- Setup → Named Credentials → New
- Name: `Asana_API`
- URL: `https://app.asana.com/api/1.0`
- Identity Type: Named Principal
- Authentication Protocol: Custom
- Header: `Authorization: Bearer {YOUR_PAT}`
**Alternative:** Use Custom Settings
```
Setup → Custom Settings → New (Protected)
Name: Asana_Settings__c
Fields:
- API_Token__c (Text, Encrypted)
- Workspace_GID__c (Text)
- Team_GID__c (Text)
```
---
## Required Asana Setup
### 1. Get Workspace GID
```bash
curl "https://app.asana.com/api/1.0/workspaces" \
-H "Authorization: Bearer YOUR_PAT"
```
**Response:**
```json
{
"data": [
{
"gid": "1234567890123456",
"name": "BlackRoad Workspace",
"resource_type": "workspace"
}
]
}
```
**Store:** `WORKSPACE_GID = "1234567890123456"`
---
### 2. Get Team GID
```bash
curl "https://app.asana.com/api/1.0/organizations/1234567890123456/teams" \
-H "Authorization: Bearer YOUR_PAT"
```
**Response:**
```json
{
"data": [
{
"gid": "9876543210987654",
"name": "Engineering",
"resource_type": "team"
}
]
}
```
**Store:** `TEAM_GID = "9876543210987654"`
---
### 3. Get User GIDs for Assignments
```bash
curl "https://app.asana.com/api/1.0/users?workspace=1234567890123456" \
-H "Authorization: Bearer YOUR_PAT"
```
**Response:**
```json
{
"data": [
{
"gid": "1111111111111111",
"name": "Alice Developer",
"email": "alice@blackroad.com"
},
{
"gid": "2222222222222222",
"name": "Bob DevOps",
"email": "bob@blackroad.com"
}
]
}
```
**Create Salesforce Mapping:**
| Role | Email | Asana GID |
|------|-------|-----------|
| DevOps Lead | bob@blackroad.com | 2222222222222222 |
| Backend Lead | alice@blackroad.com | 1111111111111111 |
| Customer Success | brenda@blackroad.com | 3333333333333333 |
Store in Custom Metadata Type: `Asana_User_Mapping__mdt`
---
## API Operations
### 1. Create Project
**Endpoint:**
```
POST https://app.asana.com/api/1.0/projects
```
**Headers:**
```
Authorization: Bearer {ASANA_PAT}
Content-Type: application/json
```
**Payload:**
```json
{
"data": {
"workspace": "1234567890123456",
"team": "9876543210987654",
"name": "Acme Corp - ACME-1042",
"notes": "Salesforce Project: https://your-domain.my.salesforce.com/a0X5e000000XYZ1EAO\n\nRepos:\n- Backend: https://github.com/blackboxprogramming/blackroad-ACME-1042-backend\n- Frontend: https://github.com/blackboxprogramming/blackroad-ACME-1042-frontend\n- Ops: https://github.com/blackboxprogramming/blackroad-ACME-1042-ops",
"color": "light-green",
"default_view": "board",
"public": false
}
}
```
**Response (201 Created):**
```json
{
"data": {
"gid": "5555555555555555",
"name": "Acme Corp - ACME-1042",
"permalink_url": "https://app.asana.com/0/5555555555555555/list"
}
}
```
**Salesforce Flow Implementation:**
```yaml
Element: HTTP Callout
Method: POST
Endpoint: {!$Credential.Asana_API}/projects
Headers:
- Authorization: Bearer {!$Credential.Asana_API.Token}
- Content-Type: application/json
Body:
{
"data": {
"workspace": "{!$CustomMetadata.Asana_Settings__mdt.Workspace_GID__c}",
"team": "{!$CustomMetadata.Asana_Settings__mdt.Team_GID__c}",
"name": "{!varProject.Name}",
"notes": "Salesforce: {!varProject.Id}\nRepos:\n- {!varProject.Backend_Repo_URL__c}",
"color": "light-green",
"default_view": "board"
}
}
Store Response: varAsanaResponse
Parse:
- varAsanaProjectGID = {!varAsanaResponse.data.gid}
- varAsanaProjectURL = {!varAsanaResponse.data.permalink_url}
Update Project Record:
- Asana_Project_URL__c = {!varAsanaProjectURL}
- Asana_Project_GID__c = {!varAsanaProjectGID}
```
---
### 2. Create Section
**Endpoint:**
```
POST https://app.asana.com/api/1.0/projects/{PROJECT_GID}/sections
```
**Payload:**
```json
{
"data": {
"name": "Discovery"
}
}
```
**Response (201 Created):**
```json
{
"data": {
"gid": "6666666666666666",
"name": "Discovery"
}
}
```
**Salesforce Implementation:**
```yaml
Element: Loop
Collection: ["Discovery", "Architecture", "Build", "Testing", "Go-Live"]
Current Item: varSectionName
Inside Loop:
- HTTP Callout
- Endpoint: {!$Credential.Asana_API}/projects/{!varAsanaProjectGID}/sections
- Body: {"data": {"name": "{!varSectionName}"}}
- Store Response: varSectionResponse
- Add to Collection: varSectionGIDs[{!varSectionName}] = {!varSectionResponse.data.gid}
```
---
### 3. Create Task
**Endpoint:**
```
POST https://app.asana.com/api/1.0/tasks
```
**Payload:**
```json
{
"data": {
"projects": ["5555555555555555"],
"name": "Confirm domain + DNS with client",
"notes": "Get final domain, subdomain, and DNS setup requirements from client.\n\nSalesforce Project: https://your-domain.my.salesforce.com/a0X5e000000XYZ1EAO",
"assignee": "3333333333333333",
"due_on": "2025-11-20",
"memberships": [
{
"project": "5555555555555555",
"section": "6666666666666666"
}
]
}
}
```
**Response (201 Created):**
```json
{
"data": {
"gid": "7777777777777777",
"name": "Confirm domain + DNS with client",
"permalink_url": "https://app.asana.com/0/5555555555555555/7777777777777777"
}
}
```
**Salesforce Implementation:**
```yaml
Element: Loop
Collection: varTaskDefinitions (custom metadata or JSON)
Current Item: varTask
Inside Loop:
- Calculate Due Date
Formula: TODAY() + {!varTask.DaysOffset}
- Lookup Assignee GID
From: Asana_User_Mapping__mdt
Match: Role = {!varTask.AssigneeRole}
- HTTP Callout
- Endpoint: {!$Credential.Asana_API}/tasks
- Body:
{
"data": {
"projects": ["{!varAsanaProjectGID}"],
"name": "{!varTask.Name}",
"notes": "{!varTask.Description}\n\nSalesforce: {!varProject.Id}",
"assignee": "{!varAssigneeGID}",
"due_on": "{!varDueDate}",
"memberships": [{
"project": "{!varAsanaProjectGID}",
"section": "{!varSectionGIDs[varTask.Section]}"
}]
}
}
```
---
### 4. Update Task (Mark Complete)
**Endpoint:**
```
PUT https://app.asana.com/api/1.0/tasks/{TASK_GID}
```
**Payload:**
```json
{
"data": {
"completed": true
}
}
```
**Response (200 OK):**
```json
{
"data": {
"gid": "7777777777777777",
"completed": true
}
}
```
---
### 5. Add Comment to Task
**Endpoint:**
```
POST https://app.asana.com/api/1.0/tasks/{TASK_GID}/stories
```
**Payload:**
```json
{
"data": {
"text": "✅ Deployed v0.1.3 to production\n\n**Commit:** a1b2c3d4\n**By:** github-user\n**Time:** 2025-11-17 14:30 UTC\n**Link:** https://github.com/org/repo/commit/a1b2c3d4"
}
}
```
**Response (201 Created):**
```json
{
"data": {
"gid": "8888888888888888",
"text": "✅ Deployed v0.1.3..."
}
}
```
---
## Task Template Definition
**Store in Salesforce Custom Metadata:** `Asana_Task_Template__mdt`
| Label | API Name | Section__c | Days_Offset__c | Assignee_Role__c |
|-------|----------|------------|----------------|------------------|
| Confirm domain + DNS | Confirm_Domain_DNS | Discovery | 1 | Sales Ops |
| Gather branding assets | Gather_Branding | Discovery | 1 | Design |
| Wire up Railway/Cloudflare | Wire_Up_Envs | Architecture | 3 | DevOps |
| Enable CI/CD secrets | Enable_Secrets | Architecture | 3 | DevOps |
| Set up database | Setup_Database | Build | 5 | Backend |
| Implement authentication | Implement_Auth | Build | 7 | Backend |
| Run E2E tests | Run_E2E_Tests | Testing | 12 | QA |
| Final client walkthrough | Final_Walkthrough | Go-Live | 18 | Customer Success |
| Deploy to production | Deploy_Production | Go-Live | 19 | DevOps |
**Query in Flow:**
```apex
[SELECT Label, Section__c, Days_Offset__c, Assignee_Role__c, Description__c
FROM Asana_Task_Template__mdt
ORDER BY Days_Offset__c ASC]
```
---
## Complete Salesforce Flow Example
**Flow Name:** `Asana_Project_Setup`
**Input Variables:**
- `ProjectRecordId` (Text)
- `AsanaProjectGID` (Text) - from Create Project step
**Steps:**
```yaml
1. Get Project Record
- Object: Project__c
- Filter: Id = {!ProjectRecordId}
2. Create Asana Project
- HTTP Callout (as documented)
- Store: varAsanaProjectGID
3. Create Sections
- Loop: ["Discovery", "Architecture", "Build", "Testing", "Go-Live"]
- HTTP Callout per section
- Store GIDs in Map: varSectionGIDs
4. Get Task Templates
- Get Records: Asana_Task_Template__mdt
- Store: varTaskTemplates
5. Loop: Create Tasks
- For Each: varTaskTemplates
- Calculate due date: TODAY() + DaysOffset
- Lookup assignee GID from Asana_User_Mapping__mdt
- HTTP Callout to create task
- Link task to correct section
6. Update Project Record
- Asana_Project_URL__c = {!varAsanaResponse.data.permalink_url}
- Asana_Project_GID__c = {!varAsanaProjectGID}
```
---
## User Mapping Setup
**Custom Metadata Type:** `Asana_User_Mapping__mdt`
**Fields:**
- `Role__c` (Text) - "DevOps", "Backend", "Customer Success", etc.
- `Email__c` (Email)
- `Asana_GID__c` (Text)
**Records:**
| Label | Role__c | Email__c | Asana_GID__c |
|-------|---------|----------|--------------|
| DevOps Team | DevOps | devops@blackroad.com | 2222222222222222 |
| Backend Team | Backend | backend@blackroad.com | 1111111111111111 |
| Customer Success | Customer Success | brenda@blackroad.com | 3333333333333333 |
| QA Team | QA | qa@blackroad.com | 4444444444444444 |
**Query in Flow:**
```apex
[SELECT Asana_GID__c
FROM Asana_User_Mapping__mdt
WHERE Role__c = :assigneeRole
LIMIT 1]
```
---
## Error Handling
### Common Errors
| Status Code | Error | Cause | Solution |
|-------------|-------|-------|----------|
| 400 | Bad Request | Invalid GID or payload | Verify workspace/team GIDs |
| 401 | Unauthorized | Invalid token | Regenerate PAT in Asana |
| 403 | Forbidden | No access to workspace | Check PAT has workspace access |
| 404 | Not Found | Project/task doesn't exist | Verify GID is correct |
| 429 | Too Many Requests | Rate limit exceeded | Implement exponential backoff |
### Salesforce Fault Path
```yaml
Fault Path:
- Element: Create Case
Subject: "Asana Integration Error: {!$Flow.FaultMessage}"
Description: "Failed to create Asana project for: {!varProject.Name}\n\nError: {!$Flow.FaultMessage}"
Priority: Medium
Type: "Automation Bug"
- Element: Send Email
To: ops@blackroad.com
Subject: "Asana Automation Failed - Manual Project Needed"
Body: "Project: {!varProject.Name}\nKey: {!varProject.Project_Key__c}\n\nPlease create Asana project manually."
```
---
## Rate Limits
**Asana API Rate Limits:**
- 1,500 requests per minute per user
- Burst: Up to 60 requests in the first second
**Per Asana Project Creation:**
- 1 request: Create project
- 5 requests: Create sections
- 8-10 requests: Create tasks
- **Total:** ~15 requests
**Best Practices:**
- Can create ~100 projects per minute
- Add delay between operations if hitting limits
- Use exponential backoff on 429 errors
---
## Testing
### Manual Test (curl)
```bash
# 1. Create project
curl -X POST "https://app.asana.com/api/1.0/projects" \
-H "Authorization: Bearer YOUR_PAT" \
-H "Content-Type: application/json" \
-d '{
"data": {
"workspace": "1234567890123456",
"name": "Test Project - DELETE ME"
}
}'
# Response: Get project GID (e.g., 5555555555555555)
# 2. Create section
curl -X POST "https://app.asana.com/api/1.0/projects/5555555555555555/sections" \
-H "Authorization: Bearer YOUR_PAT" \
-d '{"data": {"name": "To Do"}}'
# 3. Create task
curl -X POST "https://app.asana.com/api/1.0/tasks" \
-H "Authorization: Bearer YOUR_PAT" \
-d '{
"data": {
"projects": ["5555555555555555"],
"name": "Test task"
}
}'
# 4. Clean up - delete project
curl -X DELETE "https://app.asana.com/api/1.0/projects/5555555555555555" \
-H "Authorization: Bearer YOUR_PAT"
```
### Salesforce Sandbox Test
1. Create test Project record
2. Run flow: `Asana_Project_Setup`
3. Verify:
- Project created in Asana
- Sections present
- Tasks created with correct assignees
- Due dates calculated correctly
4. Clean up test project in Asana
---
## Monitoring
**Track These Metrics:**
| Metric | Target | Alert Threshold |
|--------|--------|-----------------|
| Asana API Success Rate | > 98% | < 95% |
| Avg Project Creation Time | < 30s | > 60s |
| Failed Project Creations | < 2% | > 5% |
| Tasks Created Per Project | ~8-10 | < 5 or > 15 |
**Salesforce Custom Object:** `Asana_API_Log__c`
**Fields:**
- Operation__c (Create Project, Create Task, etc.)
- Project__c (Lookup)
- Status__c (Success, Failed)
- Response_Time__c
- Error_Message__c
- Timestamp__c
---
## Security Best Practices
1. **PAT Security:**
- Never share PAT
- Use encrypted custom settings
- Rotate every 90 days
- Generate separate PAT per integration (if multiple)
2. **Project Visibility:**
- Set `public: false` for client projects
- Only share with relevant team members
- Review permissions quarterly
3. **Audit Logging:**
- Log all Asana API calls in Salesforce
- Review monthly
- Track who accessed projects
---
## Related Documentation
- [Asana API Docs: Projects](https://developers.asana.com/docs/projects)
- [Asana API Docs: Tasks](https://developers.asana.com/docs/tasks)
- [Salesforce Orchestration: New Client Kickoff](../salesforce/orchestrations/new-client-kickoff-orchestration.md)
- [Workflow: New Client Kickoff](../workflows/new-client-kickoff.md)
- [Integration: Salesforce → GitHub](./salesforce-to-github.md)
---
## Changelog
| Date | Version | Change | Author |
|------|---------|--------|--------|
| 2025-11-17 | 1.0 | Initial specification | Cece (Claude) |

View File

@@ -0,0 +1,539 @@
# Integration: Salesforce → GitHub
**Purpose:** Enable Salesforce to create and configure GitHub repositories automatically
**Direction:** Salesforce calls GitHub REST API
**Authentication:** GitHub App or Personal Access Token
**Status:** Active
**Last Updated:** 2025-11-17
---
## Overview
This integration allows Salesforce Flows and Orchestrations to:
- Create new repositories
- Configure repository settings
- Add labels, branch protection, workflows
- Manage repository secrets
**Key Use Case:** Automatic repo creation when Opportunity moves to "Closed Won"
---
## Authentication Setup
### Option A: GitHub App (Recommended for Production)
**Benefits:**
- More secure (short-lived tokens)
- Better rate limits
- Granular permissions
- Audit trail
**Setup Steps:**
1. **Create GitHub App:**
- Go to: https://github.com/organizations/blackboxprogramming/settings/apps
- Click "New GitHub App"
- Name: `BlackRoad Salesforce Integration`
- Homepage URL: `https://blackroad.app`
- Webhook URL: (leave blank for now, covered in github-to-salesforce.md)
2. **Permissions:**
- Repository permissions:
- Administration: Read & Write
- Contents: Read & Write
- Metadata: Read-only
- Secrets: Read & Write
- Workflows: Read & Write
- Organization permissions:
- Members: Read-only
3. **Install App:**
- Install app on organization: `blackboxprogramming`
- Select: All repositories (or specific repos)
4. **Generate Private Key:**
- Download private key (`.pem` file)
- Store securely in Salesforce
5. **Get App Details:**
- App ID: `123456`
- Installation ID: `789012`
**Salesforce Named Credential:**
- Name: `GitHub_API`
- URL: `https://api.github.com`
- Identity Type: Named Principal
- Authentication Protocol: Custom
- Custom Authentication: Use Apex class to generate JWT → exchange for installation access token
**Apex Class for Token Generation:**
```apex
public class GitHubAppTokenProvider {
private static final String GITHUB_APP_ID = '123456';
private static final String GITHUB_APP_PRIVATE_KEY = 'YOUR_PRIVATE_KEY_HERE'; // Store in Protected Custom Setting
public static String getInstallationToken() {
// 1. Generate JWT
String jwt = generateJWT();
// 2. Exchange JWT for installation access token
HttpRequest req = new HttpRequest();
req.setEndpoint('https://api.github.com/app/installations/789012/access_tokens');
req.setMethod('POST');
req.setHeader('Authorization', 'Bearer ' + jwt);
req.setHeader('Accept', 'application/vnd.github+json');
Http http = new Http();
HttpResponse res = http.send(req);
if (res.getStatusCode() == 201) {
Map<String, Object> result = (Map<String, Object>) JSON.deserializeUntyped(res.getBody());
return (String) result.get('token');
}
throw new CalloutException('Failed to get GitHub access token: ' + res.getBody());
}
private static String generateJWT() {
// Use JWT library or implement JWT generation
// See: https://docs.github.com/en/apps/creating-github-apps/authenticating-with-a-github-app/generating-a-json-web-token-jwt-for-a-github-app
}
}
```
---
### Option B: Personal Access Token (Quick Start / Testing)
**Setup Steps:**
1. **Create PAT:**
- Go to: https://github.com/settings/tokens
- Click "Generate new token (classic)"
- Scopes:
- `repo` (full control)
- `workflow` (update workflows)
- `admin:org` (read org)
2. **Store in Salesforce:**
- Setup → Named Credentials → New
- Name: `GitHub_API`
- URL: `https://api.github.com`
- Identity Type: Named Principal
- Authentication Protocol: Password Authentication
- Username: (your GitHub username)
- Password: (paste PAT)
**⚠️ Security Note:** PAT never expires unless you set an expiration. For production, use GitHub App.
---
## API Endpoints & Payloads
### 1. Create Repository
**Endpoint:**
```
POST https://api.github.com/orgs/blackboxprogramming/repos
```
**Headers:**
```
Authorization: Bearer {GITHUB_TOKEN}
Accept: application/vnd.github+json
X-GitHub-Api-Version: 2022-11-28
Content-Type: application/json
```
**Payload:**
```json
{
"name": "blackroad-ACME-1042-backend",
"description": "Backend for Acme Corp (ACME-1042)",
"private": true,
"auto_init": true,
"gitignore_template": "Python",
"license_template": "mit"
}
```
**Response (201 Created):**
```json
{
"id": 123456789,
"name": "blackroad-ACME-1042-backend",
"full_name": "blackboxprogramming/blackroad-ACME-1042-backend",
"html_url": "https://github.com/blackboxprogramming/blackroad-ACME-1042-backend",
"clone_url": "https://github.com/blackboxprogramming/blackroad-ACME-1042-backend.git",
"default_branch": "main",
...
}
```
**Salesforce Flow Implementation:**
```yaml
Element: HTTP Callout
Method: POST
Endpoint: {!$Credential.GitHub_API}/orgs/blackboxprogramming/repos
Headers:
- Authorization: Bearer {!$Credential.GitHub_API.AccessToken}
- Accept: application/vnd.github+json
- Content-Type: application/json
Body: (JSON from above, with merge fields)
Store Response In: varRepoResponse
Parse:
- varRepoURL = {!varRepoResponse.html_url}
- varRepoName = {!varRepoResponse.name}
```
---
### 2. Create Labels
**Endpoint:**
```
POST https://api.github.com/repos/blackboxprogramming/{REPO_NAME}/labels
```
**Payload (repeat for each label):**
```json
{
"name": "type:feature",
"color": "0E8A16",
"description": "New feature or enhancement"
}
```
**Salesforce Implementation:**
Use a loop to create multiple labels from a JSON dataset:
```yaml
Element: Loop
Collection: Parse JSON from sop/templates/repo-template/.github/labels.json
Current Item: varLabel
Inside Loop:
- HTTP Callout
- Endpoint: {!$Credential.GitHub_API}/repos/blackboxprogramming/{!varRepoName}/labels
- Body: {!varLabel}
```
---
### 3. Apply Branch Protection
**Endpoint:**
```
PUT https://api.github.com/repos/blackboxprogramming/{REPO_NAME}/branches/main/protection
```
**Payload:**
```json
{
"required_status_checks": {
"strict": true,
"contexts": ["test (3.11)", "test (3.12)", "lint", "build"]
},
"enforce_admins": true,
"required_pull_request_reviews": {
"dismiss_stale_reviews": true,
"require_code_owner_reviews": true,
"required_approving_review_count": 1
},
"restrictions": null,
"required_linear_history": true,
"allow_force_pushes": false,
"allow_deletions": false
}
```
**Response (200 OK):**
```json
{
"url": "https://api.github.com/repos/blackboxprogramming/blackroad-ACME-1042-backend/branches/main/protection",
...
}
```
---
### 4. Create Workflow Files
**Endpoint:**
```
PUT https://api.github.com/repos/blackboxprogramming/{REPO_NAME}/contents/.github/workflows/ci.yml
```
**Payload:**
```json
{
"message": "Add CI workflow",
"content": "{BASE64_ENCODED_CI_YML}",
"branch": "main"
}
```
**Salesforce Implementation:**
1. Store workflow YAML templates in Salesforce Static Resources:
- `ci_yml`
- `deploy_yml`
- `safety_yml`
2. For each workflow:
- Load static resource
- Base64 encode content (use Apex)
- PUT to GitHub
**Apex Helper:**
```apex
public static String base64EncodeStaticResource(String resourceName) {
StaticResource sr = [SELECT Body FROM StaticResource WHERE Name = :resourceName LIMIT 1];
return EncodingUtil.base64Encode(sr.Body);
}
```
---
### 5. Add Repository Secrets
**Endpoint:**
```
PUT https://api.github.com/repos/blackboxprogramming/{REPO_NAME}/actions/secrets/{SECRET_NAME}
```
**Payload:**
```json
{
"encrypted_value": "{ENCRYPTED_SECRET}",
"key_id": "{PUBLIC_KEY_ID}"
}
```
**Pre-requisite:** Get repository public key
```
GET https://api.github.com/repos/blackboxprogramming/{REPO_NAME}/actions/secrets/public-key
```
**Response:**
```json
{
"key_id": "012345678912345678",
"key": "BASE64_PUBLIC_KEY"
}
```
**Encryption:**
Use libsodium sealed boxes (NaCl) to encrypt secrets.
**Apex Implementation:** Use external service or pre-encrypted values.
**Secrets to Add:**
| Secret Name | Value Source |
|-------------|--------------|
| `PROJECT_KEY` | From Project__c.Project_Key__c |
| `SALESFORCE_INSTANCE_URL` | From Salesforce |
| `SALESFORCE_ACCESS_TOKEN` | Generate Connected App token |
| `RAILWAY_TOKEN` | From Salesforce Custom Setting |
| `CLOUDFLARE_API_TOKEN` | From Salesforce Custom Setting |
| `ASANA_PAT` | From Salesforce Custom Setting |
---
## Complete Flow Example
**Salesforce Flow: Create GitHub Repos for Project**
```yaml
Flow Name: GitHub_Repo_Setup
Input Variables:
- ProjectRecordId (Text)
Steps:
1. Get Project Record
- Object: Project__c
- Filter: Id = {!ProjectRecordId}
- Store: varProject
2. Loop: For Each Repo Type
- Collection: ["backend", "frontend", "ops"]
- Current Item: varRepoType
2.1: Create Repo
- HTTP Callout (as documented above)
- Store Response: varRepoResponse
2.2: Create Labels
- Loop through labels.json
- HTTP Callout for each label
2.3: Apply Branch Protection
- HTTP Callout (as documented)
2.4: Create Workflow Files
- For each: ci.yml, deploy.yml, safety.yml
- HTTP Callout to create file
2.5: Add Secrets
- Get public key
- Encrypt secrets
- PUT each secret
2.6: Update Project Record
- Assignment:
- Backend_Repo_URL__c = {!varRepoResponse.html_url} (if backend)
- Frontend_Repo_URL__c = {!varRepoResponse.html_url} (if frontend)
- Ops_Repo_URL__c = {!varRepoResponse.html_url} (if ops)
3. Update Project Record with all URLs
- Update Record: Project__c
4. Send Success Notification
- Post to Chatter or send email
```
---
## Error Handling
### Common Errors
| Status Code | Error | Cause | Solution |
|-------------|-------|-------|----------|
| 401 | Unauthorized | Invalid token | Refresh GitHub App token or regenerate PAT |
| 403 | Forbidden | Insufficient permissions | Check GitHub App/PAT scopes |
| 422 | Unprocessable Entity | Repo name already exists | Check for existing repo first |
| 422 | Validation Failed | Branch protection: required check doesn't exist | Create workflow first, then apply protection |
| 404 | Not Found | Repo or resource doesn't exist | Verify repo was created successfully |
### Salesforce Fault Path
```yaml
Fault Path:
- Element: Create Case
Subject: "GitHub Integration Error: {!$Flow.FaultMessage}"
Description: "Failed to create GitHub repo for Project: {!varProject.Name}\n\nError: {!$Flow.FaultMessage}"
Priority: High
OwnerId: {!DevOpsQueueId}
- Element: Send Email
To: devops@blackroad.com
Subject: "GitHub Automation Failed"
Body: (include error details)
```
---
## Testing
### Manual Test (Postman / curl)
```bash
# 1. Create repo
curl -X POST \
https://api.github.com/orgs/blackboxprogramming/repos \
-H "Authorization: token YOUR_PAT" \
-H "Accept: application/vnd.github+json" \
-d '{
"name": "test-repo-delete-me",
"private": true,
"auto_init": true
}'
# 2. Create label
curl -X POST \
https://api.github.com/repos/blackboxprogramming/test-repo-delete-me/labels \
-H "Authorization: token YOUR_PAT" \
-d '{
"name": "type:test",
"color": "BADA55"
}'
# 3. Clean up
curl -X DELETE \
https://api.github.com/repos/blackboxprogramming/test-repo-delete-me \
-H "Authorization: token YOUR_PAT"
```
### Salesforce Sandbox Test
1. Create test Project record
2. Run flow: `GitHub_Repo_Setup` with test Project ID
3. Verify:
- Repos created in GitHub
- Labels applied
- Branch protection enabled
- Workflows present
- Secrets added
4. Clean up test repos
---
## Rate Limits
**GitHub API Rate Limits:**
- PAT: 5,000 requests/hour
- GitHub App: 15,000 requests/hour
**Per Repository Creation:**
- Approximately 50-100 API calls (1 repo + labels + protection + workflows + secrets)
- Can create ~50-300 repos per hour
**Best Practices:**
- Use GitHub App for better limits
- Implement exponential backoff on 403 (rate limit exceeded)
- Cache public keys for secret encryption
- Batch operations where possible
---
## Monitoring
**Track These Metrics:**
| Metric | Target | Alert Threshold |
|--------|--------|-----------------|
| API Success Rate | > 98% | < 95% |
| Avg Response Time | < 2s | > 5s |
| Rate Limit Usage | < 50% | > 80% |
| Failed Repo Creations | < 2% | > 5% |
**Salesforce Custom Object:** `GitHub_API_Log__c`
**Fields:**
- Operation__c (Create Repo, Add Label, etc.)
- Project__c (Lookup)
- Status__c (Success, Failed)
- Status_Code__c (200, 201, 422, etc.)
- Error_Message__c
- Response_Time__c
- Timestamp__c
---
## Related Documentation
- [GitHub API Docs: Repositories](https://docs.github.com/en/rest/repos/repos)
- [GitHub API Docs: Branch Protection](https://docs.github.com/en/rest/branches/branch-protection)
- [GitHub API Docs: Secrets](https://docs.github.com/en/rest/actions/secrets)
- [Salesforce Orchestration: New Client Kickoff](../salesforce/orchestrations/new-client-kickoff-orchestration.md)
- [Workflow: New Client Kickoff](../workflows/new-client-kickoff.md)
---
## Changelog
| Date | Version | Change | Author |
|------|---------|--------|--------|
| 2025-11-17 | 1.0 | Initial specification | Cece (Claude) |