250 lines
8.5 KiB
Markdown
250 lines
8.5 KiB
Markdown
# 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\"]}')
|
|
"
|
|
```
|