Use Odit with Claude or ChatGPT

Odit API reference

Read your own Odit data from a script, a spreadsheet or anything that speaks HTTP, or have Odit read SMS text you send. It can't change anything.

The basics

  • Every call goes to https://api.odit.et with an API key: Authorization: Bearer odit_sk_.... Make one under Connected apps on your account page.
  • An answer is { "success": true, "data": ... }. A refusal is { "success": false, "error": { "code", "message" } }; branch on code, show message.
  • Money is { "amount": "1250.00", "currency": "ETB" }. The amount is a decimal string, never a float, so nothing is lost on the way.
  • Times are ISO 8601 in UTC. A bare date in a query, like 2026-10-01, means that whole day in Addis time.
  • Each call reads one phone: the one the app has open, or the one you name with device, an id from /v1/devices. Two phones often hold the same SMS, so nothing adds them together.
  • Long lists come a page at a time. Pass nextCursor back as cursor until it is null.
  • You get 60 requests a minute and 5,000 a day, shared with anything you connected through Claude or ChatGPT. Past that the answer is a 429 with Retry-After.
  • v1 only ever gains fields. The machine-readable version of this page is the OpenAPI document.

Try it: read an SMS

Paste a message the way your phone got it, and Odit answers with what it reads. Nothing is saved to your account. The call underneath is POST /v1/parse.

Sent as 127. Every sender

Goes from this page straight to api.odit.et and is kept nowhere. Make one under Connected apps on your account page.

The same call from a script, with your key in ODIT_KEY

curl https://api.odit.et/v1/parse \ -H "Authorization: Bearer $ODIT_KEY" \ -H "Content-Type: application/json" \ -d '{"messages":[{"sender":"127","body":"Dear Hana\nYou have transferred ETB 250.00 to Abebe Kebede (2519****0000) on 12/10/2026 11:41:00. Your transaction number is CAB0000000. The service fee is ETB 0.87 and 15% VAT on the service fee is ETB 0.13. Your current E-Money Account balance is ETB 1,830.55. To download your payment information please click this link: https://transactioninfo.ethiotelecom.et/receipt/CAB0000000.\n\nThank you for using telebirr\nEthio telecom"}]}'

Odit's answer shows here.

Endpoints

GET /v1/devices

Your phones, and which one the app has loaded.

Answer

{
    "success": true,
    "data": [
        {
            "id": "00000000-0000-0000-0000-000000000001",
            "name": "Galaxy A15",
            "loaded": true,
            "createdAt": "2026-03-05T09:12:44.000Z"
        }
    ]
}

data is a list of Device.

  • idstring

    The device id: pass it as device to read this phone.

  • namestring, or null
  • loadedtrue or false

    The device the app has loaded; calls without device read this one.

  • createdAtstring

    ISO 8601, UTC.

GET /v1/transactions

Transactions on one phone, newest first.

Parameters

  • devicestring, optional

    A device id from /v1/devices. Omitted: the device the app has loaded.

  • fromstring, optional

    On or after. A bare date means the start of that day, Addis time.

  • tostring, optional

    On or before. A bare date means the end of that day, Addis time.

  • direction"incoming" or "outgoing", optional
  • category"none" or integer, optional

    A category id, or none for uncategorized.

  • accountinteger, optional

    An account id.

  • qstring, optional

    Text matched against the message, the other side and the note.

  • updatedSincestring, optional

    Only transactions that changed at or after this time, oldest change first: keep the last updatedAt you saw and pass it back to poll for changes. Deletions are not listed; webhooks send those.

  • limitinteger, optional, default 50
  • cursorstring, optional

    nextCursor from the previous page.

  • include"rawSms", optional

    rawSms adds the SMS text to each row.

Answer

