{"openapi":"3.1.0","info":{"title":"Odit API","version":"1","description":"Read-only access to your own Odit data. Each call reads one phone: the one the app has loaded, or the one named by `device`. POST /v1/parse reads SMS text you send and saves nothing to your account. v1 only ever gains fields; anything else is a new version."},"servers":[{"url":"https://api.odit.et"}],"components":{"securitySchemes":{"apiKey":{"type":"http","scheme":"bearer","description":"An API key from the account page on odit.et: `Authorization: Bearer odit_sk_...`."}},"schemas":{"Money":{"type":"object","properties":{"amount":{"type":"string","description":"A decimal string, as written by the bank: \"1250.00\". Never a float. Transaction amounts and fees are positive (`direction` carries the sign); a balance or a net can be negative."},"currency":{"type":"string","description":"ISO 4217 code, mostly ETB."}},"required":["amount","currency"]},"Device":{"type":"object","properties":{"id":{"type":"string","description":"The device id: pass it as `device` to read this phone."},"name":{"anyOf":[{"type":"string"},{"type":"null"}]},"loaded":{"type":"boolean","description":"The device the app has loaded; calls without `device` read this one."},"createdAt":{"type":"string","description":"ISO 8601, UTC."}},"required":["id","name","loaded","createdAt"]},"Transaction":{"type":"object","properties":{"id":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"device":{"type":"string","description":"The id of the device this SMS came in on."},"occurredAt":{"type":"string","description":"When the bank sent the message. ISO 8601, UTC."},"receivedAt":{"type":"string","description":"When Odit stored it. ISO 8601, UTC."},"direction":{"type":"string","enum":["incoming","outgoing"]},"type":{"type":"string","description":"The kind of message the parser read, e.g. TRANSFER_OUT. A label, not a fixed list."},"amount":{"$ref":"#/components/schemas/Money"},"fee":{"anyOf":[{"$ref":"#/components/schemas/Money"},{"type":"null"}]},"balanceAfter":{"anyOf":[{"$ref":"#/components/schemas/Money"},{"type":"null"}],"description":"The balance the message states, if any."},"provider":{"anyOf":[{"type":"object","properties":{"key":{"type":"string"},"name":{"type":"string"}},"required":["key","name"]},{"type":"null"}],"description":"The bank or wallet that sent the message."},"account":{"anyOf":[{"type":"object","properties":{"id":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"name":{"type":"string"}},"required":["id","name"]},{"type":"null"}],"description":"The user's own account the money moved through, when Odit linked one."},"counterparty":{"anyOf":[{"type":"object","properties":{"name":{"anyOf":[{"type":"string"},{"type":"null"}]},"identifier":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Phone or account number, unmasked."},"type":{"anyOf":[{"type":"string"},{"type":"null"}]}},"required":["name","identifier","type"]},{"type":"null"}],"description":"The other side, when the message names one."},"category":{"anyOf":[{"type":"object","properties":{"id":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"name":{"type":"string"}},"required":["id","name"]},{"type":"null"}]},"isInternalTransfer":{"type":"boolean","description":"Money moving between two of the user's own accounts."},"isDebt":{"type":"boolean"},"note":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The user's own note."},"reference":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The bank's transaction reference."},"source":{"type":"string","enum":["sms","email"]},"updatedAt":{"type":"string","description":"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."},"rawSms":{"description":"The SMS text. Only with `include=rawSms`, and never for email.","type":"string"}},"required":["id","device","occurredAt","receivedAt","direction","type","amount","fee","balanceAfter","provider","account","counterparty","category","isInternalTransfer","isDebt","note","reference","source","updatedAt"]},"Error":{"type":"object","properties":{"success":{"type":"boolean","const":false},"error":{"type":"object","properties":{"code":{"type":"string","description":"Stable and machine-readable: branch on this."},"message":{"type":"string","description":"For a person to read. May change."}},"required":["code","message"]}},"required":["success","error"]},"Account":{"type":"object","properties":{"id":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"The account's id. A transaction's `account.id` is one of these."},"name":{"type":"string","description":"The bank or wallet, with your own label: \"telebirr (Main)\"."},"provider":{"type":"object","properties":{"key":{"type":"string"},"name":{"type":"string"}},"required":["key","name"]},"label":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The label you gave it in the app."},"visibility":{"type":"string","enum":["visible","soft_hidden"],"description":"`soft_hidden` is cold storage: real money the app leaves out of everyday totals. Accounts you hid outright are not listed."},"balance":{"anyOf":[{"$ref":"#/components/schemas/Money"},{"type":"null"}],"description":"The latest balance the account's own SMS stated, carried forward through later transactions. Null until one is seen."},"balanceAsOf":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"When that balance was last stated. ISO 8601, UTC."},"isDormant":{"type":"boolean","description":"Marked dormant in the app: its balance is not counted in totals."},"isPrimary":{"type":"boolean"}},"required":["id","name","provider","label","visibility","balance","balanceAsOf","isDormant","isPrimary"]},"BalanceReading":{"type":"object","properties":{"at":{"type":"string","description":"When the SMS stating it was sent. ISO 8601, UTC."},"balance":{"$ref":"#/components/schemas/Money"},"transactionId":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"The transaction whose SMS stated this balance."}},"required":["at","balance","transactionId"]},"Person":{"type":"object","properties":{"id":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"name":{"type":"string"},"sent":{"description":"All time, as the People screen counts it.","$ref":"#/components/schemas/Money"},"received":{"description":"All time, as the People screen counts it.","$ref":"#/components/schemas/Money"},"transactionCount":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"isFavorite":{"type":"boolean"},"tags":{"type":"array","items":{"type":"string"}}},"required":["id","name","sent","received","transactionCount","isFavorite","tags"]},"Category":{"type":"object","properties":{"id":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"A transaction's `category.id` is one of these."},"name":{"type":"string"},"parentId":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"null"}],"description":"Set on a subcategory."},"type":{"type":"string","enum":["IN","OUT","BOTH","NEUTRAL"],"description":"What money it's for. A subcategory takes its parent's type."},"custom":{"type":"boolean","description":"Made by you, rather than one of the built-in categories."},"hidden":{"type":"boolean","description":"A built-in category you hid from your picker. Transactions already in it keep it."},"color":{"type":"string"},"icon":{"anyOf":[{"type":"string"},{"type":"null"}]}},"required":["id","name","parentId","type","custom","hidden","color","icon"]},"Goal":{"type":"object","properties":{"id":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"name":{"type":"string"},"type":{"type":"string","enum":["savings","spending_limit","income_target","balance_target"]},"timeframe":{"type":"string","enum":["one_time","daily","weekly","monthly","quarterly","yearly","open_ended"]},"status":{"type":"string","enum":["active","paused","archived"]},"target":{"$ref":"#/components/schemas/Money"},"current":{"description":"Where the goal stands now, as the Goals screen computes it.","$ref":"#/components/schemas/Money"},"progress":{"type":"number","description":"Percent of the target, 0 to 100, as the Goals screen shows it."},"startsAt":{"type":"string","description":"ISO 8601, UTC."},"endsAt":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"ISO 8601, UTC. Null when open ended."},"isAchieved":{"type":"boolean"},"achievedAt":{"anyOf":[{"type":"string"},{"type":"null"}]}},"required":["id","name","type","timeframe","status","target","current","progress","startsAt","endsAt","isAchieved","achievedAt"]},"Recurring":{"type":"object","properties":{"id":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"name":{"type":"string"},"direction":{"type":"string","enum":["incoming","outgoing"]},"amount":{"$ref":"#/components/schemas/Money"},"frequency":{"type":"string","enum":["daily","weekly","biweekly","monthly","quarterly","yearly"]},"status":{"type":"string","enum":["active","paused","completed"]},"next":{"anyOf":[{"type":"object","properties":{"expectedAt":{"type":"string","description":"ISO 8601, UTC."},"status":{"type":"string","description":"Whether it is still expected, or already late."}},"required":["expectedAt","status"]},{"type":"null"}],"description":"The next payment Odit is waiting for. Null when none is open."},"lastOccurredAt":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"ISO 8601, UTC."}},"required":["id","name","direction","amount","frequency","status","next","lastOccurredAt"]},"Packages":{"type":"object","properties":{"asOf":{"type":"string","description":"ISO 8601, UTC."},"packages":{"type":"array","items":{"type":"object","properties":{"provider":{"type":"string","description":"The telecom: `ethiotelecom` or `safaricom`."},"name":{"type":"string"},"expiresAt":{"type":"string","description":"ISO 8601, UTC."},"addedAt":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"ISO 8601, UTC."},"parts":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string","enum":["data","voice","sms"]},"unit":{"type":"string","enum":["MB","min","sms"]},"total":{"anyOf":[{"type":"number"},{"type":"null"}],"description":"What the package gave. Null when unknown or unlimited."},"left":{"anyOf":[{"type":"number"},{"type":"null"}],"description":"What is left, as of `leftAt`."},"leftAt":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"ISO 8601, UTC."},"unlimited":{"type":"boolean"},"note":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"What limits it: `night`, `bonus`, `apps` or `on-net`."}},"required":["kind","unit","total","left","leftAt","unlimited","note"]}}},"required":["provider","name","expiresAt","addedAt","parts"]},"description":"Active packages, soonest expiry first."},"totals":{"type":"object","properties":{"dataMb":{"type":"number","description":"General data left. App-only and night data are apart."},"restrictedDataMb":{"type":"number"},"unlimitedData":{"type":"boolean"},"voiceMin":{"type":"number","description":"Minutes that call anyone."},"onNetVoiceMin":{"type":"number"},"sms":{"type":"number"}},"required":["dataMb","restrictedDataMb","unlimitedData","voiceMin","onNetVoiceMin","sms"]},"recentlyEnded":{"type":"array","items":{"type":"object","properties":{"provider":{"type":"string"},"name":{"type":"string"},"expiresAt":{"type":"string"},"endedAt":{"type":"string"}},"required":["provider","name","expiresAt","endedAt"]},"description":"Packages that ended in the last week, newest first."}},"required":["asOf","packages","totals","recentlyEnded"]},"Summary":{"type":"object","properties":{"from":{"type":"string","description":"ISO 8601, UTC."},"to":{"type":"string","description":"ISO 8601, UTC."},"moneyIn":{"$ref":"#/components/schemas/Money"},"moneyOut":{"description":"Spending, fees not included.","$ref":"#/components/schemas/Money"},"fees":{"description":"Every fee in the period, internal transfers included.","$ref":"#/components/schemas/Money"},"net":{"description":"Money in minus money out and fees. Can be negative.","$ref":"#/components/schemas/Money"},"transactionCount":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"internalTransfers":{"type":"object","properties":{"in":{"$ref":"#/components/schemas/Money"},"out":{"$ref":"#/components/schemas/Money"}},"required":["in","out"],"description":"Money moved between your own accounts. Not counted above."},"unclassifiedCount":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Rows Odit could not tell the direction of. Not counted above."}},"required":["from","to","moneyIn","moneyOut","fees","net","transactionCount","internalTransfers","unclassifiedCount"]},"ParseRequest":{"type":"object","properties":{"messages":{"minItems":1,"maxItems":50,"type":"array","items":{"type":"object","properties":{"id":{"description":"Your own id for the message. Sent back as is, so you can match results.","type":"string","maxLength":200},"sender":{"type":"string","minLength":1,"maxLength":100,"description":"Who sent the SMS, as the phone shows it: \"127\", \"CBE\", \"DashenBank\"."},"body":{"type":"string","minLength":1,"maxLength":5000,"description":"The SMS text, exactly as it arrived."},"provider":{"description":"A provider key such as `telebirr` or `cbe`. Reads the message as that provider and ignores `sender`. For a sender Odit does not know.","type":"string"}},"required":["sender","body"]}}},"required":["messages"],"description":"Up to 50 messages in one call."},"ParsedMessage":{"type":"object","properties":{"id":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The `id` you sent, or null."},"status":{"type":"string","enum":["parsed","not_recognized","unsupported_sender"],"description":"`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."},"provider":{"anyOf":[{"type":"object","properties":{"key":{"type":"string"},"name":{"type":"string"}},"required":["key","name"]},{"type":"null"}],"description":"The bank or wallet the message is from. Null for an unsupported sender."},"type":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The kind of message, e.g. TRANSFER_TB_FROM_TB. A label, not a fixed list. Null unless parsed."},"direction":{"anyOf":[{"type":"string","enum":["incoming","outgoing"]},{"type":"null"}],"description":"Null when the message moves no money in or out of the account: a reminder, a failed transfer, a package notice."},"amount":{"anyOf":[{"$ref":"#/components/schemas/Money"},{"type":"null"}]},"fee":{"anyOf":[{"$ref":"#/components/schemas/Money"},{"type":"null"}]},"balanceAfter":{"anyOf":[{"$ref":"#/components/schemas/Money"},{"type":"null"}],"description":"The balance the message states, if any."},"participants":{"type":"array","items":{"type":"object","properties":{"role":{"type":"string","enum":["sender","receiver"]},"name":{"anyOf":[{"type":"string"},{"type":"null"}]},"identifier":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Phone or account number as the SMS writes it, often masked."},"type":{"type":"string","description":"The kind of account: TELEBIRR, CBE, MERCHANT, UNKNOWN."}},"required":["role","name","identifier","type"]},"description":"The people, merchants and accounts the message names."},"reference":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The provider's transaction reference."},"receiptUrl":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"A link to the receipt, when the message has one."}},"required":["id","status","provider","type","direction","amount","fee","balanceAfter","participants","reference","receiptUrl"]},"TransactionCreatedEvent":{"type":"object","properties":{"type":{"type":"string","const":"transaction.created"},"timestamp":{"type":"string","description":"When the event happened. ISO 8601, UTC."},"data":{"$ref":"#/components/schemas/Transaction"}},"required":["type","timestamp","data"],"description":"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."},"TransactionUpdatedEvent":{"type":"object","properties":{"type":{"type":"string","const":"transaction.updated"},"timestamp":{"type":"string","description":"When the event happened. ISO 8601, UTC."},"data":{"$ref":"#/components/schemas/Transaction"}},"required":["type","timestamp","data"],"description":"A transaction changed in a way the API shows. Renaming a category, account or person is not a change to its transactions."},"TransactionDeletedEvent":{"type":"object","properties":{"type":{"type":"string","const":"transaction.deleted"},"timestamp":{"type":"string","description":"When the event happened. ISO 8601, UTC."},"data":{"type":"object","properties":{"id":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"device":{"anyOf":[{"type":"string"},{"type":"null"}]}},"required":["id","device"]}},"required":["type","timestamp","data"],"description":"A transaction is gone, or Odit no longer reads it as one."},"TransactionsBackfilledEvent":{"type":"object","properties":{"type":{"type":"string","const":"transactions.backfilled"},"timestamp":{"type":"string","description":"When the event happened. ISO 8601, UTC."},"data":{"type":"object","properties":{"device":{"type":"string"},"count":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"from":{"type":"string","description":"The oldest SMS time among them. ISO 8601, UTC."},"to":{"type":"string","description":"The newest SMS time among them. ISO 8601, UTC."}},"required":["device","count","from","to"]}},"required":["type","timestamp","data"],"description":"More than 200 transactions arrived at once (an old phone, an import). Fetch them from /v1/transactions instead of one event each."},"DeviceMergedEvent":{"type":"object","properties":{"type":{"type":"string","const":"device.merged"},"timestamp":{"type":"string","description":"When the event happened. ISO 8601, UTC."},"data":{"type":"object","properties":{"from":{"type":"string"},"into":{"anyOf":[{"type":"string"},{"type":"null"}]}},"required":["from","into"]}},"required":["type","timestamp","data"],"description":"Two of your phones were merged; `from`'s transactions now belong to `into`. No per-transaction events follow."},"DeviceDeletedEvent":{"type":"object","properties":{"type":{"type":"string","const":"device.deleted"},"timestamp":{"type":"string","description":"When the event happened. ISO 8601, UTC."},"data":{"type":"object","properties":{"device":{"type":"string"}},"required":["device"]}},"required":["type","timestamp","data"],"description":"A phone was removed from your account. No per-transaction events follow."},"WebhookTestEvent":{"type":"object","properties":{"type":{"type":"string","const":"webhook.test"},"timestamp":{"type":"string","description":"When the event happened. ISO 8601, UTC."},"data":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"]}},"required":["type","timestamp","data"],"description":"Sent when you press \"Send test event\" on the account page."}}},"paths":{"/v1/devices":{"get":{"summary":"Your phones, and which one the app has loaded.","security":[{"apiKey":[]}],"parameters":[],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"const":true},"data":{"type":"array","items":{"$ref":"#/components/schemas/Device"}}},"required":["success","data"]},"example":{"success":true,"data":[{"id":"00000000-0000-0000-0000-000000000001","name":"Galaxy A15","loaded":true,"createdAt":"2026-03-05T09:12:44.000Z"}]}}}},"400":{"description":"The request is not valid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No key, or a key that is not valid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The key works but may not do this.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Over 60 requests a minute or 5,000 a day. See Retry-After.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/transactions":{"get":{"summary":"Transactions on one phone, newest first.","security":[{"apiKey":[]}],"parameters":[{"name":"device","in":"query","required":false,"description":"A device id from /v1/devices. Omitted: the device the app has loaded.","schema":{"type":"string"}},{"name":"from","in":"query","required":false,"description":"On or after. A bare date means the start of that day, Addis time.","schema":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}(T\\d{2}:\\d{2}(:\\d{2}(\\.\\d+)?)?(Z|[+-]\\d{2}:\\d{2}))?$"}},{"name":"to","in":"query","required":false,"description":"On or before. A bare date means the end of that day, Addis time.","schema":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}(T\\d{2}:\\d{2}(:\\d{2}(\\.\\d+)?)?(Z|[+-]\\d{2}:\\d{2}))?$"}},{"name":"direction","in":"query","required":false,"schema":{"type":"string","enum":["incoming","outgoing"]}},{"name":"category","in":"query","required":false,"description":"A category id, or `none` for uncategorized.","schema":{"anyOf":[{"type":"string","const":"none"},{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991}]}},{"name":"account","in":"query","required":false,"description":"An account id.","schema":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991}},{"name":"q","in":"query","required":false,"description":"Text matched against the message, the other side and the note.","schema":{"type":"string","minLength":1,"maxLength":200}},{"name":"updatedSince","in":"query","required":false,"description":"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.","schema":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}(T\\d{2}:\\d{2}(:\\d{2}(\\.\\d+)?)?(Z|[+-]\\d{2}:\\d{2}))?$"}},{"name":"limit","in":"query","required":false,"schema":{"default":50,"type":"integer","minimum":1,"maximum":100}},{"name":"cursor","in":"query","required":false,"description":"`nextCursor` from the previous page.","schema":{"type":"string"}},{"name":"include","in":"query","required":false,"description":"`rawSms` adds the SMS text to each row.","schema":{"type":"string","enum":["rawSms"]}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"const":true},"data":{"type":"array","items":{"$ref":"#/components/schemas/Transaction"}},"nextCursor":{"type":["string","null"],"description":"Pass as `cursor` for the next page; null on the last page."}},"required":["success","data","nextCursor"]},"example":{"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"}}}},"400":{"description":"The request is not valid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No key, or a key that is not valid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The key works but may not do this.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Over 60 requests a minute or 5,000 a day. See Retry-After.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/transactions/{id}":{"get":{"summary":"One transaction.","security":[{"apiKey":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}},{"name":"include","in":"query","required":false,"description":"`rawSms` adds the SMS text.","schema":{"type":"string","enum":["rawSms"]}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"const":true},"data":{"$ref":"#/components/schemas/Transaction"}},"required":["success","data"]},"example":{"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"}}}}},"400":{"description":"The request is not valid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No key, or a key that is not valid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The key works but may not do this.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Over 60 requests a minute or 5,000 a day. See Retry-After.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/accounts":{"get":{"summary":"Your accounts on one phone, with the balance each last stated.","security":[{"apiKey":[]}],"parameters":[{"name":"device","in":"query","required":false,"description":"A device id from /v1/devices. Omitted: the device the app has loaded.","schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"const":true},"data":{"type":"array","items":{"$ref":"#/components/schemas/Account"}}},"required":["success","data"]},"example":{"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}]}}}},"400":{"description":"The request is not valid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No key, or a key that is not valid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The key works but may not do this.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Over 60 requests a minute or 5,000 a day. See Retry-After.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/accounts/{id}/balances":{"get":{"summary":"Every balance an account stated, newest first.","security":[{"apiKey":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}},{"name":"from","in":"query","required":false,"description":"On or after. A bare date means that whole day, Addis time.","schema":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}(T\\d{2}:\\d{2}(:\\d{2}(\\.\\d+)?)?(Z|[+-]\\d{2}:\\d{2}))?$"}},{"name":"to","in":"query","required":false,"description":"On or before. A bare date means that whole day, Addis time.","schema":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}(T\\d{2}:\\d{2}(:\\d{2}(\\.\\d+)?)?(Z|[+-]\\d{2}:\\d{2}))?$"}},{"name":"limit","in":"query","required":false,"schema":{"default":100,"type":"integer","minimum":1,"maximum":500}},{"name":"cursor","in":"query","required":false,"description":"`nextCursor` from the previous page.","schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"const":true},"data":{"type":"array","items":{"$ref":"#/components/schemas/BalanceReading"}},"nextCursor":{"type":["string","null"],"description":"Pass as `cursor` for the next page; null on the last page."}},"required":["success","data","nextCursor"]},"example":{"success":true,"data":[{"at":"2026-10-12T08:41:00.000Z","balance":{"amount":"18420.55","currency":"ETB"},"transactionId":48210}],"nextCursor":"WyIyMDI2LTEwLTEyIDA4OjQxOjAwKzAwIiw0ODIxMF0"}}}},"400":{"description":"The request is not valid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No key, or a key that is not valid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The key works but may not do this.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Over 60 requests a minute or 5,000 a day. See Retry-After.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/people":{"get":{"summary":"People and merchants on one phone, most money moved first.","security":[{"apiKey":[]}],"parameters":[{"name":"device","in":"query","required":false,"description":"A device id from /v1/devices. Omitted: the device the app has loaded.","schema":{"type":"string"}},{"name":"q","in":"query","required":false,"description":"Text matched against the name.","schema":{"type":"string","minLength":1,"maxLength":100}},{"name":"limit","in":"query","required":false,"schema":{"default":50,"type":"integer","minimum":1,"maximum":100}},{"name":"cursor","in":"query","required":false,"description":"`nextCursor` from the previous page.","schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"const":true},"data":{"type":"array","items":{"$ref":"#/components/schemas/Person"}},"nextCursor":{"type":["string","null"],"description":"Pass as `cursor` for the next page; null on the last page."}},"required":["success","data","nextCursor"]},"example":{"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"}}}},"400":{"description":"The request is not valid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No key, or a key that is not valid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The key works but may not do this.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Over 60 requests a minute or 5,000 a day. See Retry-After.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/categories":{"get":{"summary":"Your categories: the built-in ones and your own.","security":[{"apiKey":[]}],"parameters":[],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"const":true},"data":{"type":"array","items":{"$ref":"#/components/schemas/Category"}}},"required":["success","data"]},"example":{"success":true,"data":[{"id":7,"name":"Family","parentId":null,"type":"BOTH","custom":false,"hidden":false,"color":"#3B82F6","icon":"users-three"}]}}}},"400":{"description":"The request is not valid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No key, or a key that is not valid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The key works but may not do this.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Over 60 requests a minute or 5,000 a day. See Retry-After.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/goals":{"get":{"summary":"Goals on one phone, with progress as the Goals screen shows it.","security":[{"apiKey":[]}],"parameters":[{"name":"device","in":"query","required":false,"description":"A device id from /v1/devices. Omitted: the device the app has loaded.","schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"const":true},"data":{"type":"array","items":{"$ref":"#/components/schemas/Goal"}}},"required":["success","data"]},"example":{"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}]}}}},"400":{"description":"The request is not valid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No key, or a key that is not valid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The key works but may not do this.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Over 60 requests a minute or 5,000 a day. See Retry-After.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/recurrings":{"get":{"summary":"Recurring payments and income on one phone.","security":[{"apiKey":[]}],"parameters":[{"name":"device","in":"query","required":false,"description":"A device id from /v1/devices. Omitted: the device the app has loaded.","schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"const":true},"data":{"type":"array","items":{"$ref":"#/components/schemas/Recurring"}}},"required":["success","data"]},"example":{"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"}]}}}},"400":{"description":"The request is not valid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No key, or a key that is not valid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The key works but may not do this.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Over 60 requests a minute or 5,000 a day. See Retry-After.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/packages":{"get":{"summary":"Telecom packages on one phone: data, minutes and SMS left.","security":[{"apiKey":[]}],"parameters":[{"name":"device","in":"query","required":false,"description":"A device id from /v1/devices. Omitted: the device the app has loaded.","schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"const":true},"data":{"$ref":"#/components/schemas/Packages"}},"required":["success","data"]},"example":{"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":[]}}}}},"400":{"description":"The request is not valid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No key, or a key that is not valid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The key works but may not do this.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Over 60 requests a minute or 5,000 a day. See Retry-After.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/summary":{"get":{"summary":"Money in, money out, fees and net for a period, counted as the dashboard counts them.","security":[{"apiKey":[]}],"parameters":[{"name":"device","in":"query","required":false,"description":"A device id from /v1/devices. Omitted: the device the app has loaded.","schema":{"type":"string"}},{"name":"from","in":"query","required":false,"description":"On or after. A bare date means the start of that day, Addis time. Omitted: the first of this month.","schema":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}(T\\d{2}:\\d{2}(:\\d{2}(\\.\\d+)?)?(Z|[+-]\\d{2}:\\d{2}))?$"}},{"name":"to","in":"query","required":false,"description":"On or before. A bare date means the end of that day, Addis time. Omitted: now.","schema":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}(T\\d{2}:\\d{2}(:\\d{2}(\\.\\d+)?)?(Z|[+-]\\d{2}:\\d{2}))?$"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"const":true},"data":{"$ref":"#/components/schemas/Summary"}},"required":["success","data"]},"example":{"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}}}}},"400":{"description":"The request is not valid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No key, or a key that is not valid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The key works but may not do this.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Over 60 requests a minute or 5,000 a day. See Retry-After.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/parse":{"post":{"summary":"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.","security":[{"apiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ParseRequest"},"example":{"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"}]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"const":true},"data":{"type":"array","items":{"$ref":"#/components/schemas/ParsedMessage"}}},"required":["success","data"]},"example":{"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"}]}}}},"400":{"description":"The request is not valid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No key, or a key that is not valid.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"The key works but may not do this.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found, or not yours.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Over 60 requests a minute or 5,000 a day. See Retry-After.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}},"webhooks":{"transaction.created":{"post":{"summary":"transaction.created. Headers: webhook-id (the same on every retry), webhook-timestamp, and webhook-signature (v1,<base64 HMAC-SHA256 of \"id.timestamp.body\">, keyed with your endpoint's whsec_ secret, base64-decoded). Any Standard Webhooks library verifies it.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TransactionCreatedEvent"},"example":{"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"}}}}},"responses":{"2XX":{"description":"Received. Anything else is retried for about a day; an endpoint failing for 3 days is turned off."}}}},"transaction.updated":{"post":{"summary":"transaction.updated. Headers: webhook-id (the same on every retry), webhook-timestamp, and webhook-signature (v1,<base64 HMAC-SHA256 of \"id.timestamp.body\">, keyed with your endpoint's whsec_ secret, base64-decoded). Any Standard Webhooks library verifies it.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TransactionUpdatedEvent"},"example":{"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"}}}}},"responses":{"2XX":{"description":"Received. Anything else is retried for about a day; an endpoint failing for 3 days is turned off."}}}},"transaction.deleted":{"post":{"summary":"transaction.deleted. Headers: webhook-id (the same on every retry), webhook-timestamp, and webhook-signature (v1,<base64 HMAC-SHA256 of \"id.timestamp.body\">, keyed with your endpoint's whsec_ secret, base64-decoded). Any Standard Webhooks library verifies it.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TransactionDeletedEvent"},"example":{"type":"transaction.deleted","timestamp":"2026-10-13T11:20:05.000Z","data":{"id":48210,"device":"00000000-0000-0000-0000-000000000001"}}}}},"responses":{"2XX":{"description":"Received. Anything else is retried for about a day; an endpoint failing for 3 days is turned off."}}}},"transactions.backfilled":{"post":{"summary":"transactions.backfilled. Headers: webhook-id (the same on every retry), webhook-timestamp, and webhook-signature (v1,<base64 HMAC-SHA256 of \"id.timestamp.body\">, keyed with your endpoint's whsec_ secret, base64-decoded). Any Standard Webhooks library verifies it.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TransactionsBackfilledEvent"},"example":{"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"}}}}},"responses":{"2XX":{"description":"Received. Anything else is retried for about a day; an endpoint failing for 3 days is turned off."}}}},"device.merged":{"post":{"summary":"device.merged. Headers: webhook-id (the same on every retry), webhook-timestamp, and webhook-signature (v1,<base64 HMAC-SHA256 of \"id.timestamp.body\">, keyed with your endpoint's whsec_ secret, base64-decoded). Any Standard Webhooks library verifies it.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeviceMergedEvent"},"example":{"type":"device.merged","timestamp":"2026-10-14T16:45:12.000Z","data":{"from":"00000000-0000-0000-0000-000000000002","into":"00000000-0000-0000-0000-000000000001"}}}}},"responses":{"2XX":{"description":"Received. Anything else is retried for about a day; an endpoint failing for 3 days is turned off."}}}},"device.deleted":{"post":{"summary":"device.deleted. Headers: webhook-id (the same on every retry), webhook-timestamp, and webhook-signature (v1,<base64 HMAC-SHA256 of \"id.timestamp.body\">, keyed with your endpoint's whsec_ secret, base64-decoded). Any Standard Webhooks library verifies it.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeviceDeletedEvent"},"example":{"type":"device.deleted","timestamp":"2026-10-15T07:30:00.000Z","data":{"device":"00000000-0000-0000-0000-000000000002"}}}}},"responses":{"2XX":{"description":"Received. Anything else is retried for about a day; an endpoint failing for 3 days is turned off."}}}},"webhook.test":{"post":{"summary":"webhook.test. Headers: webhook-id (the same on every retry), webhook-timestamp, and webhook-signature (v1,<base64 HMAC-SHA256 of \"id.timestamp.body\">, keyed with your endpoint's whsec_ secret, base64-decoded). Any Standard Webhooks library verifies it.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookTestEvent"},"example":{"type":"webhook.test","timestamp":"2026-10-12T09:10:00.000Z","data":{"message":"A test from Odit. Your webhook works."}}}}},"responses":{"2XX":{"description":"Received. Anything else is retried for about a day; an endpoint failing for 3 days is turned off."}}}}}}