Skip to content

Types

Stable types import path.

This module re-exports ID types, enums, and constants from affinity.models.types.

CompanyId

Bases: IntId

Called Organization in V1.

Source code in affinity/models/types.py
44
45
class CompanyId(IntId):
    """Called Organization in V1."""

DropdownOptionColor

Bases: IntEnum

Colors for dropdown options.

Affinity uses integer color codes for dropdown field options.

Source code in affinity/models/types.py
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
class DropdownOptionColor(IntEnum):
    """
    Colors for dropdown options.

    Affinity uses integer color codes for dropdown field options.
    """

    DEFAULT = 0
    BLUE = 1
    GREEN = 2
    YELLOW = 3
    ORANGE = 4
    RED = 5
    PURPLE = 6
    GRAY = 7

EnrichedFieldId

Bases: StrId

Enriched field IDs are strings in V2 (e.g., 'affinity-data-description').

Source code in affinity/models/types.py
192
193
class EnrichedFieldId(StrId):
    """Enriched field IDs are strings in V2 (e.g., 'affinity-data-description')."""

EntityType

Bases: OpenIntEnum

Entity types in Affinity.

Source code in affinity/models/types.py
292
293
294
295
296
297
class EntityType(OpenIntEnum):
    """Entity types in Affinity."""

    PERSON = 0
    ORGANIZATION = 1
    OPPORTUNITY = 8

FieldId

Bases: StrId

V2-style field id (e.g. 'field-123').

FieldId provides normalized comparison semantics: - FieldId(123) == FieldId("123") → True - FieldId("field-123") == "field-123" → True - FieldId("field-123") == 123 → True

This normalization is specific to FieldId because field IDs uniquely come from mixed sources (some APIs return integers, some return strings like "field-123"). Other TypedId subclasses (PersonId, CompanyId, etc.) don't have this problem - they consistently use integers.

Source code in affinity/models/types.py
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
class FieldId(StrId):
    """
    V2-style field id (e.g. 'field-123').

    FieldId provides normalized comparison semantics:
    - ``FieldId(123) == FieldId("123")`` → ``True``
    - ``FieldId("field-123") == "field-123"`` → ``True``
    - ``FieldId("field-123") == 123`` → ``True``

    This normalization is specific to FieldId because field IDs uniquely come
    from mixed sources (some APIs return integers, some return strings like
    "field-123"). Other TypedId subclasses (PersonId, CompanyId, etc.) don't
    have this problem - they consistently use integers.
    """

    def __new__(cls, value: Any) -> FieldId:
        """Normalize value to 'field-xxx' format at construction time."""
        if isinstance(value, cls):
            return value
        if isinstance(value, bool):  # an int subclass: would build 'field-True'
            raise ValueError("FieldId must be an int, digits, or 'field-<digits>'")
        if isinstance(value, int):
            return str.__new__(cls, f"field-{value}")
        if isinstance(value, str):
            candidate = value.strip()
            if candidate.isdigit():
                return str.__new__(cls, f"field-{candidate}")
            if _FIELD_ID_RE.match(candidate):
                return str.__new__(cls, candidate)
        raise ValueError("FieldId must be an int, digits, or 'field-<digits>'")

    @classmethod
    def __get_pydantic_core_schema__(
        cls, source_type: Any, handler: GetCoreSchemaHandler
    ) -> CoreSchema:
        _ = source_type
        _ = handler

        def validate(value: Any) -> FieldId:
            # Use __new__ which handles all normalization
            return cls(value)

        return core_schema.no_info_plain_validator_function(validate)

    def __eq__(self, other: object) -> bool:
        """
        Normalize comparison for FieldId.

        Supports comparison with:
        - Other FieldId instances
        - Strings (e.g., "field-123" or "123")
        - Integers (e.g., 123)
        """
        if isinstance(other, FieldId):
            # Both are FieldId - compare string representations
            return str.__eq__(self, other)
        if isinstance(other, str):
            # Compare with string - could be "field-123" or "123"
            try:
                other_normalized = FieldId(other)
                return str.__eq__(self, other_normalized)
            except ValueError:
                return False
        if isinstance(other, int):
            # Compare with integer
            return str.__eq__(self, f"field-{other}")
        return NotImplemented

    def __hash__(self) -> int:
        """Hash the string representation for dict/set usage."""
        return str.__hash__(self)

    def __repr__(self) -> str:
        """Return a representation useful for debugging."""
        return f"FieldId({str.__repr__(self)})"

    def __str__(self) -> str:
        """Return the canonical string value (e.g., 'field-123')."""
        return str.__str__(self)

