Marqeta

Hex.pmHex DocsCICoverage Status

Production-grade Elixir client for the Marqeta Core, Credit, and DiVA APIs.

Full coverage of all 120 Marqeta API surfaces with:


Installation

def deps do
[
{:marqeta, "~> 1.0"}
]
end

Configuration

# config/config.exs
config :marqeta,
base_url: "https://sandbox-api.marqeta.com/v3",
application_token: System.fetch_env!("MARQETA_APP_TOKEN"),
admin_access_token: System.fetch_env!("MARQETA_ADMIN_TOKEN"),
pool_size: 10,
timeout: 30_000,
retry_max_attempts: 3,
retry_base_delay: 500,
retry_jitter: true,
sandbox: true
# config/prod.exs
config :marqeta,
base_url: "https://yourprogram-api.marqeta.com/v3",
sandbox: false,
pool_size: 25

Quick Start

Create a Cardholder

{:ok, user} = Marqeta.Users.create(%{
first_name: "Jane",
last_name: "Doe",
email: "jane@example.com",
phone: "5555551234",
address1: "123 Main St",
city: "San Francisco",
state: "CA",
postal_code: "94105",
country: "USA",
birth_date: "1990-01-15",
identifications: [%{type: "SSN", value: "123456789"}]
})

Run KYC

{:ok, result} = Marqeta.KYCVerification.perform(%{user_token: user["token"]})
IO.inspect(result["result"]["status"]) # => "success"

Issue a Virtual Card

{:ok, card_product} = Marqeta.CardProducts.create(%{
name: "My Card Product",
start_date: "2024-01-01",
config: %{
card_life_cycle: %{activate_upon_issue: true},
fulfillment: %{payment_instrument: "VIRTUAL_PAN"},
jit_funding: %{
program_funding_source: %{
funding_source_token: "my_program_funding_source",
enabled: true
}
}
}
})
{:ok, card} = Marqeta.Cards.create(%{
user_token: user["token"],
card_product_token: card_product["token"]
})

Fund a User's GPA

{:ok, order} = Marqeta.GPAOrders.create(%{
user_token: user["token"],
amount: 250.00,
currency_code: "USD",
funding_source_token: "my_funding_source"
})

Set Spend Limits

# $500/day limit for a specific user
{:ok, _} = Marqeta.VelocityControls.create(%{
association: %{user_token: user["token"]},
currency_code: "USD",
amount_limit: 500.00,
velocity_window: "DAY",
include_purchases: true,
include_withdrawals: true,
active: true
})

Check Balance

{:ok, balance} = Marqeta.Users.balances(user["token"])
IO.inspect(balance["gpa"]["available_balance"])

Pagination & Streaming

All list endpoints return paginated responses. Use the stream helpers for automatic multi-page iteration:

# Stream ALL users across all pages
Marqeta.Users.stream(%{count: 100})
|> Stream.filter(& &1["status"] == "ACTIVE")
|> Stream.map(& &1["email"])
|> Enum.to_list()
# Stream all transactions for a card, filter clearings
Marqeta.Cards.stream_transactions("card_token", %{count: 50})
|> Stream.filter(& &1["state"] == "COMPLETION")
|> Enum.count()
# Manual pagination
{:ok, page1} = Marqeta.Transactions.list(%{count: 25, start_index: 0})
{:ok, page2} = Marqeta.Transactions.list(%{count: 25, start_index: 25})

Error Handling

All functions return {:ok, result} or {:error, %Marqeta.Error{}}.

case Marqeta.Users.create(params) do
{:ok, user} ->
process_user(user)
{:error, %Marqeta.Error{type: :validation_error, field_errors: errors}} ->
Enum.each(errors, fn e ->
IO.puts("#{e.field}: #{e.message}")
end)
{:error, %Marqeta.Error{type: :rate_limit_error, retryable?: true}} ->
# Built-in retry handles this automatically
:ok
{:error, %Marqeta.Error{type: :authentication_error}} ->
Logger.error("Check your Marqeta credentials")
{:error, %Marqeta.Error{} = err} ->
Logger.error("Marqeta error: #{Exception.message(err)}",
request_id: err.request_id,
error_code: err.error_code
)
end

