# createTransaction

createTransaction — creates a new transaction for the authenticated user.

Last updated October 2, 2026

## Reference

Authentication: **bearer token required** (see [how to get a token](https://financesavi.com/developers/authentication#get-a-token))

```
createTransaction — creates a new transaction for the authenticated user.

Authentication: Required (Bearer token). The user must be the owner or collaborator of the target account.

Parameters:
- accountId (string, required unless type is JOURNAL): Account the transaction belongs to
- name (string, required): Transaction display name
- amount (float, required): Amount in smallest denomination (negative = expense, positive = income)
- currency (string, optional): 3-letter ISO currency code for transaction denomination
- category (string, required unless type is JOURNAL): Top-level category (FOOD, INCOME, RENT, TRANSPORTATION, …)
- transactedAt (string, required): ISO 8601 / RFC3339 datetime of the transaction
- fileIds ([string], required): List of attached file IDs (pass [] for none)
- type (string, optional): INCOME | EXPENSE | TRANSFER | JOURNAL — inferred from amount/category if omitted
- categoryId (string, optional): v2 category ID — resolved from category if omitted
- incomeCategory (string, optional): Income sub-category
- expenseCategory (string, optional): Expense sub-category
- subcategory (string, optional): Sub-category label
- vendorId (string, optional): Vendor ID
- vendorName (string, optional): Vendor display name
- description (string, optional): Additional notes
- tagIds ([string], optional): Tag IDs to attach
- cronExpression (string, optional): Recurrence cron expression
- endDate (string, optional): ISO 8601 recurrence end date
- isExcludedFromAnalytics (bool, optional): Exclude from spending analytics
- lineItems ([{id, name, amount}], optional): Itemised line items
- oneOffSplitParticipants ([{name, amount}], optional): Freeform Split With Others rows stored directly on the transaction. The server generates persisted row ids; names are display labels only and do not identify or notify another user.
- appleCardTransaction ({transactionId}, optional): Apple Card / FinanceKit metadata; triggers idempotent create and BANK_INTEGRATION source
- toAccountId (string, optional): Transfer destination account. accountId is the source and amount must be negative. Both accounts' balances change.
- toAmount (float, optional): Amount the destination receives in its own currency, when it differs from the source's.
- lines ([{accountRef: {kind, id}, side, amount, memo?}], optional): JOURNAL entry lines; requires type JOURNAL. kind is ACCOUNT, CATEGORY or SYSTEM (OPENING_BALANCE_EQUITY, UNCATEGORIZED_INCOME, UNCATEGORIZED_EXPENSE, FX_GAIN_LOSS); side is DEBIT or CREDIT; amount is a positive integer in minor units. Debits must equal credits. accountId and amount are derived from the lines and category is optional.
- clientRequestId (string, optional): Caller-chosen idempotency key, 1-128 characters of letters, digits, '-' or '_' (a UUID works). Repeating a call with the same key on the same account returns the first call's transactionId and creates nothing.

Returns:
- transactionId (string): Newly created (or idempotently matched) transaction ID
- budgetAlertPending (boolean, optional): The transaction was saved, and its alert bookkeeping will be retried in the background
```

## createTransaction request

POST https://api-v2.financesavi.com/rpc · Bearer token required

### cURL

```bash
curl -sS --max-time 30 -X POST 'https://api-v2.financesavi.com/rpc' \
  -H 'Content-Type: application/json' \
  -H "Authorization: Bearer $SAVI_TOKEN" \
  -d '{"jsonrpc":"2.0","method":"createTransaction","params":{"accountId":"account_5kQe2Xv9Lm","name":"Coffee Shop","amount":-450,"currency":"CAD","category":"FOOD","transactedAt":"2026-10-01T09:30:00Z","fileIds":[]},"id":1}'
```

### JavaScript

```javascript
const response = await fetch("https://api-v2.financesavi.com/rpc", {
  method: 'POST',
  signal: AbortSignal.timeout(30_000),
  headers: {
    'Content-Type': 'application/json',
    Authorization: `Bearer ${process.env.SAVI_TOKEN}`,
  },
  body: JSON.stringify({
      "jsonrpc": "2.0",
      "method": "createTransaction",
      "params": {
        "accountId": "account_5kQe2Xv9Lm",
        "name": "Coffee Shop",
        "amount": -450,
        "currency": "CAD",
        "category": "FOOD",
        "transactedAt": "2026-10-01T09:30:00Z",
        "fileIds": []
      },
      "id": 1
    }),
});
console.log(await response.json());
```

### TypeScript

```typescript
const response = await fetch("https://api-v2.financesavi.com/rpc", {
  method: 'POST',
  signal: AbortSignal.timeout(30_000),
  headers: {
    'Content-Type': 'application/json',
    Authorization: `Bearer ${process.env.SAVI_TOKEN}`,
  },
  body: JSON.stringify({
      "jsonrpc": "2.0",
      "method": "createTransaction",
      "params": {
        "accountId": "account_5kQe2Xv9Lm",
        "name": "Coffee Shop",
        "amount": -450,
        "currency": "CAD",
        "category": "FOOD",
        "transactedAt": "2026-10-01T09:30:00Z",
        "fileIds": []
      },
      "id": 1
    }),
});
console.log(await response.json());
```

### Python

```python
import os
import requests

response = requests.post(
    "https://api-v2.financesavi.com/rpc",
    headers={"Authorization": f"Bearer {os.environ['SAVI_TOKEN']}"},
    json={
        "jsonrpc": "2.0",
        "method": "createTransaction",
        "params": {
            "accountId": "account_5kQe2Xv9Lm",
            "name": "Coffee Shop",
            "amount": -450,
            "currency": "CAD",
            "category": "FOOD",
            "transactedAt": "2026-10-01T09:30:00Z",
            "fileIds": [],
        },
        "id": 1,
    },
    timeout=30,
)
print(response.json())
```

### Go

```go
package main

import (
  "fmt"
  "io"
  "net/http"
  "os"
  "strings"
  "time"
)

func main() {
  payload := strings.NewReader("{\"jsonrpc\":\"2.0\",\"method\":\"createTransaction\",\"params\":{\"accountId\":\"account_5kQe2Xv9Lm\",\"name\":\"Coffee Shop\",\"amount\":-450,\"currency\":\"CAD\",\"category\":\"FOOD\",\"transactedAt\":\"2026-10-01T09:30:00Z\",\"fileIds\":[]},\"id\":1}")
  req, err := http.NewRequest("POST", "https://api-v2.financesavi.com/rpc", payload)
  if err != nil { panic(err) }
  req.Header.Set("Content-Type", "application/json")
  req.Header.Set("Authorization", "Bearer " + os.Getenv("SAVI_TOKEN"))
  client := &http.Client{Timeout: 30 * time.Second}
  res, err := client.Do(req)
  if err != nil { panic(err) }
  defer res.Body.Close()
  body, err := io.ReadAll(res.Body)
  if err != nil { panic(err) }
  fmt.Println(res.Status, string(body))
}
```

### C#

```csharp
// .NET 6 or later
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;

using var client = new HttpClient { Timeout = TimeSpan.FromSeconds(30) };
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", Environment.GetEnvironmentVariable("SAVI_TOKEN"));
using var content = new StringContent(@"{""jsonrpc"":""2.0"",""method"":""createTransaction"",""params"":{""accountId"":""account_5kQe2Xv9Lm"",""name"":""Coffee Shop"",""amount"":-450,""currency"":""CAD"",""category"":""FOOD"",""transactedAt"":""2026-10-01T09:30:00Z"",""fileIds"":[]},""id"":1}", Encoding.UTF8, "application/json");
using var response = await client.PostAsync(@"https://api-v2.financesavi.com/rpc", content);
Console.WriteLine($"HTTP {(int)response.StatusCode}");
Console.WriteLine(await response.Content.ReadAsStringAsync());
```

### Java

```java
// Java 11+: save as SaviExample.java, then run java SaviExample.java
import java.net.URI;
import java.net.http.*;
import java.time.Duration;

class SaviExample {
  public static void main(String[] args) throws Exception {
    var client = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(30)).build();
    var builder = HttpRequest.newBuilder(URI.create("https://api-v2.financesavi.com/rpc"))
        .timeout(Duration.ofSeconds(30))
        .header("Content-Type", "application/json");
    builder.header("Authorization", "Bearer " + System.getenv("SAVI_TOKEN"));
    var request = builder.POST(HttpRequest.BodyPublishers.ofString("{\"jsonrpc\":\"2.0\",\"method\":\"createTransaction\",\"params\":{\"accountId\":\"account_5kQe2Xv9Lm\",\"name\":\"Coffee Shop\",\"amount\":-450,\"currency\":\"CAD\",\"category\":\"FOOD\",\"transactedAt\":\"2026-10-01T09:30:00Z\",\"fileIds\":[]},\"id\":1}")).build();
    var response = client.send(request, HttpResponse.BodyHandlers.ofString());
    System.out.println("HTTP " + response.statusCode());
    System.out.println(response.body());
  }
}
```

### PHP

```php
<?php
// Requires the PHP cURL extension.
$curl = curl_init('https://api-v2.financesavi.com/rpc');
curl_setopt_array($curl, [
    CURLOPT_POST => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CONNECTTIMEOUT => 30,
    CURLOPT_TIMEOUT => 30,
    CURLOPT_HTTPHEADER => [
        'Content-Type: application/json',
        'Authorization: Bearer ' . getenv('SAVI_TOKEN'),
    ],
    CURLOPT_POSTFIELDS => '{"jsonrpc":"2.0","method":"createTransaction","params":{"accountId":"account_5kQe2Xv9Lm","name":"Coffee Shop","amount":-450,"currency":"CAD","category":"FOOD","transactedAt":"2026-10-01T09:30:00Z","fileIds":[]},"id":1}',
]);
$body = curl_exec($curl);
if ($body === false) {
    throw new RuntimeException(curl_error($curl));
}
echo "HTTP " . curl_getinfo($curl, CURLINFO_HTTP_CODE) . PHP_EOL;
echo $body . PHP_EOL;
curl_close($curl);
```

### Ruby

```ruby
require 'net/http'
require 'uri'

uri = URI('https://api-v2.financesavi.com/rpc')
request = Net::HTTP::Post.new(uri)
request['Content-Type'] = 'application/json'
request['Authorization'] = 'Bearer ' + ENV.fetch('SAVI_TOKEN')
request.body = '{"jsonrpc":"2.0","method":"createTransaction","params":{"accountId":"account_5kQe2Xv9Lm","name":"Coffee Shop","amount":-450,"currency":"CAD","category":"FOOD","transactedAt":"2026-10-01T09:30:00Z","fileIds":[]},"id":1}'
response = Net::HTTP.start(uri.host, uri.port,
  use_ssl: uri.scheme == 'https', open_timeout: 30, read_timeout: 30, write_timeout: 30
) { |http| http.request(request) }
puts "HTTP #{response.code}"
puts response.body
```

### Swift

```swift
// Swift 5.5+: save as SaviExample.swift, then run swift SaviExample.swift
import Foundation
#if canImport(FoundationNetworking)
import FoundationNetworking
#endif

var request = URLRequest(url: URL(string: "https://api-v2.financesavi.com/rpc")!)
request.httpMethod = "POST"
request.timeoutInterval = 30
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
guard let token = ProcessInfo.processInfo.environment["SAVI_TOKEN"] else { fatalError("Set SAVI_TOKEN first") }
request.setValue("Bearer " + token, forHTTPHeaderField: "Authorization")
request.httpBody = Data("{\"jsonrpc\":\"2.0\",\"method\":\"createTransaction\",\"params\":{\"accountId\":\"account_5kQe2Xv9Lm\",\"name\":\"Coffee Shop\",\"amount\":-450,\"currency\":\"CAD\",\"category\":\"FOOD\",\"transactedAt\":\"2026-10-01T09:30:00Z\",\"fileIds\":[]},\"id\":1}".utf8)
let configuration = URLSessionConfiguration.ephemeral
configuration.timeoutIntervalForResource = 30
let session = URLSession(configuration: configuration)
let (data, response) = try await session.data(for: request)
if let http = response as? HTTPURLResponse { print("HTTP", http.statusCode) }
print(String(decoding: data, as: UTF8.self))
```

### Local API capture (scrubbed) (HTTP 200)

```json
{
  "jsonrpc": "2.0",
  "result": {
    "transactionId": "tx_9bLm4Rt7Yc"
  },
  "id": 1
}
```

Source: https://financesavi.com/developers/api/createTransaction