__eq__(other: object) -> bool

Normalize comparison for FieldId.

Supports comparison with: - Other FieldId instances - Strings (e.g., "field-123" or "123") - Integers (e.g., 123)

Source code in affinity/models/types.py
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
def __eq__(self, other: object) -> bool:
    """
    Normalize comparison for FieldId.

    Supports comparison with:
    - Other FieldId instances
    - Strings (e.g., "field-123" or "123")
    - Integers (e.g., 123)
    """
    if isinstance(other, FieldId):
        # Both are FieldId - compare string representations
        return str.__eq__(self, other)
    if isinstance(other, str):
        # Compare with string - could be "field-123" or "123"
        try:
            other_normalized = FieldId(other)
            return str.__eq__(self, other_normalized)
        except ValueError:
            return False
    if isinstance(other, int):
        # Compare with integer
        return str.__eq__(self, f"field-{other}")
    return NotImplemented

__hash__() -> int

Hash the string representation for dict/set usage.

Source code in affinity/models/types.py
131
132
133
def __hash__(self) -> int:
    """Hash the string representation for dict/set usage."""
    return str.__hash__(self)

__new__(value: Any) -> FieldId

Normalize value to 'field-xxx' format at construction time.

Source code in affinity/models/types.py
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
def __new__(cls, value: Any) -> FieldId:
    """Normalize value to 'field-xxx' format at construction time."""
    if isinstance(value, cls):
        return value
    if isinstance(value, bool):  # an int subclass: would build 'field-True'
        raise ValueError("FieldId must be an int, digits, or 'field-<digits>'")
    if isinstance(value, int):
        return str.__new__(cls, f"field-{value}")
    if isinstance(value, str):
        candidate = value.strip()
        if candidate.isdigit():
            return str.__new__(cls, f"field-{candidate}")
        if _FIELD_ID_RE.match(candidate):
            return str.__new__(cls, candidate)
    raise ValueError("FieldId must be an int, digits, or 'field-<digits>'")

__repr__() -> str

Return a representation useful for debugging.

Source code in affinity/models/types.py
135
136
137
def __repr__(self) -> str:
    """Return a representation useful for debugging."""
    return f"FieldId({str.__repr__(self)})"

__str__() -> str

Return the canonical string value (e.g., 'field-123').

Source code in affinity/models/types.py
139
140
141
def __str__(self) -> str:
    """Return the canonical string value (e.g., 'field-123')."""
    return str.__str__(self)

FieldType

Bases: OpenStrEnum

Field type for API parameters and response metadata.

Note: LIST is only valid in requests to list entry endpoints. Company/person endpoints accept ENRICHED, GLOBAL, and RELATIONSHIP_INTELLIGENCE only. HIDDEN is response-only: from API version 2026-07-15, fields the user may not see (restricted opportunity fields) come back as type: "hidden" with a null value; it is never a valid request value.

