> ## Documentation Index
> Fetch the complete documentation index at: https://code.dcycle.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Invoice Comments

> Leave comments on an invoice, @mention teammates, and attach supporting files

# Invoice Comments

Comments let your team annotate an invoice with context — the source of an evidence document, a note explaining a manual adjustment, or a question for a teammate. Comments support tagging other users (`@mentions`, which trigger an in-app notification) and file attachments.

Only the comment's author can edit or delete it.

## Authentication

All endpoints require the standard headers:

<ParamField header="x-api-key" type="string" required>
  Your API key for authentication
</ParamField>

<ParamField header="x-organization-id" type="string" required>
  Your organization UUID. The invoice must belong to a facility owned by this organization, or the request returns `404`.
</ParamField>

## Create a Comment

<span className="api-method post">POST</span> `/v1/invoices/{invoice_id}/comments`

<ParamField path="invoice_id" type="uuid" required>
  UUID of the invoice to comment on
</ParamField>

<ParamField body="comment" type="string" required>
  The comment text (1–10,000 characters)
</ParamField>

<ParamField body="mentioned_user_ids" type="uuid[]">
  IDs of users to tag. Invalid or non-existent user ids are silently dropped. Each valid mention receives an in-app notification.
</ParamField>

```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}}
curl -X POST "https://api.dcycle.io/v1/invoices/${INVOICE_ID}/comments" \
  -H "x-organization-id: ${DCYCLE_ORG_ID}" \
  -H "x-api-key: ${DCYCLE_API_KEY}" \
  -H "Content-Type: application/json" \
  -d '{"comment": "Verified against the supply contract.", "mentioned_user_ids": ["b3f1..."]}'
```

## List Comments

<span className="api-method get">GET</span> `/v1/invoices/{invoice_id}/comments`

Returns all comments for the invoice, oldest first.

## Edit a Comment

<span className="api-method patch">PATCH</span> `/v1/invoices/comments/{comment_id}`

Same body as create. Only the original author may edit; any other user gets `403`. Only newly-added mentions (not already-mentioned users) receive a notification.

## Delete a Comment

<span className="api-method delete">DELETE</span> `/v1/invoices/comments/{comment_id}`

Author-only, `403` otherwise. Deleting a comment also deletes its mentions and attachments.

## Response Object

<ResponseField name="id" type="string">UUID of the comment</ResponseField>
<ResponseField name="invoice_id" type="string">UUID of the invoice</ResponseField>
<ResponseField name="comment" type="string">Comment text</ResponseField>
<ResponseField name="created_by" type="string | null">UUID of the author</ResponseField>

<ResponseField name="user" type="object | null">
  Author details

  <Expandable title="User object">
    <ResponseField name="id" type="string">UUID</ResponseField>
    <ResponseField name="first_name" type="string">First name</ResponseField>
    <ResponseField name="last_name" type="string">Last name</ResponseField>
    <ResponseField name="email" type="string">Email address</ResponseField>
    <ResponseField name="profile_img_url" type="string | null">Profile image URL</ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="mentioned_user_ids" type="string[]">UUIDs of users mentioned in this comment</ResponseField>
<ResponseField name="attachments" type="array">List of attachment objects (see below)</ResponseField>
<ResponseField name="created_at" type="datetime">When the comment was created</ResponseField>
<ResponseField name="updated_at" type="datetime | null">When the comment was last edited</ResponseField>

## Attachments

Attaching a file to a comment is a two-step flow: request a presigned upload URL, upload the file directly to S3, then register the attachment.

### 1. Request a Presigned Upload URL

<span className="api-method post">POST</span> `/v1/invoices/comments/{comment_id}/attachments/presigned-url`

<ParamField body="file_name" type="string" required>
  Original file name with extension (no path separators)
</ParamField>

<ParamField body="content_type" type="string">
  MIME type of the file
</ParamField>

Returns `{"upload_url": "...", "file_id": "...", "file_name": "..."}`. `upload_url` is a short-lived (5 minute) presigned S3 `PUT` URL.

### 2. Upload the File

`PUT` the raw file bytes directly to `upload_url`, setting the `Content-Type` header to match what you sent in step 1 (the URL signature covers `Content-Type`).

### 3. Register the Attachment

<span className="api-method post">POST</span> `/v1/invoices/comments/{comment_id}/attachments`

<ParamField body="file_name" type="string" required>
  Same file name as step 1
</ParamField>

<ParamField body="file_id" type="string" required>
  The `file_id` returned in step 1. Must belong to this invoice's own organization — a `file_id` issued for a different organization is rejected with `400`.
</ParamField>

<ParamField body="content_length_bytes" type="integer">
  File size in bytes, for display purposes
</ParamField>

Supported extensions: `csv`, `xlsx`, `xls`, `pdf`, `jpg`, `jpeg`, `png`, `doc`, `docx`, `txt`, `json`, `xml`. The attachment is also mirrored into your organization's general Files listing.

### Attachment Object

<ResponseField name="id" type="string">UUID of the attachment</ResponseField>
<ResponseField name="comment_id" type="string">UUID of the parent comment</ResponseField>
<ResponseField name="file_name" type="string">Original file name</ResponseField>
<ResponseField name="file_url" type="string">Public URL of the stored file</ResponseField>
<ResponseField name="file_id" type="string">S3 key of the stored file</ResponseField>
<ResponseField name="uploaded_by" type="string | null">UUID of the user who uploaded it</ResponseField>
<ResponseField name="created_at" type="datetime">When the attachment was registered</ResponseField>

## Common Errors

### 404 Not Found

**Cause:** The invoice (or comment) doesn't exist, or doesn't belong to your organization.

### 403 Forbidden

**Cause:** Attempting to edit or delete a comment you didn't author.

### 400 Bad Request

**Cause:** Unsupported attachment file extension, or `file_id` wasn't issued for this organization.

## Related Endpoints

<CardGroup cols={2}>
  <Card title="Get Invoice" icon="file-invoice" href="/api-reference/invoices/get">
    Get a single invoice by ID
  </Card>

  <Card title="List Invoices" icon="list" href="/api-reference/invoices/list">
    Retrieve invoices with filtering and pagination
  </Card>
</CardGroup>
