Solarium
API Reference

Introduction

The Solarium API is a GraphQL API that gives you full programmatic access to your accounting data: transactions, accounts, contacts, receipts, and financial reports.

All requests go to a single endpoint. Authentication is via API key (Bearer token) or JWT. Every query and mutation requires an organizationId. Your API key is scoped to one organization, so use the myOrganizations query to resolve it.

Base URL: https://solarium-api.lovemaple.app/graphql

Set Up Your AI Agent

The fastest way to get started: copy the instructions below and paste them into Claude, ChatGPT, or any AI agent. Replace sol_your_key_here with your API key from Settings > API Keys. When you create a key in the dashboard, we'll pre-fill these instructions with your actual key.

Quickstart (Manual)

If you're writing code directly, grab your API key and make your first request:

Request
# ━━━ AI Agent Instructions (copy & paste into Claude/ChatGPT) ━━━
#
# You have access to the Solarium accounting API.
#
# Endpoint: https://solarium-api.lovemaple.app/graphql
# Method: POST with JSON body {"query": "...", "variables": {...}}
# Auth header: Authorization: Bearer sol_your_key_here
#
# First, resolve your org ID:
#   query { myOrganizations { id name } }
# Use the returned id as organizationId on every call.
#
# Transaction lifecycle: create → classify → confirm
#
# Key operations:
#   listLedgerEvents(organizationId, limit, status, search, dateFrom, dateTo)
#   createLedgerEvent(organizationId, merchantName, amount, transactionDate, eventType)
#   updateStagedEntry(organizationId, eventId, categoryId, paymentId)
#   confirmLedgerEvent(organizationId, eventId)
#   listAccounts(organizationId, limit, search)
#   listContacts(organizationId, limit, search)
#   incomeStatement(organizationId, startDate, endDate)
#   importBankStatement(organizationId, paymentAccountId, rows: [...])
#
# Amounts are strings ("42.50"). Dates are YYYY-MM-DD.
# Event types: "expense", "income", "transfer".
# Statuses: "needs_review", "confirmed", "synced", "reconciled".
# See full docs at: https://trysolarium.com/integrations/api

# ━━━ Manual Quickstart ━━━

# curl: resolve your org ID
curl -X POST https://solarium-api.lovemaple.app/graphql \
  -H "Authorization: Bearer sol_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{"query": "{ myOrganizations { id name } }"}'

# Python: full example
import httpx

API_URL = "https://solarium-api.lovemaple.app/graphql"
API_KEY = "sol_your_key_here"

def query(q, variables=None):
    resp = httpx.post(
        API_URL,
        json={"query": q, "variables": variables or {}},
        headers={"Authorization": f"Bearer {API_KEY}"},
    )
    return resp.json()["data"]

# 1. Get your org ID
orgs = query("{ myOrganizations { id name } }")
org_id = orgs["myOrganizations"][0]["id"]

# 2. List accounts
accounts = query("""
  query($orgId: UUID!) {
    listAccounts(organizationId: $orgId, limit: 10) {
      items { id name accountType currentBalance }
      totalCount
    }
  }
""", {"orgId": org_id})

# 3. Create a transaction
result = query("""
  mutation($orgId: UUID!) {
    createLedgerEvent(
      organizationId: $orgId
      merchantName: "Staples Canada"
      amount: "42.50"
      transactionDate: "2026-01-15"
      eventType: "expense"
    ) { success eventId }
  }
""", {"orgId": org_id})

# 4. Classify it (set category + payment account)
query("""
  mutation($orgId: UUID!, $eventId: UUID!) {
    updateStagedEntry(
      organizationId: $orgId
      eventId: $eventId
      categoryId: "category-account-uuid"
      paymentId: "payment-account-uuid"
    ) { success }
  }
""", {"orgId": org_id, "eventId": result["createLedgerEvent"]["eventId"]})

# 5. Confirm it (posts to books)
query("""
  mutation($orgId: UUID!, $eventId: UUID!) {
    confirmLedgerEvent(
      organizationId: $orgId
      eventId: $eventId
    ) { success journalEntryId }
  }
""", {"orgId": org_id, "eventId": result["createLedgerEvent"]["eventId"]})
Response
{
  "data": {
    "myOrganizations": [
      {
        "id": "a1b2c3d4-...",
        "name": "My Company Inc."
      }
    ]
  }
}

Authentication

Authenticate by passing your API key as a Bearer token in the Authorization header.

API keys are created in the Solarium dashboard at Settings > API Keys. API keys start with sol_ and are shown once at creation. Store them securely. Only the SHA-256 hash is stored on our end.

API keys are scoped to a single organization. You cannot access another organization's data with an API key. Admin operations (managing members, creating other API keys) are not available via API key.

Rate Limiting

API key requests are limited to 5,000 requests per hour per API key. The limit resets on a rolling 1-hour window from the first request.

Every API key response includes rate limit headers:

  • X-RateLimit-Limit: your hourly quota (5000)
  • X-RateLimit-Remaining: requests left in the current window

If you exceed the limit, requests return 429 Too Many Requests with a Retry-After header. JWT-authenticated requests (dashboard sessions) are not rate limited.

Request
# API Key auth (recommended for integrations)
curl -X POST https://solarium-api.lovemaple.app/graphql \
  -H "Authorization: Bearer sol_abc123..." \
  -H "Content-Type: application/json" \
  -d '{"query": "{ myOrganizations { id name } }"}'