Source code in affinity/models/types.py
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
class FieldType(OpenStrEnum):
    """
    Field type for API parameters and response metadata.

    Note: LIST is only valid in requests to list entry endpoints.
    Company/person endpoints accept ENRICHED, GLOBAL, and
    RELATIONSHIP_INTELLIGENCE only. HIDDEN is response-only: from API version 2026-07-15,
    fields the user may not see (restricted opportunity fields) come back as
    ``type: "hidden"`` with a null value; it is never a valid request value.
    """

    ENRICHED = "enriched"
    LIST = "list"
    GLOBAL = "global"
    RELATIONSHIP_INTELLIGENCE = "relationship-intelligence"
    HIDDEN = "hidden"

FieldValueChangeAction

Bases: OpenIntEnum

Types of changes that can occur to field values.

Source code in affinity/models/types.py
608
609
610
611
612
613
class FieldValueChangeAction(OpenIntEnum):
    """Types of changes that can occur to field values."""

    CREATE = 0
    DELETE = 1
    UPDATE = 2

FieldValueType

Bases: OpenStrEnum

Field value types (V2-first).

V2 represents valueType as strings (e.g. "dropdown-multi", "ranked-dropdown", "interaction"). V1 represents field value types as numeric codes; numeric inputs are normalized into the closest V2 string type where possible.

Source code in affinity/models/types.py
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
class FieldValueType(OpenStrEnum):
    """
    Field value types (V2-first).

    V2 represents `valueType` as strings (e.g. "dropdown-multi", "ranked-dropdown", "interaction").
    V1 represents field value types as numeric codes; numeric inputs are normalized into the closest
    V2 string type where possible.
    """

    TEXT = "text"

    NUMBER = "number"
    NUMBER_MULTI = "number-multi"

    DATETIME = "datetime"  # V2 canonical (V1 docs call this "Date")

    LOCATION = "location"
    LOCATION_MULTI = "location-multi"

    DROPDOWN = "dropdown"
    DROPDOWN_MULTI = "dropdown-multi"
    RANKED_DROPDOWN = "ranked-dropdown"

    PERSON = "person"
    PERSON_MULTI = "person-multi"

    COMPANY = "company"  # V1 calls this "organization"
    COMPANY_MULTI = "company-multi"

    FILTERABLE_TEXT = "filterable-text"
    FILTERABLE_TEXT_MULTI = "filterable-text-multi"

    INTERACTION = "interaction"  # V2-only (relationship-intelligence)

    # V2-only, computed by Affinity (no V1 code; not creatable through the API)
    FORMULA_NUMBER = "formula-number"  # value data: {"calculatedValue": number | null}
    # API version 2026-09-17+
    LIST_MULTI = "list-multi"  # the lists an entity is on (ListData[], with totalCount)
    NOTE = "note"  # the entity's latest note (notes.BaseNote | null)
    REMINDER = "reminder"  # the entity's next uncompleted reminder (ReminderData | null)

    @classmethod
    def _missing_(cls, value: object) -> OpenStrEnum:
        # Normalize known V1 numeric codes to canonical V2 strings.
        # V1 API field value types :
        #   0 = Person, 1 = Organization, 2 = Dropdown (simple),
        #   3 = Number, 4 = Date, 5 = Location,
        #   6 = Text (long text block), 7 = Ranked Dropdown (with colors),
        #   10 = Filterable Text
        if isinstance(value, bool):  # an int subclass: True would read as 1 (company)
            raise ValueError(f"{value!r} is not a valid {cls.__qualname__}")
        if isinstance(value, int):
            mapping: dict[int, FieldValueType] = {
                0: cls.PERSON,
                1: cls.COMPANY,
                2: cls.DROPDOWN,  # V1 "Dropdown" (simple, free text)
                3: cls.NUMBER,
                4: cls.DATETIME,
                5: cls.LOCATION,
                6: cls.TEXT,  # V1 "Text" (long text block)
                7: cls.RANKED_DROPDOWN,  # V1 "Ranked Dropdown" (with colors)
                10: cls.FILTERABLE_TEXT,
            }
            if value in mapping:
                return mapping[value]

            # Keep "unknown numeric" inputs stable by caching under the int key as well.
            text = str(value)
            existing = cls._value2member_map_.get(text)
            if existing is not None:
                existing_enum = cast(OpenStrEnum, existing)
                cls._value2member_map_[value] = existing_enum
                return existing_enum
            created = super()._missing_(text)
            cls._value2member_map_[value] = created
            return created

        if isinstance(value, str):
            text = value.strip()
            lowered = text.lower()
            if lowered == "date":
                return cls.DATETIME
            if lowered in ("organization", "organisation"):
                return cls.COMPANY
            if lowered in ("organization-multi", "organisation-multi"):
                return cls.COMPANY_MULTI
            if lowered == "filterable_text":
                return cls.FILTERABLE_TEXT

        return super()._missing_(value)

