Comments
Inline notes that hang off any document — invoices, bills, estimates, expenses, journals. Comments are how teams record the “why” behind a number: approver remarks on a bill, sales explaining a credit note, an accountant flagging a reconciliation mismatch.
Comments share the same polymorphic shape as attachments — a single comments table keyed by (entity_type, entity_id). Posting a comment also emits a COMMENT_ADDED activity event, so the dashboard sidebar and the public /activity stream pick it up automatically without any extra wiring.
Resource shape
{
"comment_id": "cmt_8c3f1a9b40b14e3aaf2d7e91a76c1f02",
"entity_type": "INVOICE",
"entity_id": "inv_abc",
"body": "Held pending GSTIN verification with vendor.",
"author_user_id": "usr_xyz",
"created_time": "2026-05-12T07:14:00Z",
"last_modified_time": "2026-05-12T07:14:00Z"
}Common request headers
Authorization: Bearer aibooks_pat_XXXX...
X-AIBooks-Org: org_8f9a1c2d
Content-Type: application/json/commentsPost a comment on a documentAIBooks.comments.CREATEAttaches a free-form text note to one document. The token's authenticated user becomes the author_user_id. There is no built-in @-mention or notification plumbing — partners that want in-app pings should listen for the comment.created webhook and dispatch themselves.
Body
| Field | Required | Notes |
|---|---|---|
entity_type | yes | One of INVOICE, BILL, ESTIMATE, CREDIT_NOTE, DEBIT_NOTE, SALES_ORDER, PURCHASE_ORDER, PAYMENT, RECEIPT, EXPENSE, JOURNAL. |
entity_id | yes | The document to attach to. |
body | yes | Plain text. Hard cap is 5,000 characters. Markdown is not rendered server-side — store it as you want it shown. |
Sample request
POST /api/public/v1/comments HTTP/1.1
Host: api.aibooks.in
Authorization: Bearer aibooks_pat_3p9q0v1w2x3y4z5a6b7c8d9e
X-AIBooks-Org: org_8f9a1c2d
Content-Type: application/json
{
"entity_type": "INVOICE",
"entity_id": "inv_8c3f1a9b",
"body": "Held pending GSTIN verification with vendor. Followed up over email on 11 May."
}Sample response
HTTP/1.1 201 Created
Content-Type: application/json
{
"code": 0,
"message": "The comment has been posted.",
"comment": {
"comment_id": "cmt_8c3f1a9b40b14e3aaf2d7e91a76c1f02",
"entity_type": "INVOICE",
"entity_id": "inv_8c3f1a9b",
"body": "Held pending GSTIN verification with vendor. Followed up over email on 11 May.",
"author_user_id": "usr_xyz",
"created_time": "2026-05-12T07:14:00Z",
"last_modified_time": "2026-05-12T07:14:00Z"
}
}/commentsList commentsAIBooks.comments.READFilter by entity_type + entity_id to fetch every comment on one document. Ordered oldest first — the natural reading order for a discussion thread. If both filters are omitted the call returns every comment in the org, capped at per_page (default 100, max 500).
Sample request
GET /api/public/v1/comments?entity_type=INVOICE&entity_id=inv_8c3f1a9b&per_page=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",
"comments": [
{
"comment_id": "cmt_8c3f1a9b40b14e3aaf2d7e91a76c1f02",
"entity_type": "INVOICE",
"entity_id": "inv_8c3f1a9b",
"body": "Held pending GSTIN verification with vendor. Followed up over email on 11 May.",
"author_user_id": "usr_xyz",
"created_time": "2026-05-12T07:14:00Z",
"last_modified_time": "2026-05-12T07:14:00Z"
},
{
"comment_id": "cmt_5e7f9a1b2c3d4e5f6a7b8c9d0e1f2a3b",
"entity_type": "INVOICE",
"entity_id": "inv_8c3f1a9b",
"body": "@reply: cmt_8c3f1a9b — Vendor confirmed GSTIN today. OK to release.",
"author_user_id": "usr_approver_01",
"created_time": "2026-05-12T11:48:23Z",
"last_modified_time": "2026-05-12T11:48:23Z"
}
],
"page_context": { "page": 1, "per_page": 50, "has_more_page": false }
}/comments/{comment_id}Get one commentAIBooks.comments.READStandard single-resource fetch. Returns 404 if the comment was deleted or belongs to a different org.
Sample request
GET /api/public/v1/comments/cmt_8c3f1a9b40b14e3aaf2d7e91a76c1f02 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",
"comment": {
"comment_id": "cmt_8c3f1a9b40b14e3aaf2d7e91a76c1f02",
"entity_type": "INVOICE",
"entity_id": "inv_8c3f1a9b",
"body": "Held pending GSTIN verification with vendor. Followed up over email on 11 May.",
"author_user_id": "usr_xyz",
"created_time": "2026-05-12T07:14:00Z",
"last_modified_time": "2026-05-12T07:14:00Z"
}
}/comments/{comment_id}Edit a commentAIBooks.comments.UPDATEOnly the original author can edit. Any other caller — even an org admin — receives 403 auth.insufficient_scope. Thelast_modified_time field reflects the most recent edit; the original created_time is preserved so audit readers can spot retro-edits.
Body
| Field | Notes |
|---|---|
body | New body text. Required. |
Sample request
PUT /api/public/v1/comments/cmt_8c3f1a9b40b14e3aaf2d7e91a76c1f02 HTTP/1.1
Host: api.aibooks.in
Authorization: Bearer aibooks_pat_3p9q0v1w2x3y4z5a6b7c8d9e
X-AIBooks-Org: org_8f9a1c2d
Content-Type: application/json
{
"body": "Held pending GSTIN verification. Vendor confirmed 12 May — released."
}Sample response
HTTP/1.1 200 OK
Content-Type: application/json
{
"code": 0,
"message": "The comment has been updated.",
"comment": {
"comment_id": "cmt_8c3f1a9b40b14e3aaf2d7e91a76c1f02",
"entity_type": "INVOICE",
"entity_id": "inv_8c3f1a9b",
"body": "Held pending GSTIN verification. Vendor confirmed 12 May — released.",
"author_user_id": "usr_xyz",
"created_time": "2026-05-12T07:14:00Z",
"last_modified_time": "2026-05-12T12:01:18Z"
}
}FinzBooks does not maintain an edit history — only the latest body is stored. If you need full edit traceability, wire the comment.updated webhook into your own log.
/comments/{comment_id}Delete a commentAIBooks.comments.DELETEAuthor-only, same gate as edit. Delete is hard — the row is removed and a COMMENT_DELETED activity event is written. If you want a soft-delete experience for end users, hide the comment in your UI on thecomment.deleted webhook instead of callingDELETE.
Sample request
DELETE /api/public/v1/comments/cmt_8c3f1a9b40b14e3aaf2d7e91a76c1f02 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": "The comment has been deleted."
}Errors
HTTP/1.1 403 Forbidden
Content-Type: application/json
{
"code": "auth.insufficient_scope",
"message": "Only the original author can edit a comment."
}| Code | HTTP | When |
|---|---|---|
validation.required_field | 422 | body empty on create or update. |
validation.invalid_value | 400 | Unknown entity_type. |
not_found.resource | 404 | Comment ID does not exist in this org. |
auth.insufficient_scope | 403 | Edit or delete attempted by a non-author. |
Operational notes
- Author identity.The author is resolved from the PAT or OAuth token — for PATs that means the user who minted the token, for OAuth that's the user who completed the consent flow. Machine-to-machine tokens with no user owner cannot post comments and will get
403 auth.insufficient_scopeon POST. - Parent document deletion. Hard-deleting an invoice (or any parent doc) cascades to its comments. There is no orphaning.
- Activity timeline coupling. Every comment write triggers an audit log entry. If you are rendering both
/commentsand/activityin the same UI, dedupe bycomment_id— the activity row carries it inmetadata.comment_id. - No threading. Comments are flat — no parent_id, no replies. If you need threaded conversations, the convention is to prefix the body with
@reply: cmt_…and reconstruct the tree in your client.