---
title: "List"
method: GET
path: "/accounts"
tags: ["accounts"]
---

# List

`GET /accounts`

## Request

- Base URL: `https://api.instruxi.dev/api/v1/enforcer`
- URL: `https://api.instruxi.dev/api/v1/enforcer/accounts`
- Auth: one of:
  - API key in header `Authorization`
  - API key in header `X-API-Key`

Every account row the caller may read, newest-name-first by default: admin sees all tenants, tenant_admin and developer see their own tenant, a plain user sees only themselves — the same compiled row filter GET /accounts/summary uses, so a summary tile and the list it drills into always agree on which rows count. All query filters intersect (AND). since/until run on accounts.created_at and are parsed by the same window parser /accounts/summary uses, so the formats and error codes cannot drift between the two endpoints. Service accounts are excluded by default; kind=service|all surfaces them for tenant admins.

## Query parameters

- `q` string
- `email` string
- `active` boolean
- `email_verified` boolean
- `profile_incomplete` boolean
- `profile_completed` boolean
- `verified_provider` string
- `verification_status` 'verified' | 'pending' | 'failed' | 'none'
- `role` 'admin' | 'tenant_admin' | 'developer' | 'user'
- `has_wallet` boolean
- `has_phone` boolean
- `invited` boolean
- `invited_by_me` boolean
- `invited_by` string, uuid
- `group_id` string, uuid
- `exclude_group_id` string, uuid
- `since` string, date-time
- `until` string, date-time
- `last_active_since` string, date-time
- `sort` 'name' | 'name_desc' | 'recent' | 'oldest' | 'active'
- `limit` integer
- `offset` integer
- `tenant_id` string, uuid
- `all_tenants` boolean
- `kind` 'human' | 'service' | 'all'

## Response `200`

OK

