Transfers

Transfers represent money movements between accounts within Crane Ledger. They automatically create balanced double-entry transactions and are used for cash management, account reconciliations, and internal fund allocations.

The Transfer Object

POST/organizations/:organization_id/transfers
Auth required
1 credits

A transfer represents a money movement between two accounts.

{
  "id": "trf_xxxxxxxxxxxxxxxx",
  "object": "transfer",
  "organization_id": "org_xxxxxxxxxxxxxxxx",
  "from_account_id": "act_xxxxxxxxxxxxxxxx",
  "to_account_id": "act_yyyyyyyyyyyyyyyy",
  "expense_transaction_id": "txn_xxxxxxxxxxxxxxxx",
  "income_transaction_id": "txn_yyyyyyyyyyyyyyyy",
  "amount": "5000.00",
  "currency_id": "CUR_USD",
  "currency_rate": "1.0000",
  "transferred_at": "2024-01-15",
  "description": "Transfer to operating account",
  "reference": "TRF-2024-001",
  "created_by": "usr_xxxxxxxxxxxxxxxx",
  "created_at": "2024-01-15T10:30:00Z",
  "updated_at": "2024-01-15T10:30:00Z",
  "metadata": {
    "purpose": "cash_management",
    "approved_by": "finance_manager"
  }
}

Attributes

AttributeTypeDescription
idstringUnique identifier with trf_ prefix
from_account_idstringSource account ID
to_account_idstringDestination account ID
amountstringTransfer amount
currency_idstringTransfer currency
transferred_atdateTransfer date
descriptionstringTransfer description
referencestringReference number
expense_transaction_idstringExpense transaction created
income_transaction_idstringIncome transaction created

Create Transfer

POST/organizations/:organization_id/transfers
Auth required
1 credits

Creates a transfer between two accounts, automatically generating the corresponding double-entry transactions.

Path Parameters

ParameterTypeRequiredDescription
organization_idstringYesThe ID of the organization

Request Body

ParameterTypeRequiredDescription
from_account_idstringYesSource account ID
to_account_idstringYesDestination account ID
amountnumberYesTransfer amount
transferred_atdateYesTransfer date (YYYY-MM-DD)
descriptionstringNoTransfer description
referencestringNoReference number

Response

{
  "request_id": "req_xxxxxxxxxxxxxxxx",
  "success": true,
  "data": {
    "status": "queued",
    "estimated_completion": "2024-01-15T10:30:05Z",
    "transfer_preview": {
      "from_account": "Operating Account",
      "to_account": "Savings Account",
      "amount": "50000.00",
      "expense_transaction_id": "txn_xxxxxxxxxxxxxxxx",
      "income_transaction_id": "txn_yyyyyyyyyyyyyyyy"
    }
  },
  "error": null,
  "duration_ms": 0,
  "credit_cost": 1
}

Asynchronous Processing

Transfer creation is processed asynchronously. The response includes a request_id that can be used to check the status of the operation. The transfer will be available for use once processing is complete.


List Transfers

GET/organizations/:organization_id/transfers
Auth required

Returns a list of transfers for the organization with optional filtering.

Path Parameters

ParameterTypeRequiredDescription
organization_idstringYesThe organization ID

Query Parameters

ParameterTypeDescription
from_account_idstringFilter by source account
to_account_idstringFilter by destination account
limitintegerNumber of results (default: 20, max: 100)
offsetintegerPagination offset

Response

{
  "object": "list",
  "data": [
    {
      "id": "trn_xxxxxxxxxxxxxxxx",
      "amount": "5000.00",
      "from_account_name": "Operating Checking",
      "to_account_name": "Savings Account",
      "transfer_date": "2024-01-15",
      "description": "Monthly savings transfer",
      "created_at": "2024-01-15T10:30:00Z"
    }
  ],
  "has_more": false,
  "total_count": 1
}

Get Transfer

GET/organizations/:organization_id/transfers/:id
Auth required

Retrieves the details of a specific transfer.

Path Parameters

ParameterTypeRequiredDescription
organization_idstringYesThe organization ID
idstringYesThe transfer ID

Response

Returns the complete transfer object with account details and transaction information.


Update Transfer

PUT/organizations/:organization_id/transfers/:id
Auth required
1 credits

Updates an existing transfer.

Path Parameters

ParameterTypeRequiredDescription
organization_idstringYesThe organization ID
idstringYesThe transfer ID

Request Body

ParameterTypeDescription
amountstringUpdated transfer amount
transfer_datedateUpdated transfer date
descriptionstringUpdated description

Delete Transfer

DELETE/organizations/:organization_id/transfers/:id
Auth required
1 credits

Deletes an existing transfer and reverses the associated accounting transactions.

Path Parameters

ParameterTypeRequiredDescription
organization_idstringYesThe organization ID
idstringYesThe transfer ID

Transfer Types

Cash Management Transfers