# JWT auth (for user sessions)
curl -X POST https://solarium-api.lovemaple.app/graphql \
  -H "Authorization: JWT eyJhbGciOiJIUzI1NiIs..." \
  -H "Content-Type: application/json" \
  -d '{"query": "{ myOrganizations { id name } }"}'
Response
# Unauthenticated request
{
  "errors": [
    { "message": "Authentication Required." }
  ]
}

# Wrong Organization
{
  "errors": [
    { "message": "API Key Not Authorized For This Organization." }
  ]
}

# Rate limit exceeded (429)
{
  "errors": [
    { "message": "Rate Limit Exceeded. 5,000 Requests/Hour." }
  ]
}
# Headers: Retry-After: 60

Errors

The Solarium API returns errors in a consistent JSON shape. GraphQL requests always return 200 OK at the HTTP level. Errors appear in the errors array of the response body.

The only exceptions are 429 Too Many Requests (rate limit) and 400 Bad Request (malformed JSON or invalid GraphQL syntax), which return non-200 HTTP status codes.

Error Object

Each error has a message string. Some errors include a path array indicating which field triggered the error.

Common Errors

MessageCause
Authentication Required.No token or API key provided
API Key Not Authorized For This Organization.API key belongs to a different organization
Organization Not Found.Invalid organizationId
Not A Member Of This Organization.JWT user is not a member of this organization
API Keys Cannot Perform Admin Operations.API key used for an admin-only mutation (e.g. managing members)
Must Be Owner Or Admin.Insufficient role for this operation
Rate Limit Exceeded. 5,000 Requests/Hour.API key exceeded hourly quota (HTTP 429)

Mutation Errors

Mutations return a success boolean and an error string instead of throwing. A failed mutation returns success: false with a human-readable error. The HTTP status is still 200.

Request
# Successful mutation
mutation {
  createAccount(
    organizationId: "a1b2c3d4-..."
    name: "Office Supplies"
    accountType: "expense"
    classification: "expense"
  ) {
    account { id name }
    success
    error
  }
}
Response
# GraphQL-level error (in errors array)
{
  "errors": [
    {
      "message": "Authentication Required.",
      "path": ["createAccount"]
    }
  ],
  "data": { "createAccount": null }
}

# Mutation-level error (success: false)
{
  "data": {
    "createAccount": {
      "account": null,
      "success": false,
      "error": "Account with this name already exists."
    }
  }
}

# Rate limit error (HTTP 429)
{
  "errors": [
    { "message": "Rate Limit Exceeded. 5,000 Requests/Hour." }
  ]
}

Transactions

Transactions (LedgerEvents) are the core entity in Solarium. Each transaction flows through a lifecycle: created → classified → confirmed → synced. Use createLedgerEvent for manual entries, or import via bank statement. Classify with updateStagedEntry (set category + payment account), then confirm to post to the books.

POST

List Transactions

Returns a paginated, filterable list of ledger events. Supports status filtering, date ranges, amount ranges, account/category scoping, and full-text search.

Parameters

organizationIdUUID!required

Organization ID

status[String]

Filter by status (needs_review, confirmed, synced, reconciled, pending, ignored)

hasDocumentBoolean

Filter by whether a receipt/document is attached

sinceDaysInt

Only return events from the last N days

dateFromString

Start date filter (YYYY-MM-DD)

dateToString

End date filter (YYYY-MM-DD)

amountMinString

Minimum amount filter (inclusive)

amountMaxString

Maximum amount filter (inclusive)

accountIds[UUID]

Filter by expense/income account IDs

categoryIds[UUID]

Filter by category account IDs

searchString

Full-text search on merchant name and memo

limitInt

Page size (default 25, max 200)

offsetInt

Number of records to skip (default 0)

sortByString

Field to sort by (e.g. transactionDate, functionalAmount)

sortDirString

asc or desc (default desc)

POST

Get a Transaction

Returns the full detail for a single ledger event, including attached receipts, journal entry, and classification data.

Parameters

organizationIdUUID!required

Organization ID

eventIdUUID!required

Ledger event ID

POST

Create a Transaction

Creates a new ledger event (manual entry). The event starts in needs_review status and must be classified and confirmed before it posts to the books.

Parameters

organizationIdUUID!required

Organization ID

merchantNameString!required

Vendor / merchant name

amountString!required

Transaction amount (positive decimal string)

transactionDateString!required

Date of transaction (YYYY-MM-DD)

eventTypeString

expense, income, or transfer (default expense)

categoryIdUUID

Expense/income account to categorize under

paymentAccountIdUUID

Bank/credit card account used for payment

POST

Update a Transaction

Updates mutable fields on an existing ledger event. Only events in needs_review or pending status can be updated.

Parameters

organizationIdUUID!required

Organization ID

eventIdUUID!required

Ledger event ID

transactionDateString

Updated transaction date (YYYY-MM-DD)

memoString

Updated memo / description

amountString

Updated amount (positive decimal string)

eventTypeString

Updated type: expense, income, or transfer

POST

Confirm a Transaction

Confirms a classified ledger event, posting it to the books by creating a journal entry. The event must have a category and payment account assigned before confirming.

Parameters

organizationIdUUID!required

Organization ID

