Activity timeline
Every state-changing call in FinzBooks — created, edited, sent, voided, paid, deleted — is written to a single append-only audit log. The activity endpoints expose that stream so partner apps can render the “recent activity” sidebar that finance teams expect on every document.
Activity events are emitted by the internal services themselves, not by the public API layer — so a record booked via the dashboard, via the public API, via a recurring schedule, or via a webhook reconciliation all produce the same uniform event shape with the same action verbs. This means a third-party timeline UI stays accurate even when the user works outside your integration.
Event shape
{
"event_id": "evt_8c3f1a9b40b14e3aaf2d7e91a76c1f02",
"action": "INVOICE_SENT",
"entity_type": "INVOICE",
"entity_id": "inv_abc",
"entity_ref": "INV-2026-0042",
"actor_user_id": "usr_xyz",
"changes": { "status": { "old": "DRAFT", "new": "SENT" } },
"metadata": {
"ip": "203.0.113.4",
"user_agent": "AIBooks-CLI/0.4",
"request_id": "req_7f6b3c8e1d22",
"source": "PUBLIC_API"
},
"occurred_at": "2026-05-11T08:30:12Z"
}The changes blob is present for UPDATED events and omitted for create/delete actions. The metadata blob carries request context — IP, request ID, source app — and is the right place to attribute changes back to your integration when you debug a customer complaint.
Common request headers
Authorization: Bearer aibooks_pat_XXXX...
X-AIBooks-Org: org_8f9a1c2dAction vocabulary
Actions are stable strings of the form {ENTITY}_{VERB}. The most common set:
CREATED— first save of a draft.UPDATED— any field change after create.SENT— email dispatched to a customer or vendor.STATUS_CHANGED— workflow transition (DRAFT → SENT, OPEN → PAID, etc.).VOIDED— soft cancel.DELETED— hard delete (post-trash).PAID— full payment received / made.PARTIALLY_PAID— at least one allocation, balance > 0.COMMENT_ADDED/ATTACHMENT_ADDED— child resources.
/activityList events for the orgAIBooks.activity.READThe general-purpose endpoint. Without filters, returns the most recent 100 events across all resources — useful for “what just happened in this org?” widgets on a partner admin page.
Query parameters
| Param | Notes |
|---|---|
entity_type | Restrict to INVOICE / BILL / ESTIMATE / CREDIT_NOTE / DEBIT_NOTE / SALES_ORDER / PURCHASE_ORDER / PAYMENT / RECEIPT / EXPENSE / JOURNAL / CONTACT / ITEM / ATTACHMENT. Unknown values return 400. |
entity_id | Scope to one specific document. |
actor_user_id | “Show me everything Priya did this week” — audit-style queries. |
since | ISO timestamp lower bound (inclusive). |
until | ISO timestamp upper bound (inclusive). |
limit | 1 – 500. Default 100. |
Sample request
GET /api/public/v1/activity?entity_type=INVOICE&since=2026-05-11T00:00:00Z&limit=50 HTTP/1.1
Host: api.aibooks.in
Authorization: Bearer aibooks_pat_3p9q0v1w2x3y4z5a6b7c8d9e
X-AIBooks-Org: org_8f9a1c2dSample response
HTTP/1.1 200 OK
Content-Type: application/json
{
"code": 0,
"message": "success",
"activity": [
{
"event_id": "evt_4e2a91f06e6c4b4eac9d8d23b6d1c401",
"action": "INVOICE_PAID",
"entity_type": "INVOICE",
"entity_id": "inv_8c3f1a9b",
"entity_ref": "INV-2026-0042",
"actor_user_id": null,
"changes": { "status": { "old": "SENT", "new": "PAID" } },
"metadata": {
"source": "RAZORPAY_WEBHOOK",
"payment_id": "pay_NfX0g3y4z5a6b7",
"request_id": "req_a1b2c3d4e5f6"
},
"occurred_at": "2026-05-12T07:51:33Z"
},
{
"event_id": "evt_3d1a80e05d5b3a3dac8c7c12a5c0b300",
"action": "INVOICE_SENT",
"entity_type": "INVOICE",
"entity_id": "inv_8c3f1a9b",
"entity_ref": "INV-2026-0042",
"actor_user_id": "usr_xyz",
"changes": { "status": { "old": "DRAFT", "new": "SENT" } },
"metadata": {
"source": "PUBLIC_API",
"ip": "203.0.113.4",
"user_agent": "AIBooks-CLI/0.4",
"request_id": "req_7f6b3c8e1d22"
},
"occurred_at": "2026-05-11T08:30:12Z"
},
{
"event_id": "evt_2c098dd04c4a292c9b7b6b01948b020f",
"action": "INVOICE_CREATED",
"entity_type": "INVOICE",
"entity_id": "inv_8c3f1a9b",
"entity_ref": "INV-2026-0042",
"actor_user_id": "usr_xyz",
"changes": null,
"metadata": { "source": "DASHBOARD", "ip": "203.0.113.4" },
"occurred_at": "2026-05-11T08:14:02Z"
}
],
"page_context": { "limit": 50, "returned": 3, "has_more_page": false }
}Newest events come first. Use since + until to page backward through history — there is no cursor on this endpoint because the stream is append-only and timestamp-ordered.
Per-resource convenience aliases
These wrap GET /activity?entity_type=…&entity_id=… so you can wire the timeline panel on each document detail screen with a single, predictable URL — no need to synthesise the query string.
/invoices/{invoice_id}/activityInvoice timelineAIBooks.activity.READSample request
GET /api/public/v1/invoices/inv_8c3f1a9b/activity?limit=20 HTTP/1.1
Host: api.aibooks.in
Authorization: Bearer aibooks_pat_3p9q0v1w2x3y4z5a6b7c8d9e
X-AIBooks-Org: org_8f9a1c2dSample response
HTTP/1.1 200 OK
Content-Type: application/json
{
"code": 0,
"message": "success",
"activity": [
{
"event_id": "evt_4e2a91f06e6c4b4eac9d8d23b6d1c401",
"action": "INVOICE_PAID",
"entity_type": "INVOICE",
"entity_id": "inv_8c3f1a9b",
"entity_ref": "INV-2026-0042",
"actor_user_id": null,
"changes": { "status": { "old": "SENT", "new": "PAID" } },
"metadata": { "source": "RAZORPAY_WEBHOOK", "payment_id": "pay_NfX0g3y4z5a6b7" },
"occurred_at": "2026-05-12T07:51:33Z"
},
{
"event_id": "evt_3d1a80e05d5b3a3dac8c7c12a5c0b300",
"action": "EMAIL_SENT",
"entity_type": "INVOICE",
"entity_id": "inv_8c3f1a9b",
"entity_ref": "INV-2026-0042",
"actor_user_id": "usr_xyz",
"changes": null,
"metadata": { "to": ["priya@acme.in"], "ses_message_id": "0102018a-4d1f-..." },
"occurred_at": "2026-05-11T08:32:14Z"
}
],
"page_context": { "limit": 20, "returned": 2, "has_more_page": false }
}/bills/{bill_id}/activityBill timelineAIBooks.activity.READSample request
GET /api/public/v1/bills/bill_2d4e6f8a/activity HTTP/1.1
Host: api.aibooks.in
Authorization: Bearer aibooks_pat_3p9q0v1w2x3y4z5a6b7c8d9e
X-AIBooks-Org: org_8f9a1c2dSample response
HTTP/1.1 200 OK
Content-Type: application/json
{
"code": 0,
"message": "success",
"activity": [
{
"event_id": "evt_9a87b6c5d4e3f2a1b0c9d8e7f6a5b4c3",
"action": "BILL_APPROVED",
"entity_type": "BILL",
"entity_id": "bill_2d4e6f8a",
"entity_ref": "BILL-2026-0118",
"actor_user_id": "usr_approver_01",
"changes": { "status": { "old": "PENDING_APPROVAL", "new": "APPROVED" } },
"metadata": { "source": "DASHBOARD", "approval_step": 2 },
"occurred_at": "2026-05-12T06:14:00Z"
}
],
"page_context": { "limit": 100, "returned": 1, "has_more_page": false }
}/estimates/{estimate_id}/activityEstimate timelineAIBooks.activity.READ/credit_notes/{credit_note_id}/activityCredit note timelineAIBooks.activity.READ/debit_notes/{debit_note_id}/activityDebit note timelineAIBooks.activity.READ/payments/{payment_id}/activityPayment timelineAIBooks.activity.READ/expenses/{expense_id}/activityExpense timelineAIBooks.activity.READEach takes a single ?limit= query param (default 100, max 500). The envelope shape is identical to GET /activity — same fields, same ordering, same pagination context.
Sample request — estimate timeline
GET /api/public/v1/estimates/est_9b1c3d5e/activity?limit=10 HTTP/1.1
Host: api.aibooks.in
Authorization: Bearer aibooks_pat_3p9q0v1w2x3y4z5a6b7c8d9e
X-AIBooks-Org: org_8f9a1c2dSample response — estimate timeline
HTTP/1.1 200 OK
Content-Type: application/json
{
"code": 0,
"message": "success",
"activity": [
{
"event_id": "evt_e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6",
"action": "ESTIMATE_ACCEPTED",
"entity_type": "ESTIMATE",
"entity_id": "est_9b1c3d5e",
"entity_ref": "EST-2026-0073",
"actor_user_id": null,
"changes": { "status": { "old": "SENT", "new": "ACCEPTED" } },
"metadata": { "source": "CUSTOMER_PORTAL", "accepted_by": "rohit@contoso.in" },
"occurred_at": "2026-05-09T11:02:11Z"
},
{
"event_id": "evt_d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5",
"action": "ESTIMATE_SENT",
"entity_type": "ESTIMATE",
"entity_id": "est_9b1c3d5e",
"entity_ref": "EST-2026-0073",
"actor_user_id": "usr_xyz",
"changes": { "status": { "old": "DRAFT", "new": "SENT" } },
"metadata": { "source": "PUBLIC_API", "to": ["rohit@contoso.in"] },
"occurred_at": "2026-05-08T14:30:45Z"
}
],
"page_context": { "limit": 10, "returned": 2, "has_more_page": false }
}Errors
HTTP/1.1 400 Bad Request
Content-Type: application/json
{
"code": "validation.invalid_value",
"message": "Unknown entity_type 'FOO'.",
"field": "entity_type",
"details": { "valid": ["INVOICE","BILL","ESTIMATE","CREDIT_NOTE", "..."] }
}| Code | HTTP | When |
|---|---|---|
validation.invalid_value | 400 | Unknown entity_type on the generic endpoint, or unparsable since / until ISO string. |
authn.token_invalid | 401 | PAT expired or revoked. |
authz.scope_missing | 403 | Token lacks AIBooks.activity.READ. |
Operational notes
- Read-only by design. There is no
POST /activity. Events are written by the internal services as a side-effect of state changes — this is what makes the audit trail trustworthy. - Retention.Events are retained indefinitely on the standard plan. There is no automatic rollup or pruning — a row from 2024 is just as queryable as today's.
- Webhooks vs. polling. If you need near-real-time activity in an external system, subscribe to the corresponding webhook events (e.g.
invoice.sent) instead of polling/activity. The activity endpoint is for rendering, not for triggering integrations. - Comments and attachments. Adding a comment or uploading an attachment writes a
COMMENT_ADDED/ATTACHMENT_ADDEDevent against the parent document, so the activity feed already includes those signals — you do not need to merge in/commentsor/attachmentsseparately for the sidebar.