# TwentyCRM DRE Custom Data Model

Created 2026-07-08 via `https://crm.debtrecoveryexperts.com/rest/metadata/` REST API.
Auth: Bearer token from `TWENTY_CRM_API_KEY` in `~/.hermes/.env`.

## Objects

### 1. Debtors (`debtor`/`debtors`)
- **Icon:** IconUsers
- **Label Identifier:** Name (auto-created TEXT field)
- **Custom fields:**
  | Field | Type | Icon | Description |
  |-------|------|------|-------------|
  | contactInfo | TEXT | IconPhone | Primary contact phone number |
  | email | TEXT | IconMail | Primary email address |
  | physicalAddress | TEXT | IconMapPin | Physical address |
  | businessType | SELECT | IconBuildingStore | INDIVIDUAL, SOLE_PROPRIETORSHIP, LLC, CORPORATION, PARTNERSHIP, OTHER |

### 2. Claims (`claim`/`claims`)
- **Icon:** IconFiles
- **Label Identifier:** Name (auto-created TEXT field)
- **Custom fields:**
  | Field | Type | Icon | Description |
  |-------|------|------|-------------|
  | claimNumber | TEXT (unique) | IconHash | DRE-YYYY-NNNN format |
  | amount | CURRENCY | IconCurrencyDollar | Claim amount in USD |
  | status | SELECT | IconStatusChange | NEW, ACTIVE, NEGOTIATION, LEGAL, SETTLED, CLOSED, WRITE_OFF |
  | tier | SELECT | IconLayersSelected | TIER_1, TIER_2, TIER_3 |
  | clientReference | TEXT | IconFileDescription | Client reference number |
  | dateAssigned | DATE_TIME | IconCalendarPlus | Date claim was assigned |
  | dateResolved | DATE_TIME | IconCalendarCheck | Date claim was resolved |

### 3. Case Notes (`caseNote`/`caseNotes`)
- **Icon:** IconNotes
- **Label Identifier:** Name (auto-created TEXT field)
- **Custom fields:**
  | Field | Type | Icon | Description |
  |-------|------|------|-------------|
  | content | RICH_TEXT | IconFileText | Note content |
  | author | TEXT | IconUser | Note author name |

### 4. Payments (`payment`/`payments`)
- **Icon:** IconReceipt
- **Label Identifier:** Name (auto-created TEXT field)
- **Custom fields:**
  | Field | Type | Icon | Description |
  |-------|------|------|-------------|
  | paymentAmount | CURRENCY | IconCurrencyDollar | Amount received |
  | payer | TEXT | IconUser | Name of payer |
  | paymentDate | DATE_TIME | IconCalendar | Date payment received |
  | paymentMethod | SELECT | IconCreditCard | CHECK, WIRE_TRANSFER, CREDIT_CARD, CASH, ACH, OTHER |

## REST API Field Creation Pattern

```bash
API_KEY=$(grep TWENTY_CRM_API_KEY ~/.hermes/.env | cut -d= -f2)

# Create object
curl -s "https://crm.debtrecoveryexperts.com/rest/metadata/objects" \
  -X POST \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $API_KEY" \
  -d '{
    "nameSingular": "myObject",
    "namePlural": "myObjects",
    "labelSingular": "My Object",
    "labelPlural": "My Objects",
    "description": "...",
    "icon": "IconFiles",
    "isSearchable": true,
    "isActive": true,
    "isLabelSyncedWithName": true
  }'
```

**Create a regular field:**
```bash
curl -s "https://crm.debtrecoveryexperts.com/rest/metadata/fields" \
  -X POST \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $API_KEY" \
  -d '{
    "name": "fieldName",
    "label": "Field Name",
    "description": "...",
    "type": "TEXT",
    "icon": "IconMail",
    "isNullable": true,
    "isUnique": false,
    "objectMetadataId": "'"$OBJECT_ID"'"
  }'
```

**Create a SELECT field with options:**
```bash
curl -s "https://crm.debtrecoveryexperts.com/rest/metadata/fields" \
  -X POST \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $API_KEY" \
  -d '{
    "name": "status",
    "label": "Status",
    "description": "...",
    "type": "SELECT",
    "icon": "IconStatusChange",
    "isNullable": true,
    "isUnique": false,
    "objectMetadataId": "'"$OBJECT_ID"'",
    "options": [
      {"label": "New", "value": "NEW", "color": "blue", "position": 0},
      {"label": "Active", "value": "ACTIVE", "color": "turquoise", "position": 1}
    ]
  }'
```

**Available Colors for SELECT options:** blue, turquoise, green, yellow, orange, red, purple, pink, grey, sky

## Relations (MANY_TO_ONE → ONE_TO_MANY)

**IMPORTANT:** The REST API endpoint `POST /rest/metadata/fields` does NOT accept `relationCreationPayload` — it returns `"Relation creation payload is required"`. Relations MUST be created via the **GraphQL mutation endpoint** at `POST https://crm.debtrecoveryexperts.com/metadata`.

