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
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
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
190
191
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
282
283
284
285
286
287
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") == 123True

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
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, 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
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
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
129
130
131
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
def __new__(cls, value: Any) -> FieldId:
    """Normalize value to 'field-xxx' format at construction time."""
    if isinstance(value, cls):
        return value
    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
133
134
135
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
137
138
139
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.

Source code in affinity/models/types.py
429
430
431
432
433
434
435
436
437
438
439
440
441
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.
    """

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

FieldValueChangeAction

Bases: OpenIntEnum

Types of changes that can occur to field values.

Source code in affinity/models/types.py
533
534
535
536
537
538
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
298
299
300
301
302
303
304
305
306
307
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
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)

    @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, 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)

InteractionType

Bases: OpenIntEnum

Types of interactions.

Source code in affinity/models/types.py
470
471
472
473
474
475
476
class InteractionType(OpenIntEnum):
    """Types of interactions."""

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

ListType

Bases: OpenIntEnum

Type of entities a list can contain.

Source code in affinity/models/types.py
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
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)

NoteType

Bases: OpenIntEnum

Types of notes.

Source code in affinity/models/types.py
516
517
518
519
520
521
522
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
290
291
292
293
294
295
class PersonType(OpenStrEnum):
    """Types of persons in Affinity."""

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

ReminderResetType

Bases: OpenIntEnum

How recurring reminders get reset.

Source code in affinity/models/types.py
500
501
502
503
504
505
class ReminderResetType(OpenIntEnum):
    """How recurring reminders get reset."""

    INTERACTION = 0  # Email or meeting
    EMAIL = 1
    MEETING = 2

ReminderStatus

Bases: OpenIntEnum

Current status of a reminder.

Source code in affinity/models/types.py
508
509
510
511
512
513
class ReminderStatus(OpenIntEnum):
    """Current status of a reminder."""

    COMPLETED = 0
    ACTIVE = 1
    OVERDUE = 2

ReminderType

Bases: OpenIntEnum

Types of reminders.

Source code in affinity/models/types.py
493
494
495
496
497
class ReminderType(OpenIntEnum):
    """Types of reminders."""

    ONE_TIME = 0
    RECURRING = 1

ResolveMode

Bases: Enum

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

Source code in affinity/models/types.py
463
464
465
466
467
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
174
175
class UserId(PersonId):
    """Workspace member ID. A subtype of PersonId — users are internal persons in Affinity."""

WebhookEvent

Bases: OpenStrEnum

Supported webhook events (27 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
  • 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
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
class WebhookEvent(OpenStrEnum):
    """
    Supported webhook events (27 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
    - **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"
    REMINDER_CREATED = "reminder.created"
    REMINDER_UPDATED = "reminder.updated"
    REMINDER_DELETED = "reminder.deleted"

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
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
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))