Bang Variants

Every function has a ! variant that returns the result directly or raises:

user = Marqeta.Users.create!(%{first_name: "Jane", ...})
card = Marqeta.Cards.create!(%{user_token: user["token"], ...})

Webhooks

Registration

{:ok, webhook} = Marqeta.Webhooks.create(%{
name: "my-webhook",
active: true,
events: ["transaction.*", "cardtransition.*"],
config: %{
url: "https://api.myapp.com/webhooks/marqeta",
secret: "MyHmacSecret@123456789",
signature_algorithm: "HMAC_SHA_256"
}
})
# Test it
{:ok, _} = Marqeta.Webhooks.ping(webhook["token"])

Signature Verification (Phoenix example)

defmodule MyAppWeb.MarqetaController do
use MyAppWeb, :controller
def handle(conn, _params) do
signature = get_req_header(conn, "x-marqeta-signature") |> List.first("")
raw_body = conn.assigns[:raw_body]
secret = Application.fetch_env!(:my_app, :marqeta_webhook_secret)
if Marqeta.Webhooks.valid_signature?(raw_body, signature, secret) do
process_event(conn.body_params)
send_resp(conn, 200, "ok")
else
send_resp(conn, 401, "invalid signature")
end
end
end

Gateway JIT Funding

defmodule MyAppWeb.JITController do
use MyAppWeb, :controller
alias Marqeta.GatewayJIT
def handle(conn, params) do
response =
if GatewayJIT.actionable?(params) do
user_token = GatewayJIT.user_token(params)
amount = GatewayJIT.amount(params)
case check_user_balance(user_token, amount) do
:ok -> GatewayJIT.approve_response(params)
:decline -> GatewayJIT.decline_response(params, reason: "INSUFFICIENT_FUNDS")
end
else
# Informative notification - no response needed
GatewayJIT.approve_response(params)
end
json(conn, response)
end
end

Credit Platform

# Full credit account creation flow
# 1. Create a credit product
{:ok, product} = Marqeta.Credit.Products.create(%{
name: "Signature Rewards Card",
# ... policies
})
# 2. Create a bundle
{:ok, bundle} = Marqeta.Credit.Bundles.create(%{
name: "Rewards Bundle",
credit_product_token: product["token"]
})
# 3. Create an account
{:ok, account} = Marqeta.Credit.Accounts.create(%{
user_token: user["token"],
bundle_token: bundle["token"],
credit_limit: 5_000.00,
config: %{
billing_cycle_day: 1,
payment_due_interval: 25,
e_disclosure_active: true
}
})
# 4. Issue a card
{:ok, card} = Marqeta.Credit.Cards.create(account["token"], %{
user_token: user["token"]
})
# 5. Make a payment
{:ok, payment} = Marqeta.Credit.Payments.create(account["token"], %{
amount: 150.00,
payment_source_token: "source_01"
})
# 6. List journal entries
Marqeta.Credit.JournalEntries.stream(account["token"], %{count: 100})
|> Enum.to_list()

DiVA (Analytics & Reporting)

# Get all settlements for January
{:ok, page} = Marqeta.DiVA.Settlements.list(%{
start_date: "2024-01-01",
end_date: "2024-01-31",
count: 500
})
# Stream all authorizations for a month
Marqeta.DiVA.Authorizations.stream(%{
start_date: "2024-01-01",
end_date: "2024-01-31"
})
|> Stream.filter(& &1["state"] == "DECLINED")
|> Enum.count()
# Platform response time report
{:ok, perf} = Marqeta.DiVA.PlatformResponse.list(%{
start_date: "2024-01-01",
count: 31
})
# List all available views
{:ok, views} = Marqeta.DiVA.Views.list()
# Data dictionary for authorizations
{:ok, dict} = Marqeta.DiVA.DataDictionary.get("authorizations")

Simulations (Sandbox)