{
    "success": true,
    "data": [
        {
            "id": 48210,
            "device": "00000000-0000-0000-0000-000000000001",
            "occurredAt": "2026-10-12T08:41:00.000Z",
            "receivedAt": "2026-10-12T08:41:06.000Z",
            "direction": "outgoing",
            "type": "TRANSFER_CBE_TO_TB",
            "amount": { "amount": "1250.00", "currency": "ETB" },
            "fee": { "amount": "5.00", "currency": "ETB" },
            "balanceAfter": { "amount": "18420.55", "currency": "ETB" },
            "provider": { "key": "cbe", "name": "CBE" },
            "account": { "id": 312, "name": "CBE (Salary)" },
            "counterparty": {
                "name": "Abebe Kebede",
                "identifier": "251911000000",
                "type": "TELEBIRR"
            },
            "category": { "id": 7, "name": "Family" },
            "isInternalTransfer": false,
            "isDebt": false,
            "note": "For the house",
            "reference": "FT0000000000",
            "source": "sms",
            "updatedAt": "2026-10-12T09:03:18.000Z"
        }
    ],
    "nextCursor": "WyIyMDI2LTEwLTEyIDA4OjQxOjAwKzAwIiw0ODIxMF0"
}

data is a list of Transaction, with nextCursor for the next page.

  • idinteger
  • devicestring

    The id of the device this SMS came in on.

  • occurredAtstring

    When the bank sent the message. ISO 8601, UTC.

  • receivedAtstring

    When Odit stored it. ISO 8601, UTC.

  • direction"incoming" or "outgoing"
  • typestring

    The kind of message the parser read, e.g. TRANSFER_OUT. A label, not a fixed list.

  • amountmoney
  • feemoney, or null
  • balanceAftermoney, or null

    The balance the message states, if any.

  • providerobject, or null

    The bank or wallet that sent the message.

    • keystring
    • namestring
  • accountobject, or null

    The user's own account the money moved through, when Odit linked one.

    • idinteger
    • namestring
  • counterpartyobject, or null

    The other side, when the message names one.

    • namestring, or null
    • identifierstring, or null

      Phone or account number, unmasked.

    • typestring, or null
  • categoryobject, or null
    • idinteger
    • namestring
  • isInternalTransfertrue or false

    Money moving between two of the user's own accounts.

  • isDebttrue or false
  • notestring, or null

    The user's own note.

  • referencestring, or null

    The bank's transaction reference.

  • source"sms" or "email"
  • updatedAtstring

    When this transaction last changed in a way the API shows: a new category, a note, a reparse that read it differently. ISO 8601, UTC. Before any change, when Odit stored it.

  • rawSmsstring, sometimes left out

    The SMS text. Only with include=rawSms, and never for email.

GET /v1/transactions/{id}

One transaction.

Parameters

  • idin the path
  • include"rawSms", optional

    rawSms adds the SMS text.

Answer

{
    "success": true,
    "data": {
        "id": 48210,
        "device": "00000000-0000-0000-0000-000000000001",
        "occurredAt": "2026-10-12T08:41:00.000Z",
        "receivedAt": "2026-10-12T08:41:06.000Z",
        "direction": "outgoing",
        "type": "TRANSFER_CBE_TO_TB",
        "amount": { "amount": "1250.00", "currency": "ETB" },
        "fee": { "amount": "5.00", "currency": "ETB" },
        "balanceAfter": { "amount": "18420.55", "currency": "ETB" },
        "provider": { "key": "cbe", "name": "CBE" },
        "account": { "id": 312, "name": "CBE (Salary)" },
        "counterparty": {
            "name": "Abebe Kebede",
            "identifier": "251911000000",
            "type": "TELEBIRR"
        },
        "category": { "id": 7, "name": "Family" },
        "isInternalTransfer": false,
        "isDebt": false,
        "note": "For the house",
        "reference": "FT0000000000",
        "source": "sms",
        "updatedAt": "2026-10-12T09:03:18.000Z"
    }
}