eventIdUUID!required

Ledger event ID to confirm

POST

Reject a Transaction

Rejects a ledger event, moving it to ignored status. Rejected events are excluded from the books and review queue.

Parameters

organizationIdUUID!required

Organization ID

eventIdUUID!required

Ledger event ID to reject

POST

Classify Transaction

Assigns classification data to a staged ledger event: category (expense/income account), payment account, and optional tax breakdown. This is the primary way to prepare a transaction for confirmation.

Parameters

organizationIdUUID!required

Organization ID

eventIdUUID!required

Ledger event ID to classify

categoryIdUUID

Expense or income account ID

paymentIdUUID

Bank/credit card payment account ID

taxAmountString

Tax portion of the amount (e.g. HST/GST)

taxAccountIdUUID

Tax liability account ID (e.g. HST Payable)

taxCodeString

Tax code identifier (e.g. HST_ON, GST, exempt)

POST

Attach Receipt to Transaction

Attaches an uploaded receipt (image or PDF) to a ledger event. The file must already be uploaded to S3 — pass the s3Key returned from the upload endpoint.

Parameters

organizationIdUUID!required

Organization ID

eventIdUUID!required

Ledger event ID to attach the receipt to

s3KeyString!required

S3 object key of the uploaded receipt file

filenameString

Original filename for display (e.g. receipt.pdf)

List Transactions — Request
query {
  listLedgerEvents(
    organizationId: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
    status: ["needs_review"]
    dateFrom: "2026-01-01"
    dateTo: "2026-06-30"
    limit: 10
  ) {
    items {
      id
      eventType
      merchantName
      functionalAmount
      functionalCurrency
      transactionDate
      status
      hasDocument
      account { id name }
      paymentAccount { id name }
    }
    totalCount
    pageSize
    offset
  }
}
Response
{
  "data": {
    "listLedgerEvents": {
      "items": [
        {
          "id": "b7c8d9e0-f1a2-3456-b789-cdef01234567",
          "eventType": "expense",
          "merchantName": "Staples Canada",
          "functionalAmount": "127.43",
          "functionalCurrency": "CAD",
          "transactionDate": "2026-06-18",
          "status": "needs_review",
          "hasDocument": true,
          "account": {
            "id": "d3e4f5a6-b7c8-9012-def0-123456789abc",
            "name": "Office Supplies"
          },
          "paymentAccount": {
            "id": "e4f5a6b7-c8d9-0123-ef01-23456789abcd",
            "name": "TD Visa *8821"
          }
        },
        {
          "id": "c8d9e0f1-a2b3-4567-c890-def012345678",
          "eventType": "expense",
          "merchantName": "Tim Hortons",
          "functionalAmount": "14.85",
          "functionalCurrency": "CAD",
          "transactionDate": "2026-06-17",
          "status": "needs_review",
          "hasDocument": false,
          "account": null,
          "paymentAccount": {
            "id": "e4f5a6b7-c8d9-0123-ef01-23456789abcd",
            "name": "TD Visa *8821"
          }
        }
      ],
      "totalCount": 43,
      "pageSize": 10,
      "offset": 0
    }
  }
}
Get a Transaction — Request
query {
  getLedgerEvent(
    organizationId: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
    eventId: "b7c8d9e0-f1a2-3456-b789-cdef01234567"
  ) {
    id
    eventType
    merchantName
    functionalAmount
    functionalCurrency
    transactionDate
    status
    memo
    hasDocument
    account { id name accountType }
    paymentAccount { id name }
    category { id name }
    journalEntry { id txnDate totalAmount }
    receipts { id filename s3Key uploadedAt }
  }
}
Response
{
  "data": {
    "getLedgerEvent": {
      "id": "b7c8d9e0-f1a2-3456-b789-cdef01234567",
      "eventType": "expense",
      "merchantName": "Staples Canada",
      "functionalAmount": "127.43",
      "functionalCurrency": "CAD",
      "transactionDate": "2026-06-18",
      "status": "needs_review",
      "memo": "Printer paper and toner cartridges",
      "hasDocument": true,
      "account": {
        "id": "d3e4f5a6-b7c8-9012-def0-123456789abc",
        "name": "Office Supplies",
        "accountType": "expense"
      },
      "paymentAccount": {
        "id": "e4f5a6b7-c8d9-0123-ef01-23456789abcd",
        "name": "TD Visa *8821"
      },
      "category": {
        "id": "d3e4f5a6-b7c8-9012-def0-123456789abc",
        "name": "Office Supplies"
      },
      "journalEntry": null,
      "receipts": [
        {
          "id": "f0a1b2c3-d4e5-6789-a012-bcdef0123456",
          "filename": "staples-receipt-jun18.pdf",
          "s3Key": "receipts/a1b2c3d4/2026/06/staples-receipt-jun18.pdf",
          "uploadedAt": "2026-06-18T14:32:00Z"
        }
      ]
    }
  }
}
Create a Transaction — Request
mutation {
  createLedgerEvent(
    organizationId: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
    merchantName: "Canada Post"
    amount: "34.95"
    transactionDate: "2026-06-20"
    eventType: "expense"
    categoryId: "a6b7c8d9-e0f1-2345-6789-abcdef012345"
    paymentAccountId: "e4f5a6b7-c8d9-0123-ef01-23456789abcd"
  ) {
    success
    eventId
  }
}
Response
{
  "data": {
    "createLedgerEvent": {
      "success": true,
      "eventId": "d9e0f1a2-b3c4-5678-d901-ef0123456789"
    }
  }
}
Update a Transaction — Request
mutation {
  updateLedgerEvent(
    organizationId: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
    eventId: "b7c8d9e0-f1a2-3456-b789-cdef01234567"
    memo: "Office supplies — printer paper and toner"
    amount: "127.43"
  ) {
    success
    event {
      id
      memo
      functionalAmount
      status
    }
  }
}
Response
{
  "data": {
    "updateLedgerEvent": {
      "success": true,
      "event": {
        "id": "b7c8d9e0-f1a2-3456-b789-cdef01234567",
        "memo": "Office supplies — printer paper and toner",
        "functionalAmount": "127.43",
        "status": "needs_review"
      }
    }
  }
}
Confirm a Transaction — Request
mutation {
  confirmLedgerEvent(
    organizationId: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
    eventId: "b7c8d9e0-f1a2-3456-b789-cdef01234567"
  ) {
    success
    eventId
    journalEntryId
  }
}
Response
{
  "data": {
    "confirmLedgerEvent": {
      "success": true,
      "eventId": "b7c8d9e0-f1a2-3456-b789-cdef01234567",
      "journalEntryId": "a2b3c4d5-e6f7-8901-ab23-cdef45678901"
    }
  }
}
Reject a Transaction — Request
mutation {
  rejectLedgerEvent(
    organizationId: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
    eventId: "c8d9e0f1-a2b3-4567-c890-def012345678"
  ) {
    success
    eventId
  }
}
Response
{
  "data": {
    "rejectLedgerEvent": {
      "success": true,
      "eventId": "c8d9e0f1-a2b3-4567-c890-def012345678"
    }
  }
}
Classify Transaction — Request
mutation {
  updateStagedEntry(
    organizationId: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
    eventId: "c8d9e0f1-a2b3-4567-c890-def012345678"
    categoryId: "f5a6b7c8-d9e0-1234-5678-9abcdef01234"
    paymentId: "e4f5a6b7-c8d9-0123-ef01-23456789abcd"
    taxAmount: "1.93"
    taxAccountId: "a0b1c2d3-e4f5-6789-0abc-def123456789"
    taxCode: "HST_ON"
  ) {
    success
  }
}
Response
{
  "data": {
    "updateStagedEntry": {
      "success": true
    }
  }
}
Attach Receipt to Transaction — Request
mutation {
  attachReceiptToEvent(
    organizationId: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
    eventId: "b7c8d9e0-f1a2-3456-b789-cdef01234567"
    s3Key: "receipts/a1b2c3d4/2026/06/canadapost-shipping-label.pdf"
    filename: "canadapost-shipping-label.pdf"
  ) {
    success
    receiptId
  }
}
Response
{
  "data": {
    "attachReceiptToEvent": {
      "success": true,
      "receiptId": "b1c2d3e4-f5a6-7890-bcde-f01234567890"
    }
  }
}

