Files
hermes-skills/skills/software-development/debt-recovery-platform/references/twentycrm-custom-data-model.md
T

8.5 KiB

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

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:

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:

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

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)

{
  "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

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

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\"]}')
"