data is a Transaction.

  • idinteger
  • devicestring

    The id of the device this SMS came in on.

  • occurredAtstring

    When the bank sent the message. ISO 8601, UTC.

  • receivedAtstring

    When Odit stored it. ISO 8601, UTC.

  • direction"incoming" or "outgoing"
  • typestring

    The kind of message the parser read, e.g. TRANSFER_OUT. A label, not a fixed list.

  • amountmoney
  • feemoney, or null
  • balanceAftermoney, or null

    The balance the message states, if any.

  • providerobject, or null

    The bank or wallet that sent the message.

    • keystring
    • namestring
  • accountobject, or null

    The user's own account the money moved through, when Odit linked one.

    • idinteger
    • namestring
  • counterpartyobject, or null

    The other side, when the message names one.

    • namestring, or null
    • identifierstring, or null

      Phone or account number, unmasked.

    • typestring, or null
  • categoryobject, or null
    • idinteger
    • namestring
  • isInternalTransfertrue or false

    Money moving between two of the user's own accounts.

  • isDebttrue or false
  • notestring, or null

    The user's own note.

  • referencestring, or null

    The bank's transaction reference.

  • source"sms" or "email"
  • updatedAtstring

    When this transaction last changed in a way the API shows: a new category, a note, a reparse that read it differently. ISO 8601, UTC. Before any change, when Odit stored it.

  • rawSmsstring, sometimes left out

    The SMS text. Only with include=rawSms, and never for email.

GET /v1/accounts

Your accounts on one phone, with the balance each last stated.

Parameters

  • devicestring, optional

    A device id from /v1/devices. Omitted: the device the app has loaded.

Answer

{
    "success": true,
    "data": [
        {
            "id": 312,
            "name": "CBE (Salary)",
            "provider": { "key": "cbe", "name": "CBE" },
            "label": "Salary",
            "visibility": "visible",
            "balance": { "amount": "18420.55", "currency": "ETB" },
            "balanceAsOf": "2026-10-12T08:41:00.000Z",
            "isDormant": false,
            "isPrimary": true
        }
    ]
}

data is a list of Account.

  • idinteger

    The account's id. A transaction's account.id is one of these.

  • namestring

    The bank or wallet, with your own label: "telebirr (Main)".

  • providerobject
    • keystring
    • namestring
  • labelstring, or null

    The label you gave it in the app.

  • visibility"visible" or "soft_hidden"

    soft_hidden is cold storage: real money the app leaves out of everyday totals. Accounts you hid outright are not listed.

  • balancemoney, or null

    The latest balance the account's own SMS stated, carried forward through later transactions. Null until one is seen.

  • balanceAsOfstring, or null

    When that balance was last stated. ISO 8601, UTC.

  • isDormanttrue or false

    Marked dormant in the app: its balance is not counted in totals.

  • isPrimarytrue or false

GET /v1/accounts/{id}/balances

Every balance an account stated, newest first.

Parameters

  • idin the path
  • fromstring, optional

    On or after. A bare date means that whole day, Addis time.

  • tostring, optional

    On or before. A bare date means that whole day, Addis time.

  • limitinteger, optional, default 100
  • cursorstring, optional

    nextCursor from the previous page.

Answer

{
    "success": true,
    "data": [
        {
            "at": "2026-10-12T08:41:00.000Z",
            "balance": { "amount": "18420.55", "currency": "ETB" },
            "transactionId": 48210
        }
    ],
    "nextCursor": "WyIyMDI2LTEwLTEyIDA4OjQxOjAwKzAwIiw0ODIxMF0"
}

data is a list of BalanceReading, with nextCursor for the next page.

  • atstring

    When the SMS stating it was sent. ISO 8601, UTC.

  • balancemoney
  • transactionIdinteger

    The transaction whose SMS stated this balance.

GET /v1/people

People and merchants on one phone, most money moved first.

Parameters

  • devicestring, optional

    A device id from /v1/devices. Omitted: the device the app has loaded.

  • qstring, optional

    Text matched against the name.

  • limitinteger, optional, default 50
  • cursorstring, optional

    nextCursor from the previous page.

Answer