Accounts

POST

List Accounts

Returns a paginated list of accounts in the chart of accounts. Supports search by name and sorting.

Parameters

organizationIdUUID!required

Organization ID

limitInt

Page size (default 25, max 200)

offsetInt

Number of records to skip

searchString

Filter by name (case-insensitive)

sortByString

Field to sort by (e.g. name, currentBalance)

sortDirString

asc or desc (default asc)

POST

Create an Account

Creates a new account in the chart of accounts.

Parameters

organizationIdUUID!required

Organization ID

nameString!required

Account name

taxCodeIdUUID!required

CRA Tax Code code ID. Determines account type and classification. Use listTaxCodes to get available codes.

lastFourString

Last 4 digits of card/account for payment matching

parentIdUUID

Parent account ID for hierarchical accounts

List Accounts — Request
query {
  listAccounts(
    organizationId: "a1b2c3d4-..."
    limit: 10
    search: "expense"
  ) {
    items {
      id
      name
      accountType
      classification
      subType
      currentBalance
      active
    }
    totalCount
    pageSize
    offset
  }
}
Response
{
  "data": {
    "listAccounts": {
      "items": [
        {
          "id": "f7e6d5c4-...",
          "name": "Office Supplies",
          "accountType": "expense",
          "classification": "expense",
          "subType": "",
          "currentBalance": 1250.00,
          "active": true
        }
      ],
      "totalCount": 24,
      "pageSize": 10,
      "offset": 0
    }
  }
}
Create an Account — Request
mutation {
  createAccount(
    organizationId: "a1b2c3d4-..."
    name: "Office Supplies"
    taxCodeId: "tax-code-uuid-for-8711"
  ) {
    account {
      id
      name
      accountType
      classification
      taxCode { code name }
    }
    success
    error
  }
}
Response
{
  "data": {
    "createAccount": {
      "account": {
        "id": "f7e6d5c4-...",
        "name": "Office Supplies",
        "accountType": "expense",
        "classification": "expense",
        "taxCode": {
          "code": "8711",
          "name": "Office stationery and supplies"
        }
      },
      "success": true,
      "error": null
    }
  }
}

Contacts

POST

List Contacts

Returns a paginated list of vendors and customers.

Parameters

organizationIdUUID!required

Organization ID

limitInt