### GraphQL Mutation for RELATION Fields

```graphql
mutation CreateOneField($input: CreateOneFieldMetadataInput!) {
  createOneField(input: $input) {
    id
    name
    label
    type
    settings
    relation {
      type
      sourceObjectMetadata { id nameSingular namePlural }
      targetObjectMetadata { id nameSingular namePlural }
      sourceFieldMetadata { id name }
      targetFieldMetadata { id name }
    }
    object { id nameSingular }
  }
}
```

### Variables (JSON)

```json
{
  "input": {
    "field": {
      "name": "debtor",
      "label": "Debtor",
      "type": "RELATION",
      "objectMetadataId": "<SOURCE_OBJECT_UUID>",
      "relationCreationPayload": {
        "type": "MANY_TO_ONE",
        "targetObjectMetadataId": "<TARGET_OBJECT_UUID>",
        "targetFieldLabel": "Claims",
        "targetFieldIcon": "IconFileDescription"
      }
    }
  }
}
```

### `relationCreationPayload` Fields

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `type` | `"MANY_TO_ONE"` \| `"ONE_TO_MANY"` | ✅ | Direction of the relation |
| `targetObjectMetadataId` | UUID string | ✅ | The object this relation points to |
| `targetFieldLabel` | string (max 63 chars) | ✅ | Label for the auto-generated reverse field on the target object |
| `targetFieldIcon` | string | ✅ | Icon name for the reverse field (e.g. `"IconCurrencyDollar"`) |

### How It Works

**MANY_TO_ONE** field on SourceObject → TargetObject:
- A foreign-key column (e.g. `debtorId`) is **automatically created** on the SourceObject table
- A reverse **ONE_TO_MANY** relation field is **automatically created** on the TargetObject
- `targetFieldLabel` and `targetFieldIcon` control the reverse field's label/icon
- The source-side `settings` will include `{"joinColumnName": "debtorId"}`

**ONE_TO_MANY** field on SourceObject → TargetObject:
- **No** join column on SourceObject (the FK lives on the other side)
- A reverse **MANY_TO_ONE** relation field (with join column) is created on TargetObject

### curl Example

```bash
API_KEY=$(grep TWENTY_CRM_API_KEY ~/.hermes/.env | cut -d= -f2)

# Create a MANY_TO_ONE relation: Payments → Claim
curl -s "https://crm.debtrecoveryexperts.com/metadata" \
  -X POST \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $API_KEY" \
  -d '{
    "query": "mutation CreateOneField($input: CreateOneFieldMetadataInput!) { createOneField(input: $input) { id name type relation { type targetFieldMetadata { id name } targetObjectMetadata { id nameSingular } } } }",
    "variables": {
      "input": {
        "field": {
          "name": "claim",
          "label": "Claim",
          "type": "RELATION",
          "objectMetadataId": "PAYMENTS_OBJECT_UUID",
          "relationCreationPayload": {
            "type": "MANY_TO_ONE",
            "targetObjectMetadataId": "CLAIMS_OBJECT_UUID",
            "targetFieldLabel": "Payments",
            "targetFieldIcon": "IconCurrencyDollar"
          }
        }
      }
    }
  }'
```

### DRE Data Model Relations Table

| Source Object | Field | Relation Type | Target Object | Reverse Field |
|---------------|-------|---------------|---------------|---------------|
| Claims | `debtor` (MANY_TO_ONE) | MANY_TO_ONE | Debtors | `claims` (ONE_TO_MANY) |
| CaseNotes | `claim` (MANY_TO_ONE) | MANY_TO_ONE | Claims | `caseNotes` (ONE_TO_MANY) |
| Payments | `claim` (MANY_TO_ONE) | MANY_TO_ONE | Claims | `payments` (ONE_TO_MANY) |

## Reserved Field Names

These names trigger `INVALID_FIELD_INPUT: This name is reserved`:
- `address` (and likely other system-level names)

Use alternatives like `physicalAddress`, `mailingAddress`, etc.

## Verification Command

```bash
API_KEY=$(grep TWENTY_CRM_API_KEY ~/.hermes/.env | cut -d= -f2)
curl -s "https://crm.debtrecoveryexperts.com/rest/metadata/objects" \
  -H "Authorization: Bearer $API_KEY" > /tmp/all_objects.json
python3 -c "
import json, sys
data = json.load(open('/tmp/all_objects.json'))
for obj in data.get('data', []):
    if not obj['isSystem']:
        print(f'\\n## {obj[\"labelSingular\"]} ({obj[\"nameSingular\"]})')
        print(f'  ID: {obj[\"id\"]}')
        for f in obj.get('fields', []):
            if not f['isSystem']:
                print(f'    - {f[\"label\"]} ({f[\"name\"]}) type={f[\"type\"]}')
"
```
