Sharing first-party transactions

Sharing batches of transactions with Pinwheel for ingestion and bill identification.

Overview

In addition to Bill Navigator detecting and managing bills from externally linked accounts, first-party transaction data can also be shared and used to reflect bills paid from internal accounts.

Transactions can be sent for multiple users without a link token needing to be created. Each transaction should be tied to an end_user_id who has, or will, have a link token created for Bill Navigator.

Formatting transactions

Transactions should be collected into TSV files with the following fields in this order, without a header:

  • transaction_id – your unique identifier for the transaction
  • end_user_id – your unique identifier for the account holder
  • institution_id – for internal accounts, use “internal”
  • account_name – a display name for the account
  • account_type – one of “depository”, “credit”, “other”
  • account_subtype – eg, "checking", "savings"
  • account_mask – the masked account number. This should be the last 4-8 digits of the account number to differentiate it to the holder
  • account_id – your unique identifier for the account
  • transaction_datetime – timestamp (UTC) when the transaction settled
  • amount – decimal amount. This should be a negative number for outgoings and positive for incomings
  • currency_code (optional) – this should be a 3 letter ISO 4217 code. Defaults to “USD” if blank
  • description – the transaction description
  • country_code (optional) – this should be a 2 letter ISO 3166-1 alpha-2 code. Defaults to “US” if blank
  • payment_channel (optional) – one of “online”, “in store”, “other”. Defaults to “other” if blank
  • transfer_type (optional) – "debit" (-) or "credit" (+). This overrides the signage of amount
  • transaction_status – one of “pending”, “posted”, “settled”, “funds_available”, “cancelled”, “failed”, “returned”. As updating transactions is not currently supported it is recommended that only final state transactions be included.
  • posted_datetime – timestamp (UTC) when the transaction posted
  • merchant_category_code – 4 digit merchant category code
  • enriched_transaction_details (optional) – this should be a JSON object of any additional details related to the transaction. Use the equivalent of jq -c compact form.

Rows can be best processed when grouped by end_user_id, institution_id, and the tuple of account data. Groupings are best processed when sorted by transaction_datetime.

Example six transaction TSV in this format