Page size (default 25, max 200)

offsetInt

Number of records to skip

searchString

Filter by name

POST

Create a Contact

Creates a new vendor or customer.

Parameters

organizationIdUUID!required

Organization ID

contactTypeString!required

vendor or customer

displayNameString!required

Contact name

emailString

Email address

phoneString

Phone number

List Contacts — Request
query {
  listContacts(
    organizationId: "a1b2c3d4-..."
    limit: 10
    search: "uber"
  ) {
    items {
      id
      contactType
      displayName
      email
      phone
      balance
      active
    }
    totalCount
  }
}
Response
{
  "data": {
    "listContacts": {
      "items": [
        {
          "id": "b2c3d4e5-...",
          "contactType": "vendor",
          "displayName": "Uber",
          "email": "",
          "phone": "",
          "balance": 0,
          "active": true
        }
      ],
      "totalCount": 1
    }
  }
}
Create a Contact — Request
mutation {
  createContact(
    organizationId: "a1b2c3d4-..."
    contactType: "vendor"
    displayName: "Staples"
    email: "[email protected]"
  ) {
    contact { id displayName contactType }
    success
    error
  }
}
Response
{
  "data": {
    "createContact": {
      "contact": {
        "id": "c3d4e5f6-...",
        "displayName": "Staples",
        "contactType": "vendor"
      },
      "success": true,
      "error": null
    }
  }
}

Receipts

Receipts are documentary evidence attached to transactions. In Solarium V2, receipts don't create transactions directly — instead, you attach a receipt image to an existing transaction using attachReceiptToEvent (see Transactions).

Receipts can also arrive automatically via Gmail scanning or SMS/email intake addresses. These are matched to bank transactions automatically when possible, or appear as unmatched receipts for manual review.

The listReceipts query below returns all receipt records, including their OCR-extracted data, matching status, and linked transaction.

POST

List Receipts

Returns a paginated list of receipts with OCR data, vendor, amount, and linked transaction.

Parameters

organizationIdUUID!required

Organization ID

limitInt

Page size (default 25)

offsetInt

Number of records to skip

searchString

Filter by vendor name

statusString

Filter by status: draft, processed, reviewed, rejected

sourceString

Filter by source: web_upload, email, sms, gmail, google_drive

List Receipts — Request
query {
  listReceipts(
    organizationId: "a1b2c3d4-..."
    limit: 10
    status: "draft"
  ) {
    items {
      id
      vendorName
      amount
      receiptDate
      status
      source
      imageUrl
      event { id merchantName status }
    }
    totalCount
  }
}
Response
{
  "data": {
    "listReceipts": {
      "items": [
        {
          "id": "r1a2b3c4-...",
          "vendorName": "Tim Hortons",
          "amount": 8.45,
          "receiptDate": "2026-01-15",
          "status": "draft",
          "source": "gmail",
          "imageUrl": "https://s3.amazonaws.com/...",
          "event": {
            "id": "e5f6g7h8-...",
            "merchantName": "Tim Hortons",
            "status": "needs_review"
          }
        }
      ],
      "totalCount": 12
    }
  }
}

Reports

POST

Dashboard Summary

High-level financial snapshot: revenue, expenses, net income, receipt counts for a date range.

Parameters

organizationIdUUID!required

Organization ID

startDateDate

Start of range (defaults to start of current month)

endDateDate

End of range (defaults to today)

POST

Income Statement

Full profit & loss report broken down by account.

Parameters

organizationIdUUID!required

Organization ID

startDateDate

Start of range

endDateDate

End of range

POST

Account Balances

Current balance for every account in the chart of accounts.

Parameters

organizationIdUUID!required

Organization ID

Dashboard Summary — Request
query {
  dashboardSummary(
    organizationId: "a1b2c3d4-..."
    startDate: "2025-01-01"
    endDate: "2025-03-31"
  ) {
    totalRevenue
    totalExpenses
    netIncome
    receiptCount
    unreviewedCount
  }
}
Response
{
  "data": {
    "dashboardSummary": {
      "totalRevenue": "84500.00",
      "totalExpenses": "52300.00",
      "netIncome": "32200.00",
      "receiptCount": 89,
      "unreviewedCount": 3
    }
  }
}
Income Statement — Request
query {
  incomeStatement(
    organizationId: "a1b2c3d4-..."
    startDate: "2025-01-01"
    endDate: "2025-03-31"
  ) {
    incomeAccounts { accountId accountName total }
    expenseAccounts { accountId accountName total }
    totalIncome
    totalExpenses
    netIncome
  }
}
Response
{
  "data": {
    "incomeStatement": {
      "incomeAccounts": [
        { "accountId": "...", "accountName": "Sales", "total": "84500.00" }
      ],
      "expenseAccounts": [
        { "accountId": "...", "accountName": "Rent", "total": "18000.00" },
        { "accountId": "...", "accountName": "Payroll", "total": "28000.00" },
        { "accountId": "...", "accountName": "Office Supplies", "total": "6300.00" }
      ],
      "totalIncome": "84500.00",
      "totalExpenses": "52300.00",
      "netIncome": "32200.00"
    }
  }
}
Account Balances — Request
query {
  accountBalances(organizationId: "a1b2c3d4-...") {
    accountId
    accountName
    accountType
    classification
    balance
  }
}
Response
{
  "data": {
    "accountBalances": [
      {
        "accountId": "...",
        "accountName": "Checking",
        "accountType": "bank",
        "classification": "asset",
        "balance": "24680.50"
      },
      {
        "accountId": "...",
        "accountName": "Accounts Receivable",
        "accountType": "accounts_receivable",
        "classification": "asset",
        "balance": "12400.00"
      }
    ]
  }
}