{
    "success": true,
    "data": [
        {
            "id": 905,
            "name": "Abebe Kebede",
            "sent": { "amount": "23500.00", "currency": "ETB" },
            "received": { "amount": "4000.00", "currency": "ETB" },
            "transactionCount": 18,
            "isFavorite": true,
            "tags": [ "family" ]
        }
    ],
    "nextCursor": "WyIyMDI2LTEwLTEyIDA4OjQxOjAwKzAwIiw0ODIxMF0"
}

data is a list of Person, with nextCursor for the next page.

  • idinteger
  • namestring
  • sentmoney

    All time, as the People screen counts it.

  • receivedmoney

    All time, as the People screen counts it.

  • transactionCountinteger
  • isFavoritetrue or false
  • tagslist of string

GET /v1/categories

Your categories: the built-in ones and your own.

Answer

{
    "success": true,
    "data": [
        {
            "id": 7,
            "name": "Family",
            "parentId": null,
            "type": "BOTH",
            "custom": false,
            "hidden": false,
            "color": "#3B82F6",
            "icon": "users-three"
        }
    ]
}

data is a list of Category.

  • idinteger

    A transaction's category.id is one of these.

  • namestring
  • parentIdinteger, or null

    Set on a subcategory.

  • type"IN" or "OUT" or "BOTH" or "NEUTRAL"

    What money it's for. A subcategory takes its parent's type.

  • customtrue or false

    Made by you, rather than one of the built-in categories.

  • hiddentrue or false

    A built-in category you hid from your picker. Transactions already in it keep it.

  • colorstring
  • iconstring, or null

GET /v1/goals

Goals on one phone, with progress as the Goals screen shows it.

Parameters

  • devicestring, optional

    A device id from /v1/devices. Omitted: the device the app has loaded.

Answer

{
    "success": true,
    "data": [
        {
            "id": 41,
            "name": "Emergency fund",
            "type": "savings",
            "timeframe": "open_ended",
            "status": "active",
            "target": { "amount": "50000.00", "currency": "ETB" },
            "current": { "amount": "18420.55", "currency": "ETB" },
            "progress": 36.8,
            "startsAt": "2026-07-01T00:00:00.000Z",
            "endsAt": null,
            "isAchieved": false,
            "achievedAt": null
        }
    ]
}

data is a list of Goal.

  • idinteger
  • namestring
  • type"savings" or "spending_limit" or "income_target" or "balance_target"
  • timeframe"one_time" or "daily" or "weekly" or "monthly" or "quarterly" or "yearly" or "open_ended"
  • status"active" or "paused" or "archived"
  • targetmoney
  • currentmoney

    Where the goal stands now, as the Goals screen computes it.

  • progressnumber

    Percent of the target, 0 to 100, as the Goals screen shows it.

  • startsAtstring

    ISO 8601, UTC.

  • endsAtstring, or null

    ISO 8601, UTC. Null when open ended.

  • isAchievedtrue or false
  • achievedAtstring, or null

GET /v1/recurrings

Recurring payments and income on one phone.

Parameters

  • devicestring, optional

    A device id from /v1/devices. Omitted: the device the app has loaded.

Answer

{
    "success": true,
    "data": [
        {
            "id": 12,
            "name": "Rent",
            "direction": "outgoing",
            "amount": { "amount": "15000.00", "currency": "ETB" },
            "frequency": "monthly",
            "status": "active",
            "next": {
                "expectedAt": "2026-10-31T21:00:00.000Z",
                "status": "upcoming"
            },
            "lastOccurredAt": "2026-10-01T06:15:00.000Z"
        }
    ]
}

data is a list of Recurring.

  • idinteger
  • namestring
  • direction"incoming" or "outgoing"
  • amountmoney
  • frequency"daily" or "weekly" or "biweekly" or "monthly" or "quarterly" or "yearly"
  • status"active" or "paused" or "completed"
  • nextobject, or null

    The next payment Odit is waiting for. Null when none is open.

    • expectedAtstring

      ISO 8601, UTC.

    • statusstring

      Whether it is still expected, or already late.

  • lastOccurredAtstring, or null

    ISO 8601, UTC.

GET /v1/packages

Telecom packages on one phone: data, minutes and SMS left.

