# NexusCRM API Read Access Guide

> **For**: Sales team members who need read-only API access to Account, Lead, Opportunity, and Task data for analysis.
>
> **What you'll get**: A token that can read CRM data via REST API. You cannot create, edit, or delete any records.

---

## Quick Start (5 minutes)

### Step 1: Get Your Token

Your admin will give you two credentials:
- **Consumer Key** (client_id)
- **Consumer Secret** (client_secret)

Store them securely. Do NOT commit them to Git, share in public channels, or store in plain text files.

Request a token:

```bash
curl -X POST https://nexuscrm.pingcap.com/services/oauth2/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=client_credentials" \
  -d "client_id=YOUR_CONSUMER_KEY" \
  -d "client_secret=YOUR_CONSUMER_SECRET"
```

Response:
```json
{
  "access_token": "eyJhbGciOiJIUzI1NiIs...",
  "token_type": "Bearer",
  "instance_url": "https://nexuscrm.pingcap.com"
}
```

Save the `access_token`. Use it in all subsequent API calls.

### Step 2: Test Your Connection

```bash
# Check who you are
curl https://nexuscrm.pingcap.com/services/oauth2/userinfo \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"

# Quick test: list 3 Accounts
curl "https://nexuscrm.pingcap.com/services/data/v63.0/query?q=SELECT+Id,Name+FROM+Account+LIMIT+3" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
```

---

## Reading Data

All API calls use the same pattern:
```
GET https://nexuscrm.pingcap.com/services/data/v63.0/query?q=<SOQL>
Authorization: Bearer YOUR_ACCESS_TOKEN
```

### Accounts

```bash
# All Accounts with key fields
curl "https://nexuscrm.pingcap.com/services/data/v63.0/query?q=$(python3 -c "import urllib.parse; print(urllib.parse.quote(\"SELECT Id, Name, Industry, BillingCountry, AnnualRevenue, OwnerId FROM Account ORDER BY AnnualRevenue DESC LIMIT 100\"))")" \
  -H "Authorization: Bearer $TOKEN"
```

**Useful Account fields:**
| Field | Type | Description |
|-------|------|-------------|
| `Id` | string | Unique identifier |
| `Name` | string | Account name |
| `Industry` | string | Industry classification |
| `BillingCountry` | string | Country |
| `AnnualRevenue` | number | Annual revenue (USD) |
| `OwnerId` | string | Account owner (User ID) |
| `CreatedDate` | datetime | When the account was created |
| `Segmentation__c` | string | Customer segmentation (KA, Non-KA, SKA) |
| `Region__c` | string | Sales region (APAC, NA, EMEA, etc.) |

### Leads

```bash
# Open Leads with status and source
curl "https://nexuscrm.pingcap.com/services/data/v63.0/query?q=$(python3 -c "import urllib.parse; print(urllib.parse.quote(\"SELECT Id, Name, Company, Status, LeadSource, Email, OwnerId, CreatedDate FROM Lead WHERE Status != 'Converted' ORDER BY CreatedDate DESC LIMIT 200\"))")" \
  -H "Authorization: Bearer $TOKEN"
```

**Useful Lead fields:**
| Field | Type | Description |
|-------|------|-------------|
| `Name` | string | Lead name |
| `Company` | string | Company name |
| `Status` | picklist | Open, Contacted, Qualified, Converted |
| `LeadSource` | picklist | Web, Referral, Partner, etc. |
| `Email` | string | Contact email |
| `OwnerId` | string | Lead owner |
| `Region__c` | string | Sales region |

### Opportunities

```bash
# Pipeline: open Opportunities with amounts
curl "https://nexuscrm.pingcap.com/services/data/v63.0/query?q=$(python3 -c "import urllib.parse; print(urllib.parse.quote(\"SELECT Id, Name, StageName, Amount, CloseDate, Probability, AccountId, OwnerId, ARR_Amount_USD__c, Launch_Date__c FROM Opportunity WHERE IsClosed = false ORDER BY Amount DESC LIMIT 200\"))")" \
  -H "Authorization: Bearer $TOKEN"
```

**Useful Opportunity fields:**
| Field | Type | Description |
|-------|------|-------------|
| `Name` | string | Opportunity name |
| `StageName` | picklist | Discovery, Proposal, Negotiation, Closed Won/Lost |
| `Amount` | currency | Deal value |
| `ARR_Amount_USD__c` | number | Net new ARR (USD) |
| `CloseDate` | date | Expected close date |
| `Probability` | percent | Win probability |
| `AccountId` | reference | Related Account |
| `OwnerId` | string | Opportunity owner |
| `Launch_Date__c` | date | Expected launch date |

### Tasks (Activities)

```bash
# Recent Tasks
curl "https://nexuscrm.pingcap.com/services/data/v63.0/query?q=$(python3 -c "import urllib.parse; print(urllib.parse.quote(\"SELECT Id, Subject, Status, Priority, OwnerId, WhoId, WhatId, ActivityDate FROM Task ORDER BY ActivityDate DESC LIMIT 100\"))")" \
  -H "Authorization: Bearer $TOKEN"
```

---

## Common Analysis Queries

### Pipeline Summary by Stage

```sql
SELECT StageName, COUNT(Id), SUM(Amount)
FROM Opportunity
WHERE IsClosed = false
GROUP BY StageName
```

### Leads by Source (this quarter)

```sql
SELECT LeadSource, COUNT(Id)
FROM Lead
WHERE CreatedDate >= THIS_QUARTER
GROUP BY LeadSource
```

### Account Revenue Distribution