- InternalDeliveryHttpHandlerAccountPageResponse
  - `data` InternalDeliveryHttpHandlerAccountView[]
    - `active` boolean
    - `authenticated_at` string
    - `created_at` string
    - `email` string
    - `email_verified` boolean
    - `first_name` string
    - `id` string
    - `invited_by` string
    - `is_discoverable` boolean
    - `kind` string: Kind is "human" or "service" (AGENTS_CONTRACT §8). Every row carries one; never empty on a response built from a DB-loaded account.
    - `last_active_at` string
    - `last_login_at` string
    - `last_name` string
    - `person` InternalDeliveryHttpHandlerPersonView
      - `first_name` string
      - `id` string
      - `last_name` string
      - `name` string
      - `phone_verified` boolean
      - `primary_phone` string: Phone lives on the person (persons.primary_phone) — surfaced here so admin lists can show phone + its verification without a second read.
      - `profile_image_url` string
    - `person_id` string: PersonID is a pointer — nil for a service account (AGENTS_CONTRACT §8), which has none. The zero UUID would read as a real person id to any consumer that doesn't know to check for it, exactly the trap account.Account.PersonID itself was made a pointer to avoid.
    - `privy_user_id` string
    - `profile_completed` boolean
    - `role` EnforcerV3InternalDomainRoleRole
      - `admin_managed` boolean
      - `created_at` string
      - `cross_tenant` boolean: Capability grants (additive; all false = baseline-only, like "user").
      - `description` string
      - `id` string
      - `mpp_ops` boolean: MPPOps: read-only MPP deposit-intent operator views (migration 000129).
      - `name` string
      - `slug` string
      - `tenant_manage` boolean
      - `tenant_read` boolean
      - `tenant_write` boolean
      - `updated_at` string
    - `role_id` string
    - `tenant` EnforcerV3InternalDomainTenantTenant
      - `account_abstraction` boolean
      - `allow_user_discovery` boolean: AllowUserDiscovery gates contact discovery for the whole tenant: when true, accounts may be discoverable (and new accounts default to discoverable); when false, discovery is off tenant-wide regardless of account.is_discoverable.
      - `app_url` string
      - `auth_provider` string: AuthProvider / WalletProvider: nil = inherit the instance default.
      - `banner_url` string
      - `billing_status` string: BillingStatus is the tenant's platform-billing dunning state, PROJECTED from enforcer-billing (migration 000087): "" | active | past_due | restricted | suspended | collections. enforcer-billing is the source of truth; this is a copy the billing.status_changed consumer keeps current so the value rides Accessor.billing_status on every resolve with no hop. "" means UNKNOWN and readers MUST treat it as active (fail open). `<-:false` makes GORM never write these two columns: Repository.Update is a whole-row Save, so without it an admin editing the tenant's name from a stale struct would silently roll the projection back. Only BillingStatusStore.ApplyBillingStatus writes them, by targeted UPDATE under the monotonic entered_at guard.
      - `billing_status_entered_at` string
      - `code` string
      - `code_expires_at` string: CodeExpiresAt is when the join code stops working everywhere a code is accepted, signing in and joining alike, as if it had been rotated away. Absent: the code never expires. Set by POST /tenants/{id}/code.
      - `created_at` string
      - `description` string
      - `id` string
      - `logo_url` string
      - `metadata` EnforcerV3InternalDomainTenantJSONBMap
      - `metered_sends_enabled` boolean: MeteredSendsEnabled turns on per-transaction limit metering: every value leg leaving the platform is measured against the caller's live cap before the server co-signs. Meaningful only while Web3Enabled is true — the pair (false, true) is refused on write; see ValidateWeb3Gates.
      - `modules` string[]: Modules is the tenant's MODULE entitlements as group slugs, prefixed `module_` (migration 000094, V3-02). Which HALO-Lego surfaces this tenant has bought — NOT which of them any given account may use, which is the role's question. It lives on the tenant rather than in account_groups because it is a fact about the TENANT: a module per account would mean auto-joining every new account and would make "remove one person from a module the tenant paid for" expressible. The resolver UNIONS this into Accessor.GroupSlugs, so every leaf's RequireModule reads it with no new field and no extra hop. Same `<-:false` guard and the same targeted-write rule as BillingStatus above: Repository.Update is a whole-row Save, so without it an admin editing the tenant's name from a stale struct would silently revoke every module. Only ModuleStore writes these two columns.
      - `modules_updated_at` string
      - `name` string
      - `payment_link_url` string
      - `privy_app_id` string
      - `self_join_policy` 'open' | 'invite_only'
      - `status` 'active' | 'inactive' | 'suspended'
      - `theme_colors` EnforcerV3InternalDomainTenantJSONBMap
      - `thumbnail_url` string
      - `updated_at` string
      - `wallet_provider` string
      - `web3_enabled` boolean: Web3Enabled is the tenant-wide web3 kill switch. False rejects EVERY web3 transaction for the tenant — guarded sends, signing, and RPC writes alike — so it is deliberately a gate rather than an enforcement mode. Defaults true (migration 000066) because false would disable web3 for every existing tenant on deploy.
    - `tenant_id` string
    - `updated_at` string
    - `username` string
    - `verification_status` string: VerificationStatus is the KYC rollup bucket — verified | pending | failed | none — decorated onto list rows, absent on payloads that do not resolve it. NOT the same thing as ProfileCompleted, which is an admin-set provisioner flag and says nothing about identity.
    - `wallet_address` string
  - `limit` integer
  - `offset` integer
  - `success` boolean
  - `total` integer

## Other responses

- `400`: Bad Request
- `401`: Unauthorized
- `403`: forbidden: tenant_id/all_tenants beyond your own tenant without the cross_tenant grant
- `404`: group_not_found

---

[API](https://skmtc.dev/instruxi/apis/enforcer-api.md) · [All operations](https://skmtc.dev/instruxi/apis/enforcer-api/llms.txt) · [OpenAPI document](https://skmtc.dev/instruxi/apis/enforcer-api/revisions/fa82bc1becc0?raw)