Bank Import

Import bank statement rows as transactions. Each row becomes a LedgerEvent with status needs_review. Use checkBankStatementDups first to detect duplicates.

POST

Check Bank Statement Duplicates

Check rows against existing transactions before importing. Returns per-row duplicate flags. Full match = same date + amount + vendor. Possible match = same date + amount, different vendor.

Parameters

organizationIdUUID!required

Organization ID

paymentAccountIdUUID!required

Bank or credit card account to check against

rows[BankStatementRowInput!]!required

Array of statement rows to check. Each row: transactionDate (String!), description (String!), amount (String!), eventType (String!), memo (String), reference (String)

POST

Import Bank Statement

Imports bank statement rows as LedgerEvents attached to a payment account. Each row is created with status needs_review. Rows that exactly match existing transactions (same date, amount, and vendor) are skipped automatically.

Parameters

organizationIdUUID!required

Organization ID

paymentAccountIdUUID!required

Bank or credit card account to import into

currencyString

ISO 4217 currency code (default "CAD")

rows[BankStatementRowInput!]!required

Array of statement rows. Each row: transactionDate (String!), description (String!), amount (String!), eventType (String!), memo (String), reference (String)

Check Bank Statement Duplicates — Request
mutation {
  checkBankStatementDups(
    organizationId: "a1b2c3d4-..."
    paymentAccountId: "chequing-uuid"
    rows: [
      {
        transactionDate: "2025-03-01"
        description: "SHOPIFY *MAPLE ROASTERS"
        amount: "149.50"
        eventType: "expense"
      },
      {
        transactionDate: "2025-03-02"
        description: "TIM HORTONS #4821"
        amount: "11.30"
        eventType: "expense"
        memo: "Team coffee run"
      }
    ]
  ) {
    results {
      rowIndex
      isDuplicate
      isPossibleDuplicate
      matchingEventId
    }
    success
  }
}
Response
{
  "data": {
    "checkBankStatementDups": {
      "results": [
        {
          "rowIndex": 0,
          "isDuplicate": false,
          "isPossibleDuplicate": false,
          "matchingEventId": null
        },
        {
          "rowIndex": 1,
          "isDuplicate": true,
          "isPossibleDuplicate": false,
          "matchingEventId": "d8e9f0a1-..."
        }
      ],
      "success": true
    }
  }
}
Import Bank Statement — Request
mutation {
  importBankStatement(
    organizationId: "a1b2c3d4-..."
    paymentAccountId: "chequing-uuid"
    currency: "CAD"
    rows: [
      {
        transactionDate: "2025-03-01"
        description: "SHOPIFY *MAPLE ROASTERS"
        amount: "149.50"
        eventType: "expense"
        reference: "TXN-20250301-001"
      },
      {
        transactionDate: "2025-03-01"
        description: "E-TRANSFER FROM CLAIRE DUBOIS"
        amount: "2500.00"
        eventType: "income"
        memo: "Invoice #1042 payment"
      },
      {
        transactionDate: "2025-03-03"
        description: "CANADA POST CORP"
        amount: "18.75"
        eventType: "expense"
        memo: "Shipping label — order #887"
        reference: "CP-88712345"
      }
    ]
  ) {
    importedCount
    skippedCount
    success
    error
  }
}
Response
{
  "data": {
    "importBankStatement": {
      "importedCount": 3,
      "skippedCount": 0,
      "success": true,
      "error": null
    }
  }
}

Category Rules

Auto-categorization rules map vendor name patterns to accounts. When a transaction's merchant name matches a rule's pattern, Solarium automatically fills in the expense category during import and review. Rules are deterministic and auditable — they complement the AI agent's suggestions with a predictable, user-controlled layer.

POST

List Category Rules

List auto-categorization rules. Rules map vendor name patterns to accounts. When a transaction's merchant matches a pattern, Solarium auto-fills the category.

Parameters

organizationIdUUID!required

Organization ID

limitInt

Page size (default 25, max 200)

offsetInt

Number of records to skip

searchString

Filter by vendor pattern (case-insensitive)

sortByString

Field to sort by (e.g. vendorPattern, matchCount)

sortDirString

asc or desc (default asc)

POST

Create a Category Rule

Create an auto-categorization rule. The vendorPattern is matched case-insensitively against merchant names. Use SQL ILIKE patterns (% for wildcard).

Parameters

organizationIdUUID!required

Organization ID

vendorPatternString!required

ILIKE pattern to match vendor names (e.g. "%SHOPIFY%", "%CANADA POST%")

categoryIdUUID!required

Account ID to auto-assign when pattern matches