```sql
SELECT Industry, COUNT(Id), SUM(AnnualRevenue)
FROM Account
WHERE AnnualRevenue > 0
GROUP BY Industry
ORDER BY SUM(AnnualRevenue) DESC
```

### Sales Team Pipeline

```sql
SELECT OwnerId, COUNT(Id), SUM(Amount)
FROM Opportunity
WHERE IsClosed = false
GROUP BY OwnerId
ORDER BY SUM(Amount) DESC
```

> **Note**: `OwnerId` returns a User ID. To get the owner's name, you'll need a separate query:
> `SELECT Id, Name, Email FROM User WHERE Id IN ('id1', 'id2', ...)`

---

## Python Example

```python
import requests

BASE_URL = "https://nexuscrm.pingcap.com"
CLIENT_ID = "your_consumer_key"
CLIENT_SECRET = "your_consumer_secret"

# Step 1: Get token
token_resp = requests.post(f"{BASE_URL}/services/oauth2/token", data={
    "grant_type": "client_credentials",
    "client_id": CLIENT_ID,
    "client_secret": CLIENT_SECRET,
})
token = token_resp.json()["access_token"]
headers = {"Authorization": f"Bearer {token}"}

# Step 2: Query data
def query(soql):
    resp = requests.get(f"{BASE_URL}/services/data/v63.0/query",
                       params={"q": soql}, headers=headers)
    data = resp.json()
    records = data.get("records", [])
    print(f"Total: {data.get('totalSize', 0)} records")
    return records

# Examples
accounts = query("SELECT Id, Name, Industry, AnnualRevenue FROM Account LIMIT 10")
leads = query("SELECT Id, Name, Company, Status FROM Lead WHERE Status = 'Open' LIMIT 10")
pipeline = query("""
    SELECT Id, Name, StageName, Amount, CloseDate, OwnerId
    FROM Opportunity
    WHERE IsClosed = false
    ORDER BY Amount DESC
    LIMIT 50
""")

# Print pipeline summary
for opp in pipeline:
    print(f"  {opp['Name']}: ${opp.get('Amount', 0):,.0f} - {opp['StageName']}")
```

---

## Pagination (for large datasets)

If your query returns more than 2000 records, the response includes a `nextRecordsUrl`:

```json
{
  "totalSize": 50000,
  "done": false,
  "nextRecordsUrl": "/services/data/v63.0/query/01g5g000003...",
  "records": [...]
}
```

Fetch the next page:
```bash
curl "https://nexuscrm.pingcap.com/services/data/v63.0/query/01g5g000003..." \
  -H "Authorization: Bearer $TOKEN"
```

Repeat until `"done": true`.

Python helper:
```python
def query_all(soql):
    """Fetch all pages of a SOQL query."""
    all_records = []
    resp = requests.get(f"{BASE_URL}/services/data/v63.0/query",
                       params={"q": soql}, headers=headers).json()
    all_records.extend(resp.get("records", []))
    
    while not resp.get("done", True):
        next_url = resp["nextRecordsUrl"]
        resp = requests.get(f"{BASE_URL}{next_url}", headers=headers).json()
        all_records.extend(resp.get("records", []))
    
    return all_records

# Get ALL Leads (handles pagination automatically)
all_leads = query_all("SELECT Id, Name, Company, Status FROM Lead")
print(f"Total leads: {len(all_leads)}")
```

---

## Limits & Best Practices

| Limit | Value | Notes |
|-------|-------|-------|
| Records per query page | 2000 | Use pagination for larger datasets |
| API calls per hour | Varies | Check `/services/data/v63.0/limits` |
| Token lifetime | Session-based | Re-authenticate if token expires |

**Best practices:**
1. **Cache your token** — don't request a new one for every API call
2. **Use LIMIT** — always limit your queries. Start with `LIMIT 100`, increase as needed
3. **Select specific fields** — avoid `SELECT *`. List only the fields you need
4. **Use WHERE clauses** — filter server-side, don't fetch everything and filter locally
5. **Respect rate limits** — if you get 429, back off and retry
6. **Store credentials securely** — use environment variables, not hardcoded values:
   ```bash
   export NEXUS_CLIENT_ID="your_key"
   export NEXUS_CLIENT_SECRET="your_secret"
   ```

---

## Troubleshooting

| Error | Cause | Fix |
|-------|-------|-----|
| `invalid_client` | Wrong ConsumerKey or ConsumerSecret | Double-check credentials |
| `access_denied` | User not authorized for this ConnectedApp | Ask admin to check PermittedUsers |
| `INSUFFICIENT_ACCESS` | No Read permission on the object | Your profile may not have access to this object |
| `MALFORMED_QUERY` | SOQL syntax error | Check field names, quotes, operators |
| `REQUEST_LIMIT_EXCEEDED` | Too many API calls | Slow down, add delays between calls |
| Token expired | Session timed out | Request a new token |

---

## What You CAN and CANNOT Do

| ✅ Can Do | ❌ Cannot Do |
|-----------|-------------|
| Read Accounts, Leads, Opportunities, Tasks | Create new records |
| Run SOQL queries | Edit existing records |
| Export data for analysis | Delete records |
| View reports via API | Access Setup/Admin pages |
| Paginate through large datasets | Run Flows or Approval actions |

This is by design — your access is scoped to `api.read` for data analysis only.

---

## Support

- **API issues**: Contact your NexusCRM administrator
- **Token not working**: Re-request credentials from admin
- **Need write access**: Discuss with admin — a different ConnectedApp with broader scope would be needed
