Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.wellapp.ai/llms.txt

Use this file to discover all available pages before exploring further.

A card represents a physical or virtual payment card (credit, debit, prepaid, corporate, or virtual) associated with a workspace. Each card record identifies its cardholder — either a Company or a People (mutually exclusive) — and carries card-identification attributes such as the last four digits, anonymized PAN, brand, type, and lifecycle dates. The entity is used to enrich payment-means data and surfaces in the financial graph as a composite via composite_cards_list on related roots (companies, people) and as a first-class cards records root.
NamingValue
ObjectCard
Resource type (JSON:API type)card
Collection / records rootcards
REST base/v1/cards
Entity classCard

API operations

OperationMethod & pathStatus
ListGET /v1/cards✅ Implemented
RetrieveGET /v1/cards/{id}✅ Implemented
CreatePOST /v1/cards🟡 Planned
UpdatePATCH /v1/cards/{id}🟡 Planned
DeleteDELETE /v1/cards/{id}🟡 Planned

Data model

Attributes

FieldTypeRequiredConstraintsAllowed valuesDescription
card_idstring, UUID, 🔒 system✅ Yesunique; generated via gen_random_uuid() on insertPublic stable identifier for the card. Use this UUID in all API references; never expose the internal pk.
last_four_digitsstring✅ Yeslength = 4; CHECK last_four_digits ~ ’^[0-9]{4}$‘Exactly 4 numeric digitsThe last four digits of the card number. Required for all cards; used for display and matching against payment-means records.
anonymized_panstring⚪ Nomax length 30; nullablePartially masked PAN string, e.g. ‘4539xXXXXXXXXXX4291’. Populated by connectors that provide it; absent when only the last four digits are known.
brandenum (CardBrandEnum)⚪ Nonullable; native PostgreSQL enum ‘card_brand_enum’visa, mastercard, amex, discover, diners, jcb, unionpayCard network / brand. Sourced from the connector’s card metadata. Null when the brand cannot be determined.
typeenum (CardTypeEnum)⚪ Nonullable; native PostgreSQL enum ‘card_type_enum’credit, debit, prepaid, corporate, virtualFunctional category of the card. Corporate and virtual cards commonly surface from expense-management connectors.
expiration_datedate⚪ Nonullable; stored as PostgreSQL DATEThe date after which the card is no longer valid. Typically the last day of the expiry month.
start_datedate⚪ Nonullable; stored as PostgreSQL DATEThe date from which the card becomes valid. Common on UK-issued cards that carry an explicit start date on the face.
issue_datedate⚪ Nonullable; stored as PostgreSQL DATEThe date the card was issued by the issuing institution. Distinct from start_date — a card may be issued before it becomes valid.
cardholder_namestring⚪ Nomax length 100; nullableThe name embossed or printed on the card. May differ from the linked People.full_name when the card was issued in a trade name or role name.
created_atdatetime, 🔒 system✅ Yesset once via onCreate lifecycle hook; never updatedTimestamp when the card record was created in the Well database. Auto-populated; not accepted from the API client.
updated_atdatetime, 🔒 system⚪ Noset via onCreate and onUpdate lifecycle hooks; nullable in schemaTimestamp of the last modification to this record. Auto-managed by MikroORM lifecycle hooks.
deleted_atdatetime⚪ Nonullable; soft-delete sentinel; all active-record queries filter deleted_at IS NULLnull (active) or a past ISO timestamp (soft-deleted)Soft-delete timestamp. When set, the card is logically deleted. Indexes on (company_pk, deleted_at) and (workspace_pk, deleted_at) are defined to avoid scanning deleted tuples in Hasura traversal paths.

Relationships

NameTypeRequiredDescription
companyto-one (company)⚪ No — mutually exclusive with people; one cardholder FK must be non-null for a meaningful recordThe Company that owns or is the named cardholder of this card. FK: cards.company_pk → companies.pk. Indexed as (company_pk, deleted_at) to support Hasura traversal companies.cards without sequential scans. Set to NULL on company deletion (ON DELETE SET NULL).
peopleto-one (people)⚪ No — mutually exclusive with company; at most one cardholder FK is setThe People (person) who is the named cardholder on this card. FK: cards.people_pk → peoples.pk. Indexed as (people_pk) to support Hasura traversal peoples.cards. Set to NULL on people deletion (ON DELETE SET NULL).
workspaceto-one (workspace)⚪ No (nullable FK, but all well-formed records carry a workspace)The tenant workspace this card belongs to. FK: cards.workspace_pk → workspaces.pk. Indexed as (workspace_pk, deleted_at) for workspace-scoped queries and Hasura RLS filtering. Set to NULL on workspace deletion (ON DELETE SET NULL).

System-computed

  • card_id is generated by PostgreSQL gen_random_uuid() on INSERT; the value is immutable after creation.
  • created_at is set once by the MikroORM @Property({ onCreate }) lifecycle hook at record creation and is never modified thereafter.
  • updated_at is set by the MikroORM @Property({ onCreate, onUpdate }) lifecycle hooks — populated on both create and every subsequent update.
  • deleted_at is null for all active records. Setting it to a timestamp soft-deletes the card; the row is retained in the database. Every active-record query must filter deleted_at IS NULL.
  • Cardholder is polymorphic: exactly one of company or people should be non-null for a meaningful card record. Neither FK carries a database-level mutual-exclusion constraint — the invariant is enforced at the service layer.
  • Three composite indexes are maintained for hot-path Hasura traversals: idx_cards_company_deleted (company_pk, deleted_at), idx_cards_people (people_pk), and idx_cards_workspace_deleted (workspace_pk, deleted_at). These were added in Migration20260416200000 after Query Insights revealed sequential scans on the companies.cards, peoples.cards, and workspace-scoped traversal paths.
  • The cards root is surfaced as a composite_cards_list array composite on the companies and people records roots (source_fields: card_id, brand, last_four_digits, type). The composite is defined in composites.yml under companies and people with display_type: relation_list and a sort_proxy of cards_aggregate.min.brand.
  • Cards are also linked from payment_means via the payment_means.card relationship (FK: payment_means.card_pk → cards.pk, ON DELETE SET NULL), added in Migration20260102111942. This underpins the composite_payment_means_summary composite renderer which reads payment_means.card.brand, payment_means.card.last_four_digits, and payment_means.card.type.

Example

{
  "data": {
    "type": "card",
    "id": "c3a7e2f1-84bb-4f91-b9c3-1d2e5f6a7b89",
    "attributes": {
      "card_id": "c3a7e2f1-84bb-4f91-b9c3-1d2e5f6a7b89",
      "last_four_digits": "4291",
      "anonymized_pan": "4539xXXXXXXXXXX4291",
      "brand": "visa",
      "type": "corporate",
      "expiration_date": "2027-09-30",
      "start_date": "2023-10-01",
      "issue_date": "2023-09-15",
      "cardholder_name": "Alice Moreau",
      "created_at": "2023-09-15T10:22:00.000Z",
      "updated_at": "2024-03-01T08:14:35.000Z",
      "deleted_at": null
    },
    "relationships": {
      "company": {
        "data": { "type": "company", "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890" }
      },
      "people": {
        "data": null
      },
      "workspace": {
        "data": { "type": "workspace", "id": "f9e8d7c6-b5a4-3210-fedc-ba9876543210" }
      }
    }
  }
}
Source: apps/api/src/database/entities/Card.ts · domain: financial-graph · tier: Supporting