cb0efb74-5c8c-4388-931d-24bd66514191	01JVAWFJBDH56425F35VQYQ8T0	CITY NATIONAL BANK	Basic Checking	depository	checking	940316	33880117714386590940316	2025-03-22T00:15:49Z	828.61	USD	CASH APP*KALISTA HOWELL800-9691940  CAUS	US			settled	2025-03-22T03:57:11Z	5373	{"txn_uuid":"cb0efb74-5c8c-4388-931d-24bd66514191","account_uuid":"243e88f2-4ba8-45ef-84c1-cdd53bd19307","end_user_uuid":"b09d6d65-168e-4bac-882e-2e10671bc766"}
c456d28b-8f2c-406e-a5dc-d2c2763bde3e	01JVAWFJBDH56425F35VQYQ8T0	CITY NATIONAL BANK	Basic Checking	depository	checking	940316	33880117714386590940316	2025-03-23T13:09:54Z	1206.09	USD	CASH APP*CODY ENNIS*ADD800-9691940  CAUS	US	online		settled	2025-03-24T20:25:06Z	5091	{"txn_uuid":"0456d28b-8f2c-406e-a5dc-d2c2763bde3e","account_uuid":"243e88f2-4ba8-45ef-84c1-cdd53bd19307","end_user_uuid":"b09d6d65-168e-4bac-882e-2e10671bc766"}
ac5b680e-56ef-4fb4-86ca-c921d6976031	01JVAWFJBDH56425F35VQYQ8T0	CITY NATIONAL BANK	Basic Checking	depository	checking	940316	33880117714386590940316	2025-03-25T15:45:51Z	1705.10	USD	TICKPICK               NEW YORK     NYUS	US	online		settled	2025-03-28T11:34:56Z		{"txn_uuid":"ac5b680e-56ef-4fb4-86ca-c921d6976031","account_uuid":"243e88f2-4ba8-45ef-84c1-cdd53bd19307","end_user_uuid":"b09d6d65-168e-4bac-882e-2e10671bc766"}
a596a06a-2051-4c85-9d4d-2af8bd989e2a	01JVAWMQ79QS7E64JRD6TC5Z33	BMO HARRIS BANK	Basic Savings	depository	savings	2715		2023-01-03T10:32:35Z	1869.60	USD	LYFT   *TEMP AUTH HOLD +18552800278 CAUS	US	other	debit	settled	2023-01-03T21:37:50Z		{"txn_uuid":"7596a06a-2051-4c85-9d4d-2af8bd989e2a","account_uuid":"b3c909b9-cdf5-45e2-87ba-3be7a2814a3a","end_user_uuid":"03f9ad37-e83e-452d-b543-51485e9caa34"}
a5355f4b-85a5-4418-85b7-9b7ea80c3f47	01JVAWMQ79QS7E64JRD6TC5Z33	BMO HARRIS BANK	Basic Savings	depository	savings	2715		2023-01-03T12:59:24Z	728.06	USD	APPLE.COM/BILL         866-712-7753 CAUS	US	in store		settled	2023-01-04T12:06:42Z	3945	{"txn_uuid":"5a355f4b-85a5-4418-85b7-9b7ea80c3f47","account_uuid":"b3c909b9-cdf5-45e2-87ba-3be7a2814a3a","end_user_uuid":"03f9ad37-e83e-452d-b543-51485e9caa34"}
de012c23-19b0-44c1-a4db-f7ef0fc412f2	01JVAWMQ79QS7E64JRD6TC5Z33	BMO HARRIS BANK	Basic Savings	depository	savings	2715		2023-01-03T20:36:43Z	1033.14	USD	CASH APP*JACQUELYN HILL800-9691940  CAUS	US	other		settled	2023-01-04T01:46:18Z		{"txn_uuid":"de012c23-19b0-44c1-a4db-f7ef0fc412f2","account_uuid":"b3c909b9-cdf5-45e2-87ba-3be7a2814a3a","end_user_uuid":"03f9ad37-e83e-452d-b543-51485e9caa34"}

Posting transactions to Pinwheel

A single batch can consist of multiple files to upload. We suggest limiting the file size to 100MB. With your Pinwheel API key, POST to https://api.getpinwheel.com/gateway/v1/transactions_batch/files with the following JSON keyed values:

  • date_range_start – ISO 8601 formatted minimum transaction_datetime across files in the batch
  • date_range_end – ISO 8601 formatted maximum transaction_datetime across files in the batch.
  • file_count – an integer count of how many files you will upload in this batch. Max 100.
{ "data":{
  "date_range_start": "2025-01-01T00:00:00Z",
  "date_range_end":   "2025-12-31T00:00:00Z",
  "file_count":       1 }}

You will get a response including the batch_id, the upload_urls to which you can post the files to, and expires_at time. The URLs will be valid for just under 5 minutes given round-trip time.

> … send … >
POST /gateway/v1/transactions_batch/files 
Host: api.getpinwheel.com
Content-Type: application/json
Pinwheel-Version: 2025-07-08
x-api-secret: YOUR-API-SECRET
{
  "data": {
    "date_range_start": "2025-01-01T00:00:00Z",
    "date_range_end": "2025-12-31T00:00:00Z",
    "file_count": 2
  }
}
< … example response … <
{
  "batch_id": "0199c9e2-a9bf-7c9e-b426-c60a8b4b09ed",
  "upload_urls": [
    "https://….s3.amazonaws.com/upload/batched_transactions/ffffffff-ffff-ffff-ffff-ffffffffffff/2025-01-01_2025-12-31_0199c9e2-a9bf-7c9e-b426-c60a8b4b09ed_002_001.tsv.gz?AWSAccessKeyId=AS…&Signature=KIok…ndEU%3D&content-type=text%2Ftab-separated-values&x-amz-security-token=IQoJb3JpZ2luX……%3D%3D&Expires=1760115099",
    "https://….s3.amazonaws.com/upload/batched_transactions/ffffffff-ffff-ffff-ffff-ffffffffffff/2025-01-01_2025-12-31_0199c9e2-a9bf-7c9e-b426-c60a8b4b09ed_002_002.tsv.gz?AWSAccessKeyId=AS…&Signature=fYav…6ig%3D&content-type=text%2Ftab-separated-values&x-amz-security-token=IQoJb3JpZ2luX…%3D%3D&Expires=1760115099"
  ],
  "expires_at": "2025-10-10T16:51:39.081266"
}

