# `AccessGrid.Console`
[🔗](https://github.com/Access-Grid/accessgrid-ex/blob/main/lib/access_grid/console.ex#L1)

Manages enterprise template and logging operations.

This module provides functions for card template management (create, update, read),
retrieving activity logs, and listing template pairs. These are enterprise-only
features requiring special account permissions.

## Examples

    # Create a new template
    {:ok, result} = AccessGrid.Console.create_template(%{
      name: "Employee Badge",
      platform: "apple",
      use_case: "employee_badge",
      protocol: "desfire"
    })

    # Read full template details
    {:ok, template} = AccessGrid.Console.read_template(result.id)

    # Get activity logs
    {:ok, logs, pagination} = AccessGrid.Console.get_logs(template.id)

# `credential_profile_result`

```elixir
@type credential_profile_result() ::
  {:ok, AccessGrid.CredentialProfile.t()}
  | {:error, AccessGrid.Types.api_error_reason(),
     AccessGrid.HttpFailure.t() | [atom(), ...]}
```

# `credential_profiles_result`

```elixir
@type credential_profiles_result() ::
  {:ok, [AccessGrid.CredentialProfile.t()]}
  | {:error, AccessGrid.Types.api_error_reason(),
     AccessGrid.HttpFailure.t() | [atom(), ...]}
```

# `hid_org_result`

```elixir
@type hid_org_result() ::
  {:ok, AccessGrid.HidOrg.t()}
  | {:error, AccessGrid.Types.api_error_reason(),
     AccessGrid.HttpFailure.t() | [atom(), ...]}
```

# `hid_orgs_result`

```elixir
@type hid_orgs_result() ::
  {:ok, [AccessGrid.HidOrg.t()]}
  | {:error, AccessGrid.Types.api_error_reason(),
     AccessGrid.HttpFailure.t() | [atom(), ...]}
```

# `ios_preflight_result`

```elixir
@type ios_preflight_result() ::
  {:ok, AccessGrid.IosPreflight.t()}
  | {:error, AccessGrid.Types.api_error_reason(),
     AccessGrid.HttpFailure.t() | [atom(), ...]}
```

# `landing_page_result`

```elixir
@type landing_page_result() ::
  {:ok, AccessGrid.LandingPage.t()}
  | {:error, AccessGrid.Types.api_error_reason(),
     AccessGrid.HttpFailure.t() | [atom(), ...]}
```

# `landing_pages_result`

```elixir
@type landing_pages_result() ::
  {:ok, [AccessGrid.LandingPage.t()]}
  | {:error, AccessGrid.Types.api_error_reason(),
     AccessGrid.HttpFailure.t() | [atom(), ...]}
```

# `ledger_items_result`

```elixir
@type ledger_items_result() ::
  {:ok, [AccessGrid.LedgerItem.t()], map()}
  | {:error, AccessGrid.Types.api_error_reason(),
     AccessGrid.HttpFailure.t() | [atom(), ...]}
```

# `logs_result`

```elixir
@type logs_result() ::
  {:ok, [AccessGrid.Event.t()], map()}
  | {:error, AccessGrid.Types.api_error_reason(),
     AccessGrid.HttpFailure.t() | [atom(), ...]}
```

# `pair_summary_result`

```elixir
@type pair_summary_result() ::
  {:ok, AccessGrid.CardTemplatePair.Summary.t()}
  | {:error, AccessGrid.Types.api_error_reason(),
     AccessGrid.HttpFailure.t() | [atom(), ...]}
```

# `pairs_result`

```elixir
@type pairs_result() ::
  {:ok, [AccessGrid.CardTemplatePair.Summary.t()], map()}
  | {:error, AccessGrid.Types.api_error_reason(),
     AccessGrid.HttpFailure.t() | [atom(), ...]}
```

# `publish_result`

```elixir
@type publish_result() ::
  {:ok, AccessGrid.CardTemplate.PublishResult.t()}
  | {:error, AccessGrid.Types.api_error_reason(),
     AccessGrid.HttpFailure.t() | [atom(), ...]}
```

# `result`

```elixir
@type result() ::
  {:ok, AccessGrid.CardTemplate.Result.t()}
  | {:error, AccessGrid.Types.api_error_reason(),
     AccessGrid.HttpFailure.t() | [atom(), ...]}
```

# `smart_tap_reveal_result`

```elixir
@type smart_tap_reveal_result() ::
  {:ok, AccessGrid.SmartTapReveal.t()}
  | {:error, AccessGrid.Types.api_error_reason(),
     AccessGrid.HttpFailure.t() | [atom(), ...]}
```

# `template_result`

```elixir
@type template_result() ::
  {:ok, AccessGrid.CardTemplate.t() | AccessGrid.CardTemplatePair.t()}
  | {:error, AccessGrid.Types.api_error_reason(),
     AccessGrid.HttpFailure.t() | [atom(), ...]}
```

# `webhook_delete_result`

```elixir
@type webhook_delete_result() ::
  :ok
  | {:error, AccessGrid.Types.api_error_reason(),
     AccessGrid.HttpFailure.t() | [atom(), ...]}
```

# `webhook_result`

```elixir
@type webhook_result() ::
  {:ok, AccessGrid.Webhook.t()}
  | {:error, AccessGrid.Types.api_error_reason(),
     AccessGrid.HttpFailure.t() | [atom(), ...]}
```

# `webhooks_result`

```elixir
@type webhooks_result() ::
  {:ok, [AccessGrid.Webhook.t()], map()}
  | {:error, AccessGrid.Types.api_error_reason(),
     AccessGrid.HttpFailure.t() | [atom(), ...]}
```

# `activate_hid_org`

```elixir
@spec activate_hid_org(
  map(),
  keyword()
) :: hid_org_result()
```

Completes registration for an HID Origo organization (activate).

Rails may return extra fields on the response (`already_completed: true` when
the org is already activated, `job_queued: true` when a registration job is
in flight). Those flags are not surfaced on the returned struct — inspect
`org.status` to determine activation state.

## Parameters

  * `params` - Map with:
    * `:email` - Email used to register the org
    * `:password` - HID portal password

  * `opts` - Options:
    * `:client` - Client struct (optional, defaults to config)

## Returns

  * `{:ok, %HidOrg{}}` - 200 OK (fresh, already-complete, or no-op)
  * `{:error, reason, %HttpFailure{}}` - 404 if no org matches the email

# `create_card_template_pair`

```elixir
@spec create_card_template_pair(
  map(),
  keyword()
) :: pair_summary_result()
```

Creates a card template pair from an existing Apple and Google template.

The Rails API enforces several validations before the pair is created:

  * Both referenced templates must belong to the current account (404 otherwise).
  * Apple template must have `platform == "apple"` and Google template `platform == "android"` (422).
  * Protocol combination must be either both SEOS, or Apple DESFire + Google Smart Tap (422).
  * Both templates must be in `status == "ready"` (i.e. published) (422).

## Parameters

  * `params` - Map with:
    * `:name` - Name for the new pair (required)
    * `:apple_card_template_id` - ex_id of the published Apple template
    * `:google_card_template_id` - ex_id of the published Google template

  * `opts` - Options:
    * `:client` - Client struct (optional, defaults to config)

## Returns

  * `{:ok, %CardTemplatePair.Summary{}}` - Pair created. Same shape as items returned by
    `list_card_template_pairs/1`.
  * `{:error, reason, %HttpFailure{}}` - Creation failed.

# `create_credential_profile`

```elixir
@spec create_credential_profile(
  map(),
  keyword()
) :: credential_profile_result()
```

Creates a new credential profile.

## Parameters

  * `params` - Map with:
    * `:name` - Display name (required)
    * `:app_name` - Reader app name (optional, defaults to `"KEY-ID-main"`)
    * `:keys` - List of `%{value, keys_diversified?, source_key_index?}` maps;
      length must match the app's required key count
    * `:file_id` - Hex file id string (optional, defaults to `"00"`)

  * `opts` - Options:
    * `:client` - Client struct (optional, defaults to config)

## Returns

  * `{:ok, %CredentialProfile{}}` - Created
  * `{:error, reason, %HttpFailure{}}` - 422 on invalid `app_name`, wrong key count, or validation failure

# `create_hid_org`

```elixir
@spec create_hid_org(
  map(),
  keyword()
) :: hid_org_result()
```

Registers a new HID Origo organization for the account.

Idempotent on `name` → `slug`: if an org with the derived slug already exists,
Rails returns the existing record with status 200 instead of creating a new one.

## Parameters

  * `params` - Map with:
    * `:name` - Display name (required)
    * `:full_address` - Full mailing address
    * `:phone` - Contact phone number
    * `:first_name` - Primary contact first name
    * `:last_name` - Primary contact last name

  * `opts` - Options:
    * `:client` - Client struct (optional, defaults to config)

## Returns

  * `{:ok, %HidOrg{}}` - Created (201) or existing (200)
  * `{:error, reason, %HttpFailure{}}` - 422 on validation failure

# `create_landing_page`

```elixir
@spec create_landing_page(
  map(),
  keyword()
) :: landing_page_result()
```

Creates a new landing page.

## Parameters

  * `params` - Map with:
    * `:name` - Display name (required)
    * `:kind` - Landing page kind (required; immutable after creation)
    * `:additional_text`, `:bg_color`, `:allow_immediate_download`,
      `:password`, `:is_2fa_enabled` - Optional fields
    * `:logo` - Base64-encoded PNG or JPEG image (optional)

  * `opts` - Options:
    * `:client` - Client struct (optional, defaults to config)

## Returns

  * `{:ok, %LandingPage{}}` - Created
  * `{:error, reason, %HttpFailure{}}` - Creation failed

# `create_template`

```elixir
@spec create_template(
  map(),
  keyword()
) :: result()
```

Creates a new card template.

Params are passed straight through to Rails — pass top-level keys using the
exact wire names (no nested `design:` / `support_info:` wrappers). Image fields
(`background`, `logo`, `icon`) accept base64-encoded strings; see
`AccessGrid.Utils.base64_file/1` for a helper.

## Parameters

  * `params` - Template configuration:
    * `:name` - Display name for the template (required)
    * `:platform` - `"apple"` or `"android"` (required)
    * `:use_case` - e.g. `"employee_badge"` (required)
    * `:protocol` - `"desfire"` (Apple), `"seos"`, or `"smart_tap"` (Android)
    * `:allow_on_multiple_devices`, `:watch_count`, `:iphone_count` - Device limits
    * `:background_color`, `:label_color`, `:label_secondary_color` - Style settings
    * `:background`, `:logo`, `:icon` - Base64-encoded PNG/JPEG images (max 10MB decoded)
    * `:support_url`, `:support_phone_number`, `:support_email` - Support contact info
    * `:privacy_policy_url`, `:terms_and_conditions_url` - Legal URLs
    * `:credential_profiles` - List of credential-profile ex_id strings to attach
    * `:landing_pages` - List of landing-page ex_id strings to attach
    * `:metadata` - Custom metadata map

  * `opts` - Options:
    * `:client` - Client struct (optional, defaults to config)

## Returns

  * `{:ok, %CardTemplate.Result{}}` - Template created successfully
  * `{:error, reason, %HttpFailure{}}` - Creation failed

# `create_webhook`

```elixir
@spec create_webhook(
  map(),
  keyword()
) :: webhook_result()
```

Creates a new webhook subscription.

## Parameters

  * `params` - Map with:
    * `:name` - Display name
    * `:url` - HTTPS endpoint to receive events
    * `:subscribed_events` - List of event names (e.g. `["ag.access_pass.issued"]`)
    * `:auth_method` - `"bearer_token"` (default) or `"mtls"`

  * `opts` - Options:
    * `:client` - Client struct (optional, defaults to config)

## Returns

  * `{:ok, %Webhook{}}` - Created. For `bearer_token`, the struct includes
    `private_key` (sensitive — store on receipt, Rails does not return it
    again). For `mtls`, the struct includes `client_cert` and `cert_expires_at`.
  * `{:error, reason, %HttpFailure{}}` - 422 on empty or invalid `subscribed_events`.

# `delete_webhook`

```elixir
@spec delete_webhook(
  String.t(),
  keyword()
) :: webhook_delete_result()
```

Deletes a webhook subscription.

## Parameters

  * `webhook_id` - The webhook ID to delete
  * `opts` - Options:
    * `:client` - Client struct (optional, defaults to config)

## Returns

  * `:ok` - Deleted (Rails returns 204 No Content; there is no body)
  * `{:error, reason, %HttpFailure{}}` - 404 if id missing

# `get_logs`

```elixir
@spec get_logs(
  String.t(),
  keyword()
) :: logs_result()
```

Retrieves activity logs for a card template.

## Parameters

  * `template_id` - The template ID to get logs for
  * `opts` - Options:
    * `:client` - Client struct (optional, defaults to config)
    * `:page` - Page number (default: 1)
    * `:per_page` - Results per page (default: 50, max: 100)
    * `:filters` - Map with optional filters:
      * `:device` - "mobile" or "watch"
      * `:start_date` - ISO8601 timestamp
      * `:end_date` - ISO8601 timestamp
      * `:event_type` - Event type string

## Returns

  * `{:ok, [%Event{}], pagination}` - List of events and pagination info
  * `{:error, reason, %HttpFailure{}}` - Retrieval failed

# `ios_preflight`

```elixir
@spec ios_preflight(String.t(), map(), keyword()) :: ios_preflight_result()
```

Runs Apple Wallet In-App Provisioning preflight for an access pass.

Returns the identifiers needed to drive the iOS provisioning flow. Note that
Rails returns these keys in camelCase (Apple convention); the resulting struct
uses snake_case Elixir-idiomatic field names.

## Parameters

  * `template_id` - The card template ID containing the access pass
  * `params` - Map with:
    * `:access_pass_ex_id` - ex_id of the access pass to preflight (required)
  * `opts` - Options:
    * `:client` - Client struct (optional, defaults to config)

## Returns

  * `{:ok, %IosPreflight{}}` - Preflight identifiers
  * `{:error, reason, %HttpFailure{}}` - Preflight failed (404 if template or access pass missing)

# `list_card_template_pairs`

```elixir
@spec list_card_template_pairs(keyword()) :: pairs_result()
```

Lists all pass template pairs for the account.

Template pairs combine an iOS and Android template for cross-platform pass issuance.

## Parameters

  * `opts` - Options:
    * `:client` - Client struct (optional, defaults to config)
    * `:page` - Page number (default: 1)
    * `:per_page` - Results per page (default: 50, max: 100)

## Returns

  * `{:ok, [%CardTemplatePair.Summary{}], pagination}` - List of pairs and pagination info
  * `{:error, reason, %HttpFailure{}}` - Retrieval failed

# `list_credential_profiles`

```elixir
@spec list_credential_profiles(keyword()) :: credential_profiles_result()
```

Lists all credential profiles for the account.

Rails returns a flat JSON array — no wrapper, no pagination — so the result
is `{:ok, list}` rather than `{:ok, list, pagination}`.

## Parameters

  * `opts` - Options:
    * `:client` - Client struct (optional, defaults to config)

## Returns

  * `{:ok, [%CredentialProfile{}]}` - List of credential profiles (may be empty)
  * `{:error, reason, %HttpFailure{}}` - Retrieval failed

# `list_hid_orgs`

```elixir
@spec list_hid_orgs(keyword()) :: hid_orgs_result()
```

Lists HID Origo organizations registered to the account.

Rails returns a flat JSON array — no wrapper, no pagination.

## Parameters

  * `opts` - Options:
    * `:client` - Client struct (optional, defaults to config)

## Returns

  * `{:ok, [%HidOrg{}]}` - List of HID orgs (may be empty)
  * `{:error, reason, %HttpFailure{}}` - Retrieval failed

# `list_landing_pages`

```elixir
@spec list_landing_pages(keyword()) :: landing_pages_result()
```

Lists all landing pages for the account.

Rails returns a flat JSON array — there is no `landing_pages` wrapper and no
pagination, so the result is `{:ok, list}` rather than `{:ok, list, pagination}`.

## Parameters

  * `opts` - Options:
    * `:client` - Client struct (optional, defaults to config)

## Returns

  * `{:ok, [%LandingPage{}]}` - List of landing pages (may be empty)
  * `{:error, reason, %HttpFailure{}}` - Retrieval failed

# `list_ledger_items`

```elixir
@spec list_ledger_items(keyword()) :: ledger_items_result()
```

Lists ledger items for the account, paginated and optionally date-filtered.

## Parameters

  * `opts` - Options:
    * `:client` - Client struct (optional, defaults to config)
    * `:page` - Page number (default: 1)
    * `:per_page` - Results per page (default: 50, max: 100)
    * `:start_date` - ISO8601 timestamp; filters items created on/after
    * `:end_date` - ISO8601 timestamp; filters items created on/before

## Returns

  * `{:ok, [%LedgerItem{}], pagination}` - List + pagination map
  * `{:error, reason, %HttpFailure{}}` - 422 on bad date format

# `list_webhooks`

```elixir
@spec list_webhooks(keyword()) :: webhooks_result()
```

Lists webhook subscriptions for the account.

## Parameters

  * `opts` - Options:
    * `:client` - Client struct (optional, defaults to config)
    * `:page` - Page number (default: 1)
    * `:per_page` - Results per page (default: 50, max: 100)

## Returns

  * `{:ok, [%Webhook{}], pagination}` - List + pagination map
  * `{:error, reason, %HttpFailure{}}` - Retrieval failed

# `publish_template`

```elixir
@spec publish_template(
  String.t(),
  keyword()
) :: publish_result()
```

Publishes a card template, moving it out of `draft` toward `ready` or `in-review`.

For Android+SEOS templates, Rails also syncs the template to the HID portal as
part of publish. If that sync fails, the template is rolled back to `draft`
and this call returns `{:error, :validation_failed, failure}` with a
field-tagged error message in `failure.body_decoded["message"]`.

## Parameters

  * `template_id` - The card template id to publish
  * `opts` - Options:
    * `:client` - Client struct (optional, defaults to config)

## Returns

  * `{:ok, %CardTemplate.PublishResult{}}` - 200 OK with `{id, status}`. `status` is
    `"publishing"` (already in flight), `"in-review"` (Apple queued), or `"ready"`
    (Android, immediate).
  * `{:error, reason, %HttpFailure{}}` - 404 if template missing, 422 on validation
    failure or HID-sync failure (for Android+SEOS).

# `read_template`

```elixir
@spec read_template(
  String.t(),
  keyword()
) :: template_result()
```

Retrieves a card template or card template pair by id.

The `/v1/console/card-templates/:id` endpoint serves both shapes: a single
template, or a pair containing two member templates. Match on the returned
struct to tell them apart.

## Parameters

  * `template_id` - The template (or pair) id to retrieve
  * `opts` - Options:
    * `:client` - Client struct (optional, defaults to config)

## Returns

  * `{:ok, %CardTemplate{}}` - Single template
  * `{:ok, %CardTemplatePair{}}` - Pair, with member templates under `:templates`
  * `{:error, reason, %HttpFailure{}}` - Retrieval failed

# `reveal_smart_tap`

```elixir
@spec reveal_smart_tap(String.t(), map(), keyword()) :: smart_tap_reveal_result()
```

Reveals Smart Tap credentials for a card template, encrypted with the
caller's ephemeral public key.

The caller decrypts the returned `encrypted_private_key` with their
corresponding private key. Each request must use a fresh ephemeral keypair —
Rails enforces single-use via the pubkey fingerprint (returns 409 if reused).

## Parameters

  * `template_id` - The card template id (must be SmartTap protocol with a `smart_tap_key`)
  * `params` - Map with:
    * `:client_public_key` - PEM-encoded public key string (required)

  * `opts` - Options:
    * `:client` - Client struct (optional, defaults to config)

## Returns

  * `{:ok, %SmartTapReveal{}}` - Encrypted credentials envelope
  * `{:error, :not_found, %HttpFailure{}}` - Template missing, not SmartTap, or no `smart_tap_key`
  * `{:error, :conflict, %HttpFailure{}}` - This `client_public_key` has been used before
  * `{:error, :validation_failed, %HttpFailure{}}` - Invalid PEM or save failure

# `update_landing_page`

```elixir
@spec update_landing_page(String.t(), map(), keyword()) :: landing_page_result()
```

Updates an existing landing page.

The `:kind` field is immutable after creation. Sending a different value
returns 422.

## Parameters

  * `landing_page_id` - The landing page ID to update
  * `params` - Same fields as `create_landing_page/2` except `:kind`
  * `opts` - Options:
    * `:client` - Client struct (optional, defaults to config)

## Returns

  * `{:ok, %LandingPage{}}` - Updated
  * `{:error, reason, %HttpFailure{}}` - 404 if id missing, 422 on validation

# `update_template`

```elixir
@spec update_template(String.t(), map(), keyword()) :: result()
```

Updates an existing card template.

## Parameters

  * `template_id` - The template ID to update
  * `params` - Fields to update (same options as create_template)
  * `opts` - Options:
    * `:client` - Client struct (optional, defaults to config)

## Returns

  * `{:ok, %CardTemplate.Result{}}` - Template updated successfully
  * `{:error, reason, %HttpFailure{}}` - Update failed

---

*Consult [api-reference.md](api-reference.md) for complete listing*