Parameters

  • devicestring, optional

    A device id from /v1/devices. Omitted: the device the app has loaded.

Answer

{
    "success": true,
    "data": {
        "asOf": "2026-10-12T09:00:00.000Z",
        "packages": [
            {
                "provider": "ethiotelecom",
                "name": "Monthly 12GB + 100 min",
                "expiresAt": "2026-11-03T20:59:59.000Z",
                "addedAt": "2026-10-04T07:20:00.000Z",
                "parts": [
                    {
                        "kind": "data",
                        "unit": "MB",
                        "total": 12288,
                        "left": 7340.5,
                        "leftAt": "2026-10-12T08:55:00.000Z",
                        "unlimited": false,
                        "note": null
                    },
                    {
                        "kind": "voice",
                        "unit": "min",
                        "total": 100,
                        "left": 64,
                        "leftAt": "2026-10-12T08:55:00.000Z",
                        "unlimited": false,
                        "note": null
                    }
                ]
            }
        ],
        "totals": {
            "dataMb": 7340.5,
            "restrictedDataMb": 0,
            "unlimitedData": false,
            "voiceMin": 64,
            "onNetVoiceMin": 0,
            "sms": 0
        },
        "recentlyEnded": []
    }
}

data is a Packages.

  • asOfstring

    ISO 8601, UTC.

  • packageslist of object

    Active packages, soonest expiry first.

    • providerstring

      The telecom: ethiotelecom or safaricom.

    • namestring
    • expiresAtstring

      ISO 8601, UTC.

    • addedAtstring, or null

      ISO 8601, UTC.

    • partslist of object
      • kind"data" or "voice" or "sms"
      • unit"MB" or "min" or "sms"
      • totalnumber, or null

        What the package gave. Null when unknown or unlimited.

      • leftnumber, or null

        What is left, as of leftAt.

      • leftAtstring, or null

        ISO 8601, UTC.

      • unlimitedtrue or false
      • notestring, or null

        What limits it: night, bonus, apps or on-net.

  • totalsobject
    • dataMbnumber

      General data left. App-only and night data are apart.

    • restrictedDataMbnumber
    • unlimitedDatatrue or false
    • voiceMinnumber

      Minutes that call anyone.

    • onNetVoiceMinnumber
    • smsnumber
  • recentlyEndedlist of object

    Packages that ended in the last week, newest first.

    • providerstring
    • namestring
    • expiresAtstring
    • endedAtstring

GET /v1/summary

Money in, money out, fees and net for a period, counted as the dashboard counts them.

Parameters

  • devicestring, optional

    A device id from /v1/devices. Omitted: the device the app has loaded.

  • fromstring, optional

    On or after. A bare date means the start of that day, Addis time. Omitted: the first of this month.

  • tostring, optional

    On or before. A bare date means the end of that day, Addis time. Omitted: now.

Answer

{
    "success": true,
    "data": {
        "from": "2026-09-30T21:00:00.000Z",
        "to": "2026-10-31T20:59:59.999Z",
        "moneyIn": { "amount": "42000.00", "currency": "ETB" },
        "moneyOut": { "amount": "31250.40", "currency": "ETB" },
        "fees": { "amount": "118.75", "currency": "ETB" },
        "net": { "amount": "10630.85", "currency": "ETB" },
        "transactionCount": 64,
        "internalTransfers": {
            "in": { "amount": "5000.00", "currency": "ETB" },
            "out": { "amount": "5000.00", "currency": "ETB" }
        },
        "unclassifiedCount": 0
    }
}

data is a Summary.

  • fromstring

    ISO 8601, UTC.

  • tostring

    ISO 8601, UTC.

  • moneyInmoney
  • moneyOutmoney

    Spending, fees not included.

  • feesmoney

    Every fee in the period, internal transfers included.

  • netmoney

    Money in minus money out and fees. Can be negative.

  • transactionCountinteger
  • internalTransfersobject

    Money moved between your own accounts. Not counted above.

    • inmoney
    • outmoney
  • unclassifiedCountinteger

    Rows Odit could not tell the direction of. Not counted above.

