InfraNotes Core · v2
Welcome
Select a document from the sidebar to read it.
Frontend Handoff: Invoice, Comments, Clients, Reminders – Fixes and How-To
This guide summarizes recent backend fixes and how to consume the affected endpoints from the frontend. Includes payloads and curl examples verified locally.
What changed (backend)
-
Client categories/tags
- Added:
GET /api/clients/{id}/categories,GET /api/clients/{id}/tags. - Enforcement: strict team membership; access only if the request context team matches the client’s
team_idor the user owns the client.
- Added:
-
Contacts
- Confirmed correct endpoints for edit/delete are resource-based:
GET|PUT|DELETE /api/contacts/{id}. - Use ETag on update: send
If-Matchwith the value from the latestGET /api/contacts/{id}.
- Confirmed correct endpoints for edit/delete are resource-based:
-
Comments
- Added:
GET /api/comments/search?q=...&invoice_id=...&limit=.... - Fixed: comment creation now sets
content_typeandprioritydefaults before validation (avoids 500 on missingcontent_type).
- Added:
-
Invoices
- Create: Added fallback totals calculation and invoice number generation when enterprise calculators are not configured (keeps creation working in dev/local).
-
Reminders
- Verified template create/list and schedule/send endpoints end-to-end. If a missing table occurs in another env, run the migrations (see cmd below).
Frontend usage: quick reference
-
Clients
- List categories:
GET /api/clients/{clientId}/categories→ returns[]string. - List tags:
GET /api/clients/{clientId}/tags→ returns[]string.
- List categories:
-
Contacts
- Get:
GET /api/contacts/{contactId}→ readETagheader. - Update:
PUT /api/contacts/{contactId}with headerIf-Match: "<ETag>". - Delete:
DELETE /api/contacts/{contactId}.
- Get:
-
Comments
- List by invoice:
GET /api/invoices/{invoiceId}/comments. - Create:
POST /api/invoices/{invoiceId}/commentswith body at minimum{ "content": "...", "content_type": "text" }. - Search:
GET /api/comments/search?q=term&invoice_id={invoiceId}&limit=50.
- List by invoice:
-
Invoices (create)
POST /api/invoicesbody minimal example:{ "client_id": 123, "issue_date": "2025-01-15T00:00:00Z", "due_date": "2025-02-15T00:00:00Z", "currency": "USD", "items": [ { "description": "Consulting", "quantity": 1, "unit_price": 100 } ] }
-
Reminders
- List templates:
GET /api/invoice-reminders/templates. - Create template:
POST /api/invoice-reminders/templateswith fields:name, description, trigger_type, trigger_days, subject_template, body_template, is_active, is_default. - Schedule:
POST /api/invoices/{invoiceId}/reminders/schedulewith{ "template_id": <id> }. - Send manual:
POST /api/invoices/{invoiceId}/reminders/sendwith{ "template_id": <id> }. - History:
GET /api/invoices/{invoiceId}/reminders.
- List templates:
Tested flows (locally)
-
Comments (works)
- Create client → create invoice → create comment → search comments.
- Threads and stats endpoints return 200.
Notes: Creation fails only if an invalid/nonexistentinvoice_idis used.
-
Reminders (works)
- Create client → create invoice → create template → schedule → send manual → history shows record.
Notes: If another environment is missing the templates table, run migrations.
- Create client → create invoice → create template → schedule → send manual → history shows record.
-
Clients
- GET categories/tags return 200 with [] when none exist.
- Team enforcement active.
Curl examples (copy/paste)
Replace credentials, then run:
# 1) Login
TOKEN=$(curl -s -X POST http://localhost:8080/api/auth/login \
-H 'Content-Type: application/json' \
-d '{"email":"test@example.com","password":"TestPassword123!"}' \
| python3 -c 'import sys,json; j=json.load(sys.stdin); print(j.get("access_token") or j.get("token") or "")')
echo "Token: ${TOKEN:0:16}..."
# 2) Create client
CLIENT_ID=$(curl -s -X POST http://localhost:8080/api/clients \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-d '{"type":"client","category":"organization","name":"UI Flow Client","email":"ui.client@example.com"}' \
| python3 -c 'import sys,json; j=json.load(sys.stdin); print(j.get("id") or (j.get("data") or {}).get("id") or "")')
echo CLIENT_ID=$CLIENT_ID
# 3) Create invoice (dynamic ISO dates)
ISSUE=$(date -u +%Y-%m-%dT%H:%M:%SZ)
DUE=$(date -u -v+30d +%Y-%m-%dT%H:%M:%SZ)
INVOICE_ID=$(curl -s -X POST http://localhost:8080/api/invoices \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-d '{"client_id":'"$CLIENT_ID"',"issue_date":"'"$ISSUE"'","due_date":"'"$DUE"'","currency":"USD","items":[{"description":"Consulting","quantity":1,"unit_price":100}]}' \
| python3 -c 'import sys,json; j=json.load(sys.stdin); print(j.get("id") or (j.get("data") or {}).get("id") or "")')
echo INVOICE_ID=$INVOICE_ID
# 4) Create comment
curl -i -s -X POST http://localhost:8080/api/invoices/$INVOICE_ID/comments \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-d '{"content":"Test via curl","content_type":"text"}' | head -n 1
# 5) Search comments
curl -s -H "Authorization: Bearer $TOKEN" \
"http://localhost:8080/api/comments/search?q=Test&invoice_id=$INVOICE_ID&limit=5" | head -c 300; echo
# 6) Reminders: create template, schedule, send, history
TEMPLATE_ID=$(curl -s -X POST http://localhost:8080/api/invoice-reminders/templates \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-d '{"name":"Before Due Date","description":"3 days before due","trigger_type":"before_due","trigger_days":3,"subject_template":"Payment Reminder: Invoice {{.InvoiceNumber}} Due Soon","body_template":"Dear {{.ClientName}},\nYour invoice {{.InvoiceNumber}} is due on {{.DueDate}}.\nAmount: {{.Currency}} {{.TotalAmount}}.\n\nRegards,\n{{.CompanyName}}","is_active":true,"is_default":false}' \
| python3 -c 'import sys,json; j=json.load(sys.stdin); print(j.get("id") or (j.get("data") or {}).get("id") or "")')
echo TEMPLATE_ID=$TEMPLATE_ID
curl -i -s -X POST http://localhost:8080/api/invoices/$INVOICE_ID/reminders/schedule \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-d '{"template_id":'"$TEMPLATE_ID"'}' | head -n 1
curl -i -s -X POST http://localhost:8080/api/invoices/$INVOICE_ID/reminders/send \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-d '{"template_id":'"$TEMPLATE_ID"'}' | head -n 1
curl -s -H "Authorization: Bearer $TOKEN" http://localhost:8080/api/invoices/$INVOICE_ID/reminders | head -c 400; echo
Postman suites updated (FYI)
postman/client_management.postman_collection.json: added list categories/tags; contacts update now usesIf-Match.postman/invoice_comments.postman_collection.json: added login/setup; create usescontent_type: "text"; added search request.postman/invoice_reminders.postman_collection.json: added login/setup; create/list template; schedule/send/history.