InteractionDirection

Bases: OpenIntEnum

Direction of communication for interactions.

Source code in affinity/models/types.py
507
508
509
510
511
class InteractionDirection(OpenIntEnum):
    """Direction of communication for interactions."""

    OUTGOING = 0
    INCOMING = 1

InteractionLoggingType

Bases: OpenIntEnum

How the interaction was logged.

Source code in affinity/models/types.py
514
515
516
517
518
class InteractionLoggingType(OpenIntEnum):
    """How the interaction was logged."""

    AUTOMATIC = 0
    MANUAL = 1

InteractionType

Bases: OpenIntEnum

Types of interactions.

Source code in affinity/models/types.py
498
499
500
501
502
503
504
class InteractionType(OpenIntEnum):
    """Types of interactions."""

    MEETING = 0  # Also called Event
    CALL = 1
    CHAT_MESSAGE = 2
    EMAIL = 3

ListRole

Bases: OpenIntEnum

Roles for list-level permissions.

Source code in affinity/models/types.py
600
601
602
603
604
605
class ListRole(OpenIntEnum):
    """Roles for list-level permissions."""

    ADMIN = 0
    BASIC = 1
    STANDARD = 2

ListType

Bases: OpenIntEnum

Type of entities a list can contain.

Source code in affinity/models/types.py
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
class ListType(OpenIntEnum):
    """Type of entities a list can contain."""

    PERSON = 0
    COMPANY = 1
    OPPORTUNITY = 8

    # V1 compatibility alias - prefer COMPANY in new code
    ORGANIZATION = COMPANY

    @classmethod
    def _missing_(cls, value: object) -> OpenIntEnum:
        # V2 list endpoints commonly return string types (e.g. "company").
        if isinstance(value, str):
            text = value.strip().lower()
            if text in ("person", "people"):
                return cls.PERSON
            if text in ("company", "organization", "organisation"):
                return cls.COMPANY
            if text in ("opportunity", "opportunities"):
                return cls.OPPORTUNITY
        return super()._missing_(value)

NoteKind

Bases: OpenStrEnum

Kind of a note as reported by V2 note search (note.kind).

Source code in affinity/models/types.py
589
590
591
592
593
594
595
596
597
class NoteKind(OpenStrEnum):
    """Kind of a note as reported by V2 note search (``note.kind``)."""

    NOTE = "note"
    MEETING_NOTE = "meeting-note"
    EMAIL_NOTE = "email-note"
    AI_SUMMARY = "ai-summary"
    MEETING_AI_SUMMARY = "meeting-ai-summary"
    CHAT_MESSAGE_NOTE = "chat-message-note"

NoteType

Bases: OpenIntEnum

Types of notes.

Source code in affinity/models/types.py
580
581
582
583
584
585
586
class NoteType(OpenIntEnum):
    """Types of notes."""

    PLAIN_TEXT = 0
    EMAIL_DERIVED = 1  # Deprecated creation method
    HTML = 2
    AI_NOTETAKER = 3

PersonType

Bases: OpenStrEnum

Types of persons in Affinity.