List Category Rules — Request
query {
  listCategoryRules(
    organizationId: "a1b2c3d4-..."
    limit: 10
    search: "tim"
  ) {
    items {
      id
      vendorPattern
      account { id name }
      matchCount
    }
    totalCount
    pageSize
    offset
  }
}
Response
{
  "data": {
    "listCategoryRules": {
      "items": [
        {
          "id": "r1a2b3c4-...",
          "vendorPattern": "%TIM HORTONS%",
          "account": {
            "id": "f7e6d5c4-...",
            "name": "Meals & Entertainment"
          },
          "matchCount": 34
        },
        {
          "id": "r5d6e7f8-...",
          "vendorPattern": "%TIMMIES%",
          "account": {
            "id": "f7e6d5c4-...",
            "name": "Meals & Entertainment"
          },
          "matchCount": 3
        }
      ],
      "totalCount": 2,
      "pageSize": 10,
      "offset": 0
    }
  }
}
Create a Category Rule — Request
mutation {
  createCategoryRule(
    organizationId: "a1b2c3d4-..."
    vendorPattern: "%PETRO-CANADA%"
    categoryId: "vehicle-expense-uuid"
  ) {
    categoryRule {
      id
      vendorPattern
      account { id name }
    }
    success
    error
  }
}
Response
{
  "data": {
    "createCategoryRule": {
      "categoryRule": {
        "id": "r9a8b7c6-...",
        "vendorPattern": "%PETRO-CANADA%",
        "account": {
          "id": "vehicle-expense-uuid",
          "name": "Vehicle Expenses"
        }
      },
      "success": true,
      "error": null
    }
  }
}

Batch Operations

All batch mutations accept up to 500 items per call. Failures are per-item — one bad record does not roll back the others. Each item runs in its own database savepoint.

For bulk transaction import, use importBankStatement (see Bank Import) which accepts an array of rows and creates LedgerEvents.

POST

Batch Create Accounts

Create up to 500 accounts in a single call.

Parameters

organizationIdUUID!required

Organization ID

items[AccountInput!]!required

Array of accounts. Each: name (String!), accountType (String!), classification (String!), subType (String), parentId (UUID)

POST

Batch Create Contacts

Create up to 500 contacts in a single call.

Parameters

organizationIdUUID!required

Organization ID

items[ContactInput!]!required

Array of contacts. Each: contactType (String!), displayName (String!), email (String), phone (String)

Batch Create Accounts — Request
mutation {
  batchCreateAccounts(
    organizationId: "a1b2c3d4-..."
    items: [
      { name: "Office Supplies", accountType: "expense", classification: "expense" },
      { name: "Travel", accountType: "expense", classification: "expense" },
      { name: "Software", accountType: "expense", classification: "expense" }
    ]
  ) {
    created
    errors { index error }
  }
}
Response
{
  "data": {
    "batchCreateAccounts": {
      "created": 3,
      "errors": []
    }
  }
}
Batch Create Contacts — Request
mutation {
  batchCreateContacts(
    organizationId: "a1b2c3d4-..."
    items: [
      { contactType: "vendor", displayName: "Staples" },
      { contactType: "vendor", displayName: "Amazon" }
    ]
  ) {
    created
    errors { index error }
  }
}
Response
{
  "data": {
    "batchCreateContacts": {
      "created": 2,
      "errors": []
    }
  }
}

Intake Addresses

POST

List Intake Addresses

Returns all registered email addresses and phone numbers that route receipts to the organization.

Parameters

organizationIdUUID!required

Organization ID

POST

Create Intake Address

Register a new email address or phone number for receipt intake. Each address is globally unique — it can only belong to one organization.

Parameters

organizationIdUUID!required

Organization ID

addressTypeString!required

"email" or "sms"

addressString!required

Email address or phone number (digits only for SMS)

labelString

Optional display label (e.g. 'Main Office')

POST

Delete Intake Address

Remove a registered intake address. Receipts from this address will no longer route to the organization.

Parameters

organizationIdUUID!required

Organization ID

intakeAddressIdUUID!required

Intake address ID to remove

List Intake Addresses — Request
query {
  listIntakeAddresses(organizationId: "a1b2c3d4-...") {
    id
    addressType
    address
    label
    active
    created
  }
}
Response
{
  "data": {
    "listIntakeAddresses": [
      {
        "id": "e1f2a3b4-...",
        "addressType": "email",
        "address": "[email protected]",
        "label": "Main Office",
        "active": true,
        "created": "2026-05-12T10:30:00Z"
      },
      {
        "id": "c5d6e7f8-...",
        "addressType": "sms",
        "address": "14165551234",
        "label": "Owner",
        "active": true,
        "created": "2026-05-12T10:00:00Z"
      }
    ]
  }
}
Create Intake Address — Request
mutation {
  createIntakeAddress(
    organizationId: "a1b2c3d4-..."
    addressType: "email"
    address: "[email protected]"
    label: "Shared Inbox"
  ) {
    intakeAddress {
      id
      addressType
      address
      label
    }
    success
    error
  }
}
Response
{
  "data": {
    "createIntakeAddress": {
      "intakeAddress": {
        "id": "f9a8b7c6-...",
        "addressType": "email",
        "address": "[email protected]",
        "label": "Shared Inbox"
      },
      "success": true,
      "error": null
    }
  }
}
Delete Intake Address — Request
mutation {
  deleteIntakeAddress(
    organizationId: "a1b2c3d4-..."
    intakeAddressId: "f9a8b7c6-..."
  ) {
    success
    error
  }
}
Response
{
  "data": {
    "deleteIntakeAddress": {
      "success": true,
      "error": null
    }
  }
}

Stripe Import