{
  "transfers": [
    {
      "type": "operating_reserve",
      "description": "Build operating cash reserve",
      "from_account": "business_checking",
      "to_account": "operating_reserve",
      "frequency": "monthly",
      "amount": "50000.00"
    },
    {
      "type": "emergency_fund",
      "description": "Emergency fund contribution",
      "from_account": "business_checking",
      "to_account": "emergency_fund",
      "frequency": "monthly",
      "amount": "10000.00"
    },
    {
      "type": "tax_payment",
      "description": "Transfer for quarterly tax payment",
      "from_account": "business_checking",
      "to_account": "tax_liability",
      "frequency": "quarterly",
      "amount": "variable"
    }
  ]
}

Account Reclassification

{
  "transfers": [
    {
      "type": "reclassification",
      "description": "Reclassify expense from wrong category",
      "from_account": "travel_expense",
      "to_account": "meals_expense",
      "amount": "450.00",
      "reason": "Meal expense misclassified as travel"
    }
  ]
}

Inter-Company Transfers

{
  "transfers": [
    {
      "type": "intercompany",
      "description": "Transfer from parent to subsidiary",
      "from_account": "parent_company_checking",
      "to_account": "subsidiary_checking",
      "amount": "25000.00",
      "metadata": {
        "subsidiary_id": "org_sub_001",
        "purpose": "working_capital"
      }
    }
  ]
}

Double-Entry Accounting

How Transfers Work

When you create a transfer, Crane Ledger automatically creates two balanced transactions:

  1. Expense Transaction (reduces the source account):

    • Debit: Destination Account (+)
    • Credit: Source Account (-)
  2. Income Transaction (increases the destination account):

    • Debit: Source Account (+)
    • Credit: Destination Account (-)

Example: Transfer from Checking to Savings

{
  "transfer": {
    "from_account_id": "act_checking",
    "to_account_id": "act_savings",
    "amount": "1000.00",
    "transferred_at": "2024-01-15"
  },
  "resulting_transactions": [
    {
      "type": "expense",
      "description": "Transfer to savings",
      "entries": [
        {
          "account_id": "act_checking",
          "entry_type": "credit",
          "amount": "1000.00"
        },
        {
          "account_id": "act_savings",
          "entry_type": "debit",
          "amount": "1000.00"
        }
      ]
    },
    {
      "type": "income",
      "description": "Transfer from checking",
      "entries": [
        {
          "account_id": "act_savings",
          "entry_type": "credit",
          "amount": "1000.00"
        },
        {
          "account_id": "act_checking",
          "entry_type": "debit",
          "amount": "1000.00"
        }
      ]
    }
  ]
}

Transfer Validation Rules

Account Compatibility

function validateTransferAccounts(fromAccount, toAccount) {
  const validations = {
    differentAccounts: fromAccount.id !== toAccount.id,
    sameCurrency: fromAccount.currency_id === toAccount.currency_id,
    sameOrganization: fromAccount.organization_id === toAccount.organization_id,
    activeAccounts: fromAccount.status === 'active' && toAccount.status === 'active',
    validAccountTypes: true
  };

  // Check account type compatibility
  const compatibleTypes = {
    'asset': ['asset', 'liability'],
    'liability': ['asset', 'liability', 'equity'],
    'equity': ['liability', 'equity']
  };

  validations.validAccountTypes = compatibleTypes[fromAccount.type]?.includes(toAccount.type);

  // Revenue and expense accounts typically don't receive transfers
  if ([fromAccount.type, toAccount.type].includes('revenue') ||
      [fromAccount.type, toAccount.type].includes('expense')) {
    validations.validAccountTypes = false;
  }

  return {
    isValid: Object.values(validations).every(v => v === true),
    validations
  };
}

// Usage
const validation = validateTransferAccounts(fromAccount, toAccount);
if (!validation.isValid) {
  console.log('Invalid transfer:', validation.validations);
}

Amount Validation

def validate_transfer_amount(amount, from_account, to_account):
    """Validate transfer amount and account balances"""

    validations = {
        'positive_amount': amount > 0,
        'sufficient_balance': True,
        'reasonable_amount': amount < 10000000,  # Business rule
        'currency_match': from_account.currency_id == to_account.currency_id
    }

    # Check available balance for asset accounts
    if from_account.type == 'asset':
        available_balance = get_account_balance(from_account.id)
        validations['sufficient_balance'] = available_balance >= amount

    # Business rules
    if amount > 500000:  # Large transfers require approval
        validations['requires_approval'] = True

    return {
        'is_valid': all(validations.values()),
        'validations': validations
    }

Transfer Workflows

Cash Management

def optimize_cash_position(accounts, target_ratios):
    """Optimize cash position across accounts"""

    transfers = []

    # Calculate current ratios
    total_cash = sum(acc.balance for acc in accounts if acc.type == 'asset')
    operating_balance = get_account_balance('operating_account')
    reserve_balance = get_account_balance('reserve_account')

    # Target operating balance (e.g., 30 days of expenses)
    target_operating = calculate_target_operating_balance()

    if operating_balance < target_operating:
        # Transfer from reserve to operating
        transfer_amount = min(
            target_operating - operating_balance,
            reserve_balance * 0.5  # Don't deplete reserve too much
        )

        if transfer_amount > 0:
            transfers.append({
                'from_account': 'reserve_account',
                'to_account': 'operating_account',
                'amount': transfer_amount,
                'description': 'Cash position optimization'
            })

    elif operating_balance > target_operating * 1.2:
        # Transfer excess to reserve
        transfer_amount = operating_balance - target_operating

        transfers.append({
            'from_account': 'operating_account',
            'to_account': 'reserve_account',
            'amount': transfer_amount,
            'description': 'Excess cash to reserve'
        })

    return transfers