POST /v1/parse

Read SMS text you send, the way Odit reads a synced message. Nothing is saved to your account. One answer per message, in the order sent. Try it with your own key. Which sender is which provider is under Senders.

Body

{
    "messages": [
        {
            "id": "my-sms-1",
            "sender": "127",
            "body": "Dear Hana\nYou have transferred ETB 250.00 to Abebe Kebede (2519****0000) on 12/10/2026 11:41:00. Your transaction number is CAB0000000. The service fee is  ETB 0.87 and  15% VAT on the service fee is ETB 0.13. Your current E-Money Account  balance is ETB 1,830.55. To download your payment information please click this link: https://transactioninfo.ethiotelecom.et/receipt/CAB0000000.\n\nThank you for using telebirr\nEthio telecom"
        }
    ]
}

JSON, with Content-Type: application/json.

  • messageslist of object
    • idstring, optional

      Your own id for the message. Sent back as is, so you can match results.

    • senderstring

      Who sent the SMS, as the phone shows it: "127", "CBE", "DashenBank".

    • bodystring

      The SMS text, exactly as it arrived.

    • providerstring, optional

      A provider key such as telebirr or cbe. Reads the message as that provider and ignores sender. For a sender Odit does not know.

Answer

{
    "success": true,
    "data": [
        {
            "id": "my-sms-1",
            "status": "parsed",
            "provider": { "key": "telebirr", "name": "Telebirr" },
            "type": "TRANSFER_TB_FROM_TB",
            "direction": "outgoing",
            "amount": { "amount": "250.00", "currency": "ETB" },
            "fee": { "amount": "1.00", "currency": "ETB" },
            "balanceAfter": { "amount": "1830.55", "currency": "ETB" },
            "participants": [
                {
                    "role": "sender",
                    "name": "Hana",
                    "identifier": null,
                    "type": "TELEBIRR"
                },
                {
                    "role": "receiver",
                    "name": "Abebe Kebede",
                    "identifier": "2519****0000",
                    "type": "TELEBIRR"
                }
            ],
            "reference": "CAB0000000",
            "receiptUrl": "https://transactioninfo.ethiotelecom.et/receipt/CAB0000000"
        }
    ]
}

data is a list of ParsedMessage.

  • idstring, or null

    The id you sent, or null.

  • status"parsed" or "not_recognized" or "unsupported_sender"

    parsed: Odit read the message. not_recognized: the sender is a bank or wallet Odit reads, but not this kind of message. unsupported_sender: Odit does not read this sender.

  • providerobject, or null

    The bank or wallet the message is from. Null for an unsupported sender.

    • keystring
    • namestring
  • typestring, or null

    The kind of message, e.g. TRANSFER_TB_FROM_TB. A label, not a fixed list. Null unless parsed.

  • direction"incoming" or "outgoing", or null

    Null when the message moves no money in or out of the account: a reminder, a failed transfer, a package notice.

  • amountmoney, or null
  • feemoney, or null
  • balanceAftermoney, or null

    The balance the message states, if any.

  • participantslist of object

    The people, merchants and accounts the message names.

    • role"sender" or "receiver"
    • namestring, or null
    • identifierstring, or null

      Phone or account number as the SMS writes it, often masked.

    • typestring

      The kind of account: TELEBIRR, CBE, MERCHANT, UNKNOWN.

  • referencestring, or null

    The provider's transaction reference.

  • receiptUrlstring, or null

    A link to the receipt, when the message has one.

Webhooks

Add up to 5 HTTPS addresses under Webhooks on your account page, and Odit POSTs each new or changed transaction to them, about 30 seconds after it lands. A webhook never carries the SMS text.

Requests are signed per Standard Webhooks, with the whsec_ secret you got when you added the address. Check the signature before you trust a body:

import { Webhook } from 'standardwebhooks';

const wh = new Webhook(process.env.ODIT_WEBHOOK_SECRET); // whsec_...