You will then post your transaction batch files to the upload_urls provided.

Checking if transactions were ingested

With your Pinwheel API key, use the GET https://api.getpinwheel.com/gateway/v1/transactions_batch/files?batch_id={batch_id} endpoint to check the status of the ingestion.

> … send … >
GET /gateway/v1/transactions_batch/files?batch_id=0199c9e2-a9bf-7c9e-b426-c60a8b4b09ed 
Host: api.getpinwheel.com
Content-Type: application/json
Pinwheel-Version: 2025-07-08
x-api-secret: YOUR-API-SECRET
< … example response … <
{
  "batch_id": "0199c9e2-a9bf-7c9e-b426-c60a8b4b09ed",
  "status": "completed",
  "files": [{
	  "file_number": 1,
	  "file_status": "ingested",
	  "s3_presigned_uri": "https://….s3.amazonaws.com/upload/batched_transactions/ffffffff-ffff-ffff-ffff-ffffffffffff/2025-01-01_2025-12-31_0199c9e2-a9bf-7c9e-b426-c60a8b4b09ed_002_001.tsv.gz?AWSAccessKeyId=AS…&Signature=KIok…ndEU%3D&content-type=text%2Ftab-separated-values&x-amz-security-token=IQoJb3JpZ2luX……%3D%3D&Expires=1760115099",
	  "s3_presigned_expiration": "2025-10-10T16:51:39.081266"
  }, {
	  "file_number": 2,
	  "file_status": "ingested",
	  "s3_presigned_uri": "https://….s3.amazonaws.com/upload/batched_transactions/ffffffff-ffff-ffff-ffff-ffffffffffff/2025-01-01_2025-12-31_0199c9e2-a9bf-7c9e-b426-c60a8b4b09ed_002_002.tsv.gz?AWSAccessKeyId=AS…&Signature=fYav…6ig%3D&content-type=text%2Ftab-separated-values&x-amz-security-token=IQoJb3JpZ2luX…%3D%3D&Expires=1760115099",
	  "s3_presigned_expiration": "2025-10-10T16:51:39.081266"
  } ],
  "created_at": "2025-10-10T16:41:39.081266",
  "started_at": "2025-10-10T16:41:40.819209",
  "completed_at": "2025-10-10T16:42:13.101926",
  "error_message": null
}

Validation details

In case your files’ status are failed or invalid, you can get a collection of ingestion errors via the GET https://api.getapipinwheel.com/gateway/v1/transactions_batch/files?batch_id={batch_id}&include=errors endpoint.

> … send … >
GET /gateway/v1/transactions_batch/files?batch_id=0199c9e2-a9bf-7c9e-b426-c60a8b4b09ed&include=errors 
Host: api.getpinwheel.com
Content-Type: application/json
Pinwheel-Version: 2025-07-08
x-api-secret: YOUR-API-SECRET
< … example response … <
[{
  "file_number": 1,
  "errors": []
}, {
  "file_number": 2,
  "errors": [
	  {"row": 242, "error": "Missing an end_user_id"},
	  {"row": 837, "error": "Missing a description"},
	  {"row": 837, "error": "Truncated enriched_trasaction_details"}
  ]
}]