Payroll Funding

// Transfer funds for payroll
async function fundPayroll(payrollAmount, payrollDate) {
  const transfers = [];

  // Check operating account balance
  const operatingBalance = await getAccountBalance('operating_account');

  if (operatingBalance < payrollAmount) {
    const shortfall = payrollAmount - operatingBalance;

    // Transfer from payroll reserve
    transfers.push({
      from_account_id: 'payroll_reserve',
      to_account_id: 'operating_account',
      amount: shortfall,
      transferred_at: payrollDate,
      description: `Payroll funding - ${payrollDate}`,
      reference: `PAYROLL-${payrollDate.replace(/-/g, '')}`
    });
  }

  // Execute transfers
  for (const transfer of transfers) {
    await createTransfer(transfer);
  }

  return {
    transfers_executed: transfers.length,
    total_transferred: transfers.reduce((sum, t) => sum + t.amount, 0)
  };
}

Transfer Scheduling

Recurring Transfers

{
  "scheduled_transfers": [
    {
      "id": "sched_monthly_reserve",
      "description": "Monthly operating reserve build",
      "from_account_id": "act_operating",
      "to_account_id": "act_reserve",
      "amount": "25000.00",
      "schedule": {
        "frequency": "monthly",
        "day_of_month": 1,
        "start_date": "2024-01-01",
        "end_date": "2024-12-31"
      },
      "auto_execute": true,
      "conditions": {
        "minimum_balance": "75000.00"
      }
    }
  ]
}

Conditional Transfers

def execute_conditional_transfer(transfer_config):
    """Execute transfer based on conditions"""

    conditions = transfer_config['conditions']

    # Check balance condition
    if 'minimum_balance' in conditions:
        from_balance = get_account_balance(transfer_config['from_account_id'])
        if from_balance < conditions['minimum_balance']:
            return {'status': 'skipped', 'reason': 'insufficient_balance'}

    # Check date conditions
    if 'execute_on_day' in conditions:
        today = datetime.now().day
        if today != conditions['execute_on_day']:
            return {'status': 'skipped', 'reason': 'wrong_day'}

    # Execute transfer
    return create_transfer(transfer_config)

Transfer Reporting

Transfer History

-- Get transfer history for period
SELECT
    t.id,
    t.transferred_at,
    fa.name as from_account,
    ta.name as to_account,
    t.amount,
    t.description,
    t.reference
FROM transfers t
JOIN accounts fa ON t.from_account_id = fa.id
JOIN accounts ta ON t.to_account_id = ta.id
WHERE t.organization_id = $1
  AND t.transferred_at BETWEEN $2 AND $3
ORDER BY t.transferred_at DESC;

Cash Flow Analysis

def analyze_transfer_impact(transfers, accounts):
    """Analyze how transfers affect cash flow"""

    impact = {
        'net_cash_movement': 0,
        'accounts_affected': set(),
        'cash_positions': {},
        'liquidity_changes': {}
    }

    for transfer in transfers:
        from_acc = accounts[transfer['from_account_id']]
        to_acc = accounts[transfer['to_account_id']]

        # Track affected accounts
        impact['accounts_affected'].update([transfer['from_account_id'], transfer['to_account_id']])

        # Calculate cash impact
        if from_acc.type == 'asset':
            impact['net_cash_movement'] -= transfer['amount']
        if to_acc.type == 'asset':
            impact['net_cash_movement'] += transfer['amount']

        # Update account positions
        impact['cash_positions'][transfer['from_account_id']] = \
            impact['cash_positions'].get(transfer['from_account_id'], 0) - transfer['amount']
        impact['cash_positions'][transfer['to_account_id']] = \
            impact['cash_positions'].get(transfer['to_account_id'], 0) + transfer['amount']

    return impact

Transfer Best Practices

Cash Management

  • Maintain Operating Reserves: Keep 3-6 months of expenses in liquid reserves
  • Optimize Interest: Move excess cash to high-yield accounts
  • Seasonal Planning: Build cash reserves before seasonal slowdowns

Internal Controls

  • Approval Workflows: Require approval for large transfers
  • Dual Authorization: Two people required for significant transfers
  • Audit Trail: Complete documentation for all transfers

Accounting Accuracy

  • Proper Classification: Use correct transfer dates
  • Consistent Descriptions: Clear, searchable transfer descriptions
  • Regular Reconciliation: Verify transfer posting accuracy

Transfer Reversibility

Transfers create permanent accounting entries. To reverse a transfer, create a new transfer in the opposite direction with appropriate documentation.


Need help?

Create a free account to access our support portal. Once signed in, use the Support tab in your dashboard to submit a support ticket — our team typically responds within 24 hours.