FinzBooksDevelopers

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_8f9a1c2d

Action 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.
GET/activityList events for the orgAIBooks.activity.READ

The 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

ParamNotes
entity_typeRestrict to INVOICE / BILL / ESTIMATE / CREDIT_NOTE / DEBIT_NOTE / SALES_ORDER / PURCHASE_ORDER / PAYMENT / RECEIPT / EXPENSE / JOURNAL / CONTACT / ITEM / ATTACHMENT. Unknown values return 400.
entity_idScope to one specific document.
actor_user_id“Show me everything Priya did this week” — audit-style queries.
sinceISO timestamp lower bound (inclusive).
untilISO timestamp upper bound (inclusive).
limit1 – 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_8f9a1c2d

Sample 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.

GET/invoices/{invoice_id}/activityInvoice timelineAIBooks.activity.READ

Sample 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_8f9a1c2d

Sample 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 }
}
GET/bills/{bill_id}/activityBill timelineAIBooks.activity.READ

Sample 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_8f9a1c2d

Sample 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 }
}
GET/estimates/{estimate_id}/activityEstimate timelineAIBooks.activity.READ
GET/credit_notes/{credit_note_id}/activityCredit note timelineAIBooks.activity.READ
GET/debit_notes/{debit_note_id}/activityDebit note timelineAIBooks.activity.READ
GET/payments/{payment_id}/activityPayment timelineAIBooks.activity.READ
GET/expenses/{expense_id}/activityExpense timelineAIBooks.activity.READ

Each 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_8f9a1c2d

Sample 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", "..."] }
}
CodeHTTPWhen
validation.invalid_value400Unknown entity_type on the generic endpoint, or unparsable since / until ISO string.
authn.token_invalid401PAT expired or revoked.
authz.scope_missing403Token lacks AIBooks.activity.READ.

Operational notes

  • Read-only by design. There is noPOST /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 aCOMMENT_ADDED / ATTACHMENT_ADDED event against the parent document, so the activity feed already includes those signals — you do not need to merge in /comments or /attachments separately for the sidebar.