A management command that imports Stripe balance_history.csv exports into Solarium as transactions (LedgerEvents). Works with Stripe Connect platforms and direct Stripe accounts.

Uses the gross revenue method — charges are booked as income (Sales Revenue), connected-account payouts as expense (Cost of Goods Sold).

CSV Row TypeEvent TypeCategory
chargeincomeSales Revenue
transfer (merchant payout)expenseCost of Goods Sold
stripe_feeexpenseBank Fees & Charges
refundrefundSales Revenue
payout (to bank)transferStripe → Chequing Account
application_feeskippedImplicit in gross
  • Deduplicates by Stripe source ID stored in backfill_id — safe to re-run
  • Each run gets a batch ID for clean reversal via delete_stripe_imports
  • --dry-run previews without writing
  • --org-name is required (no default)
POST

Import Command

Run on the server to import a Stripe balance history CSV.

POST

Reverse Import

Delete all events from a specific import batch, or all Stripe imports for an org.

Import Command — Request
# Dry run — preview what would be created
python manage.py import_stripe_balance ~/balance_history.csv \
  --org-name "My Company Inc." --dry-run

# Real import
python manage.py import_stripe_balance ~/balance_history.csv \
  --org-name "My Company Inc."

# Verify idempotency (should create 0, skip all)
python manage.py import_stripe_balance ~/balance_history.csv \
  --org-name "My Company Inc."
Response
# Dry run output:
  2026-06-21 income     $     23.55  Stripe Revenue
  2026-06-21 expense    $     13.21  Stripe Merchant Payout tr_1Tkqw...
  2026-06-17 expense    $      3.20  Stripe Fee
  2026-06-04 transfer   $   1300.00  Stripe Payout To Bank
  2026-06-04 refund     $      4.99  Stripe Refund

Batch: si:20260621235202
Created: 61, Skipped (app_fee): 9, Skipped (dupe): 0
(Dry Run — Nothing Written)

# Real run output:
Batch: si:20260621235202
Created: 61, Skipped (app_fee): 9, Skipped (dupe): 0

# Re-run output (idempotent):
Created: 0, Skipped (app_fee): 9, Skipped (dupe): 61
Reverse Import — Request
# Delete a specific batch
python manage.py delete_stripe_imports \
  --org-name "My Company Inc." \
  --batch-id si:20260621235202

# Delete all Stripe imports for an org
python manage.py delete_stripe_imports \
  --org-name "My Company Inc."

# Dry run first
python manage.py delete_stripe_imports \
  --org-name "My Company Inc." --dry-run
Response
# Dry run:
Found 61 Stripe Import Events To Delete.
  Would Delete: 2026-06-21 $23.55 Stripe Revenue (si:20260621235202:ch_3Tknn1...)
  Would Delete: 2026-06-21 $13.21 Stripe Merchant Payout tr_1Tkqw... (si:20260621235202:tr_1Tkqw...)
  ... And 59 More

# Real delete:
Deleted 61 Stripe Import Events.

Venn / Bank CSV Import

Import bank transaction CSVs (Venn, TD, or any bank) into Solarium as transactions via the importBankStatement API or the CSV import page in the admin UI.

The CSV import flow:

1. Upload CSV on the Import page

2. Map columns (date, description, amount)

3. Select payment account

4. Preview rows with duplicate detection

5. Import — each row becomes a LedgerEvent with needs_review status

Transaction TypeEvent Type
Card payments (expenses)expense
Loads / fundingtransfer
Interest / cashbackincome

Duplicate detection runs automatically: exact matches (same date + amount + vendor) are flagged as duplicates, partial matches (same date + amount, different vendor) as possible duplicates.

POST

CSV Import via API

Use the importBankStatement mutation to import rows programmatically.

POST

Check Duplicates Before Import

Run checkBankStatementDups first to detect duplicates. Returns per-row flags so you can skip or override.

CSV Import via API — Request
mutation {
  importBankStatement(
    organizationId: "a1b2c3d4-..."
    paymentAccountId: "bank-account-uuid"
    currency: "CAD"
    rows: [
      {
        transactionDate: "2026-06-19"
        description: "MVC INC."
        amount: "-23.55"
        eventType: "expense"
      },
      {
        transactionDate: "2026-06-13"
        description: "Account Funding"
        amount: "500.00"
        eventType: "income"
      }
    ]
  ) {
    importedCount
    skippedCount
    success
    error
  }
}
Response
{
  "data": {
    "importBankStatement": {
      "importedCount": 2,
      "skippedCount": 0,
      "success": true,
      "error": null
    }
  }
}
Check Duplicates Before Import — Request
mutation {
  checkBankStatementDups(
    organizationId: "a1b2c3d4-..."
    paymentAccountId: "bank-account-uuid"
    rows: [
      {
        transactionDate: "2026-06-19"
        description: "MVC INC."
        amount: "23.55"
        eventType: "expense"
      }
    ]
  ) {
    results {
      rowIndex
      isDuplicate
      isPossibleDuplicate
      matchingEventId
    }
    success
  }
}
Response
{
  "data": {
    "checkBankStatementDups": {
      "results": [
        {
          "rowIndex": 0,
          "isDuplicate": true,
          "isPossibleDuplicate": false,
          "matchingEventId": "e5f6g7h8-..."
        }
      ],
      "success": true
    }
  }
}