> ## 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.

# Card

> A `card` represents a physical or virtual payment card (credit, debit, prepaid, corporate, or virtual) associated with a workspace

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.

| Naming                          | Value       |
| ------------------------------- | ----------- |
| Object                          | Card        |
| Resource type (JSON:API `type`) | `card`      |
| Collection / records root       | `cards`     |
| REST base                       | `/v1/cards` |
| Entity class                    | `Card`      |

## API operations

| Operation | Method & path           | Status        |
| --------- | ----------------------- | ------------- |
| List      | `GET /v1/cards`         | ✅ Implemented |
| Retrieve  | `GET /v1/cards/{id}`    | ✅ Implemented |
| Create    | `POST /v1/cards`        | 🟡 Planned    |
| Update    | `PATCH /v1/cards/{id}`  | 🟡 Planned    |
| Delete    | `DELETE /v1/cards/{id}` | 🟡 Planned    |

## Data model

### Attributes

| Field              | Type                    | Required | Constraints                                                                          | Allowed values                                          | Description                                                                                                                                                                                                    |
| ------------------ | ----------------------- | -------- | ------------------------------------------------------------------------------------ | ------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| card\_id           | string, UUID, 🔒 system | ✅ Yes    | unique; generated via gen\_random\_uuid() on insert                                  | —                                                       | Public stable identifier for the card. Use this UUID in all API references; never expose the internal `pk`.                                                                                                    |
| last\_four\_digits | string                  | ✅ Yes    | length = 4; CHECK last\_four\_digits \~ '^\[0-9]\{4}\$'                              | Exactly 4 numeric digits                                | The last four digits of the card number. Required for all cards; used for display and matching against payment-means records.                                                                                  |
| anonymized\_pan    | string                  | ⚪ No     | max length 30; nullable                                                              | —                                                       | Partially masked PAN string, e.g. '4539xXXXXXXXXXX4291'. Populated by connectors that provide it; absent when only the last four digits are known.                                                             |
| brand              | enum (CardBrandEnum)    | ⚪ No     | nullable; native PostgreSQL enum 'card\_brand\_enum'                                 | visa, mastercard, amex, discover, diners, jcb, unionpay | Card network / brand. Sourced from the connector's card metadata. Null when the brand cannot be determined.                                                                                                    |
| type               | enum (CardTypeEnum)     | ⚪ No     | nullable; native PostgreSQL enum 'card\_type\_enum'                                  | credit, debit, prepaid, corporate, virtual              | Functional category of the card. Corporate and virtual cards commonly surface from expense-management connectors.                                                                                              |
| expiration\_date   | date                    | ⚪ No     | nullable; stored as PostgreSQL DATE                                                  | —                                                       | The date after which the card is no longer valid. Typically the last day of the expiry month.                                                                                                                  |
| start\_date        | date                    | ⚪ No     | nullable; stored as PostgreSQL DATE                                                  | —                                                       | The date from which the card becomes valid. Common on UK-issued cards that carry an explicit start date on the face.                                                                                           |
| issue\_date        | date                    | ⚪ No     | nullable; stored as PostgreSQL DATE                                                  | —                                                       | The date the card was issued by the issuing institution. Distinct from start\_date — a card may be issued before it becomes valid.                                                                             |
| cardholder\_name   | string                  | ⚪ No     | max length 100; nullable                                                             | —                                                       | The 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\_at        | datetime, 🔒 system     | ✅ Yes    | set once via onCreate lifecycle hook; never updated                                  | —                                                       | Timestamp when the card record was created in the Well database. Auto-populated; not accepted from the API client.                                                                                             |
| updated\_at        | datetime, 🔒 system     | ⚪ No     | set via onCreate and onUpdate lifecycle hooks; nullable in schema                    | —                                                       | Timestamp of the last modification to this record. Auto-managed by MikroORM lifecycle hooks.                                                                                                                   |
| deleted\_at        | datetime                | ⚪ No     | nullable; soft-delete sentinel; all active-record queries filter deleted\_at IS NULL | null (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

| Name      | Type               | Required                                                                                            | Description                                                                                                                                                                                                                                                              |
| --------- | ------------------ | --------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| company   | to-one (company)   | ⚪ No — mutually exclusive with `people`; one cardholder FK must be non-null for a meaningful record | The 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). |
| people    | to-one (people)    | ⚪ No — mutually exclusive with `company`; at most one cardholder FK is set                          | The 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).                                               |
| workspace | to-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

```json theme={null}
{
  "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" }
      }
    }
  }
}
```

<sub>Source: `apps/api/src/database/entities/Card.ts` · domain: financial-graph · tier: Supporting</sub>