// The body exactly as it arrived, before any JSON parsing.
const event = wh.verify(rawBody, {
  'webhook-id': req.headers['webhook-id'],
  'webhook-timestamp': req.headers['webhook-timestamp'],
  'webhook-signature': req.headers['webhook-signature'],
});

Answer with any 2xx. Anything else is tried again after 5 seconds, 5 minutes, 30 minutes, 2, 5, 10 and 10 hours, then dropped. The webhook-id stays the same on every try, so you can skip one you already handled. An address that has failed for 3 days is turned off and you get an email.

transaction.created

A new transaction, about 30 seconds after it lands. Never carries rawSms. Not sent for the SMS a phone already had when it first synced, or when your account joined the API.

Body

{
    "type": "transaction.created",
    "timestamp": "2026-10-12T08:41:36.000Z",
    "data": {
        "id": 48210,
        "device": "00000000-0000-0000-0000-000000000001",
        "occurredAt": "2026-10-12T08:41:00.000Z",
        "receivedAt": "2026-10-12T08:41:06.000Z",
        "direction": "outgoing",
        "type": "TRANSFER_CBE_TO_TB",
        "amount": { "amount": "1250.00", "currency": "ETB" },
        "fee": { "amount": "5.00", "currency": "ETB" },
        "balanceAfter": { "amount": "18420.55", "currency": "ETB" },
        "provider": { "key": "cbe", "name": "CBE" },
        "account": { "id": 312, "name": "CBE (Salary)" },
        "counterparty": {
            "name": "Abebe Kebede",
            "identifier": "251911000000",
            "type": "TELEBIRR"
        },
        "category": null,
        "isInternalTransfer": false,
        "isDebt": false,
        "note": null,
        "reference": "FT0000000000",
        "source": "sms",
        "updatedAt": "2026-10-12T08:41:06.000Z"
    }
}

data is a Transaction, the same shape GET /v1/transactions answers with.

transaction.updated

A transaction changed in a way the API shows. Renaming a category, account or person is not a change to its transactions.

Body

{
    "type": "transaction.updated",
    "timestamp": "2026-10-12T09:03:48.000Z",
    "data": {
        "id": 48210,
        "device": "00000000-0000-0000-0000-000000000001",
        "occurredAt": "2026-10-12T08:41:00.000Z",
        "receivedAt": "2026-10-12T08:41:06.000Z",
        "direction": "outgoing",
        "type": "TRANSFER_CBE_TO_TB",
        "amount": { "amount": "1250.00", "currency": "ETB" },
        "fee": { "amount": "5.00", "currency": "ETB" },
        "balanceAfter": { "amount": "18420.55", "currency": "ETB" },
        "provider": { "key": "cbe", "name": "CBE" },
        "account": { "id": 312, "name": "CBE (Salary)" },
        "counterparty": {
            "name": "Abebe Kebede",
            "identifier": "251911000000",
            "type": "TELEBIRR"
        },
        "category": { "id": 7, "name": "Family" },
        "isInternalTransfer": false,
        "isDebt": false,
        "note": "For the house",
        "reference": "FT0000000000",
        "source": "sms",
        "updatedAt": "2026-10-12T09:03:18.000Z"
    }
}

data is a Transaction, the same shape GET /v1/transactions answers with.

transaction.deleted

A transaction is gone, or Odit no longer reads it as one.

Body

{
    "type": "transaction.deleted",
    "timestamp": "2026-10-13T11:20:05.000Z",
    "data": {
        "id": 48210,
        "device": "00000000-0000-0000-0000-000000000001"
    }
}

data has these fields:

  • idinteger
  • devicestring, or null

transactions.backfilled

More than 200 transactions arrived at once (an old phone, an import). Fetch them from /v1/transactions instead of one event each.

Body

{
    "type": "transactions.backfilled",
    "timestamp": "2026-10-12T10:02:41.000Z",
    "data": {
        "device": "00000000-0000-0000-0000-000000000001",
        "count": 1840,
        "from": "2025-04-02T05:10:00.000Z",
        "to": "2026-10-12T08:41:00.000Z"
    }
}