Source code in affinity/models/types.py
300
301
302
303
304
305
class PersonType(OpenStrEnum):
    """Types of persons in Affinity."""

    INTERNAL = "internal"
    EXTERNAL = "external"
    COLLABORATOR = "collaborator"

ReminderResetType

Bases: OpenStrEnum

What resets a recurring reminder (V2 resetTrigger).

MEETING is a deprecated alias of EVENT (V2's name for a meeting or other calendar event); "meeting" and the 1.x integers 0/1/2 are still accepted.

Source code in affinity/models/types.py
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
class ReminderResetType(OpenStrEnum):
    """What resets a recurring reminder (V2 ``resetTrigger``).

    ``MEETING`` is a deprecated alias of ``EVENT`` (V2's name for a meeting or other calendar
    event); ``"meeting"`` and the 1.x integers 0/1/2 are still accepted.
    """

    INTERACTION = "interaction"  # an email or an event
    EMAIL = "email"
    EVENT = "event"
    MEETING = "event"

    @classmethod
    def _missing_(cls, value: object) -> OpenStrEnum:
        return _legacy_reminder_value(
            cls, value, {0: "interaction", 1: "email", 2: "event"}, {"meeting": "event"}
        )

ReminderStatus

Bases: OpenStrEnum

Current status of a reminder (V2 values; the 1.x integers 0/1/2 are still accepted).

Source code in affinity/models/types.py
568
569
570
571
572
573
574
575
576
577
class ReminderStatus(OpenStrEnum):
    """Current status of a reminder (V2 values; the 1.x integers 0/1/2 are still accepted)."""

    COMPLETED = "completed"
    ACTIVE = "active"
    OVERDUE = "overdue"

    @classmethod
    def _missing_(cls, value: object) -> OpenStrEnum:
        return _legacy_reminder_value(cls, value, {0: "completed", 1: "active", 2: "overdue"})

ReminderType

Bases: OpenStrEnum

Types of reminders (V2 values; the 1.x integers 0 and 1 are still accepted).

Source code in affinity/models/types.py
538
539
540
541
542
543
544
545
546
class ReminderType(OpenStrEnum):
    """Types of reminders (V2 values; the 1.x integers 0 and 1 are still accepted)."""

    ONE_TIME = "one-time"
    RECURRING = "recurring"

    @classmethod
    def _missing_(cls, value: object) -> OpenStrEnum:
        return _legacy_reminder_value(cls, value, {0: "one-time", 1: "recurring"})

ResolveMode

Bases: Enum

Controls how FieldResolver.get() returns complex field values.

Source code in affinity/models/types.py
491
492
493
494
495
class ResolveMode(Enum):
    """Controls how FieldResolver.get() returns complex field values."""

    RAW = "raw"  # Return extracted values as-is (default)
    TEXT = "text"  # Resolve all complex types to human-readable strings

UserId

Bases: PersonId

Workspace member ID. A subtype of PersonId — users are internal persons in Affinity.

Source code in affinity/models/types.py
176
177
class UserId(PersonId):
    """Workspace member ID. A subtype of PersonId — users are internal persons in Affinity."""

WebhookEvent

Bases: OpenStrEnum

Supported webhook events (30 total).

Events cover CRUD operations on Affinity entities:

  • Lists: created, updated, deleted
  • List entries: created, deleted
  • Notes: created, updated, deleted
  • Fields: created, updated, deleted
  • Field values: created, updated, deleted
  • Persons: created, updated, deleted
  • Organizations (companies): created, updated, deleted, merged
  • Opportunities: created, updated, deleted
  • Files: created, deleted
  • Smart interaction values: updated
  • Reminders: created, updated, deleted

This enum extends OpenStrEnum for forward compatibility - any unknown events from Affinity are preserved as strings rather than raising errors.

See the webhooks guide for complete documentation and usage examples.

Source code in affinity/models/types.py
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
class WebhookEvent(OpenStrEnum):
    """
    Supported webhook events (30 total).

    Events cover CRUD operations on Affinity entities:

    - **Lists**: created, updated, deleted
    - **List entries**: created, deleted
    - **Notes**: created, updated, deleted
    - **Fields**: created, updated, deleted
    - **Field values**: created, updated, deleted
    - **Persons**: created, updated, deleted
    - **Organizations (companies)**: created, updated, deleted, merged
    - **Opportunities**: created, updated, deleted
    - **Files**: created, deleted
    - **Smart interaction values**: updated
    - **Reminders**: created, updated, deleted

    This enum extends ``OpenStrEnum`` for forward compatibility - any unknown
    events from Affinity are preserved as strings rather than raising errors.

    See the webhooks guide for complete documentation and usage examples.
    """

    LIST_CREATED = "list.created"
    LIST_UPDATED = "list.updated"
    LIST_DELETED = "list.deleted"
    LIST_ENTRY_CREATED = "list_entry.created"
    LIST_ENTRY_DELETED = "list_entry.deleted"
    NOTE_CREATED = "note.created"
    NOTE_UPDATED = "note.updated"
    NOTE_DELETED = "note.deleted"
    FIELD_CREATED = "field.created"
    FIELD_UPDATED = "field.updated"
    FIELD_DELETED = "field.deleted"
    FIELD_VALUE_CREATED = "field_value.created"
    FIELD_VALUE_UPDATED = "field_value.updated"
    FIELD_VALUE_DELETED = "field_value.deleted"
    PERSON_CREATED = "person.created"
    PERSON_UPDATED = "person.updated"
    PERSON_DELETED = "person.deleted"
    ORGANIZATION_CREATED = "organization.created"
    ORGANIZATION_UPDATED = "organization.updated"
    ORGANIZATION_DELETED = "organization.deleted"
    ORGANIZATION_MERGED = "organization.merged"
    OPPORTUNITY_CREATED = "opportunity.created"
    OPPORTUNITY_UPDATED = "opportunity.updated"
    OPPORTUNITY_DELETED = "opportunity.deleted"
    FILE_CREATED = "file.created"
    FILE_DELETED = "file.deleted"
    SMART_INTERACTION_VALUE_UPDATED = "smart_interaction_value.updated"
    REMINDER_CREATED = "reminder.created"
    REMINDER_UPDATED = "reminder.updated"
    REMINDER_DELETED = "reminder.deleted"

WebhookStatus

Bases: OpenStrEnum

Whether a webhook delivers events (V2 status).

Source code in affinity/models/types.py
616
617
618
619
620
class WebhookStatus(OpenStrEnum):
    """Whether a webhook delivers events (V2 ``status``)."""

    ACTIVE = "active"
    DISABLED = "disabled"

field_id_to_v1_numeric(field_id: AnyFieldId) -> int

Convert v2 FieldId into v1 numeric field_id.

Accepts: - FieldId('field-123') -> 123 Rejects: - EnrichedFieldId(...) (cannot be represented as v1 numeric id)

Source code in affinity/models/types.py
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
def field_id_to_v1_numeric(field_id: AnyFieldId) -> int:
    """
    Convert v2 FieldId into v1 numeric field_id.

    Accepts:
    - FieldId('field-123') -> 123
    Rejects:
    - EnrichedFieldId(...) (cannot be represented as v1 numeric id)
    """
    if isinstance(field_id, EnrichedFieldId):
        raise ValueError(
            "Field IDs must be 'field-<digits>' for v1 conversion; "
            "enriched/relationship-intelligence IDs (e.g., 'affinity-data-*', "
            "'source-of-introduction') are not supported."
        )

    match = _FIELD_ID_RE.match(str(field_id))
    if match is None:
        raise ValueError(
            "Field IDs must be 'field-<digits>' for v1 conversion; "
            "enriched/relationship-intelligence IDs (e.g., 'affinity-data-*', "
            "'source-of-introduction') are not supported."
        )
    return int(match.group(1))