# Simulate a purchase
{:ok, auth} = Marqeta.Simulations.authorization(%{
card_token: card["token"],
amount: 42.50,
mid: "merchant_01",
card_acceptor: %{
name: "Coffee Shop",
mcc: "5812",
city: "San Francisco"
}
})
# Settle it
{:ok, _} = Marqeta.Simulations.clearing(%{
original_transaction_token: auth["token"],
amount: 42.50
})
# Quick full purchase
{:ok, txn} = Marqeta.Simulations.purchase(%{
card_token: card["token"],
amount: 100.00
})
# ATM withdrawal
{:ok, _} = Marqeta.Simulations.atm_withdrawal(%{
card_token: card["token"],
amount: 200.00
})

Telemetry

# Attach a custom handler
:telemetry.attach(
"my-marqeta-metrics",
[:marqeta, :request, :stop],
fn _event, measurements, metadata, _config ->
duration_ms = System.convert_time_unit(measurements.duration, :native, :millisecond)
MyMetrics.record("marqeta.request.duration", duration_ms,
tags: ["method:#{metadata.method}", "status:#{metadata.status}"]
)
end,
nil
)
# Or use the built-in metrics for Phoenix LiveDashboard / PromEx
Marqeta.Telemetry.metrics()

Testing

# In your test module
use Marqeta.Test.BypassHelper
setup do
bypass = Bypass.open()
configure_marqeta(bypass)
{:ok, bypass: bypass}
end
test "creates a user", %{bypass: bypass} do
user = build(:user)
expect_post(bypass, "/users", user, 201)
assert {:ok, result} = Marqeta.Users.create(%{first_name: "Jane"})
assert result["token"] == user["token"]
end
test "handles validation error", %{bypass: bypass} do
expect_error(bypass, "POST", "/users", %{
"error_code" => "400040",
"error_message" => "Email is invalid"
}, 400)
assert {:error, err} = Marqeta.Users.create(%{email: "bad"})
assert err.type == :validation_error
end

API Coverage

CategoryModules
Core UsersUsers, UserTransitions, Businesses, BusinessTransitions, AccountHolderGroups
CardsCards, CardProducts, CardTransitions, BulkCardOrders, PINs
FundingGPAOrders, ProgramFundingSources, ProgramGatewayFundingSources, AccountHolderFundingSources, IntraAccountTransfers, ProgramTransfers, ProgramReserve, FundingViaACH, InstantFunding, AutoReload, ACHReceiving
Spend ControlsVelocityControls, AuthorizationControls, MCCGroups, MerchantGroups, AcceptedCountries
ComplianceKYCVerification, FraudFeedback, ThreeDSecure
DisputesDisputesVisa, DisputesMastercard, DisputesPulse, DisputesEvidenceCollection
FeesFees, FeeCharges, FeeRefunds
Digital WalletsDigitalWalletsManagement, TokenizationAsAService
PlatformWebhooks, GatewayJIT, CommandomMode, SelfServiceCredentials, Simulations, Sandbox
Credit (25)Accounts, Cards, Applications, Bundles, Products, Policies, Payments, PaymentSchedules, PaymentSources, JournalEntries, LedgerEntries, Statements, Disputes, Adjustments, Rewards, RewardAccounts, RewardRedemptions, RewardRules, RewardConversions, RewardGlobalConfigurations, Delinquency, Transitions, Substatuses, Refunds, BalanceRefunds
DiVA (35)Authorizations, Settlements, Declines, Loads, Chargebacks, CardCounts, UserCounts, ActivityBalances, ActivityBalancesFundingDay, ActivityBalancesNetworkDetail, ClearingDetail, Cards, Users, DirectDeposit, ACHGateway, ACHOrigination, ACHPending, ACHVerification, PlatformResponse, ProgramBalancesSettlement, ProgramFundingBalances, RTDAuthorizations, RTDTransactionCountByRules, CoreAPITransactionToken, CreditAccounts, CreditCards, CreditAccountDailyBalances, CreditJournalEntries, CreditLedgerEntries, CreditPayments, CreditDisputes, CreditRewards, CreditStatements, CreditAccountAdjustments, Views, DataDictionary

Total: 120 API surfaces across Core, Credit, and DiVA.


License

MIT License. See LICENSE for details.