data has these fields:

  • devicestring
  • countinteger
  • fromstring

    The oldest SMS time among them. ISO 8601, UTC.

  • tostring

    The newest SMS time among them. ISO 8601, UTC.

device.merged

Two of your phones were merged; from's transactions now belong to into. No per-transaction events follow.

Body

{
    "type": "device.merged",
    "timestamp": "2026-10-14T16:45:12.000Z",
    "data": {
        "from": "00000000-0000-0000-0000-000000000002",
        "into": "00000000-0000-0000-0000-000000000001"
    }
}

data has these fields:

  • fromstring
  • intostring, or null

device.deleted

A phone was removed from your account. No per-transaction events follow.

Body

{
    "type": "device.deleted",
    "timestamp": "2026-10-15T07:30:00.000Z",
    "data": { "device": "00000000-0000-0000-0000-000000000002" }
}

data has these fields:

  • devicestring

webhook.test

Sent when you press "Send test event" on the account page.

Body

{
    "type": "webhook.test",
    "timestamp": "2026-10-12T09:10:00.000Z",
    "data": { "message": "A test from Odit. Your webhook works." }
}

data has these fields:

  • messagestring

Senders

sender is the name or number your phone shows the SMS as, in any case. To read a message as one provider whatever its sender, send that provider's key as provider instead.

Wallets

  • Telebirrtelebirr
    127Telebirr+251127Unknown127
  • M-PESAmpesa
    MPESA
  • CBE Birrcbebirr
    CBE BirrCBE-BirrCBEBirr
  • Apolloapollo
    apollo
  • COOPay Ebirrcoopay
    COOPayEBIRRCOOPay_E-BiCooPay_Ebir836
  • Kaafi Ebirrkaafi
    EBIRR602
  • HalalPayhalalpay
    HalalPay

Banks

  • CBEcbe
    CBE889
  • BOAboa
    BOA
  • Zemen Bankzemenbank
    Zemen BankZemenBank
  • Awash Bankawashbank
    Awash BankAwashBank
  • Dashen Bankdashenbank
    Dashen BankDashenBank
  • Enat Bankenatbank
    ENAT BANKENATBANK
  • Amhara Bankamharabank
    AMHARA BANKAMHARABANK
  • Nib Banknibbank
    NIB
  • Berhan Bankberhanbank
    Berhan BankBerhanBank
  • Hibret Bankhibretbank
    HibretBankUnited Bank8818
  • Ahadu Bankahadubank
    Ahadu BankAhaduBank
  • Hijra Bankhijrabank
    HIJRA BANKHIJRABANK
  • Coopbankcoopbank
    CBO
  • Oromia Bankoromiabank
    Oromia BankOromiaBankOIB
  • Abay Bankabaybank
    ABAY BANKABAYBANK
  • Wegagen Bankwegagenbank
    WegagenBank
  • ZamZam Bankzamzambank
    ZamZam BankZamZamBank
  • Tsehay Banktsehaybank
    Tsehay BankTsehayBank
  • Bunna Bankbunnabank
    Bunna BankBunnaBank
  • Siinqee Banksiinqeebank
    SiinqeeBank
  • Anbesa Bankanbesabank
    AnbesaBankLIB
  • Tsedey Banktsedeybank
    Tsedey Bank
  • Global Bankglobalbank
    GBEDGB
  • Gadaa Bankgadaabank
    Gadaa Bank
  • Addis Bankaddisbank
    Addis Bank
  • Siket Banksiketbank
    Siket Bank
  • Goh Betoch Bankgohbetochbank
    GOH BANK
  • Rammis Bankrammisbank
    RAMMIS BANK
  • Sidama Banksidamabank
    Sidama BankSidama
  • Omo Bankomobank
    Omo Bank
  • Shabelle Bankshabellebank
    Shabelle Ba

Telecoms

  • Ethio telecomethiotelecom
    994251994+251994+251251994
  • Safaricomsafaricom
    Safaricom