My App

API Reference

The complete, always-current endpoint reference — generated from the live API

The full endpoint reference — every route, request body, response schema, enum, and error — is published as an OpenAPI 3.0 specification generated directly from the running API. Because it is generated from the same code that serves your requests, it never drifts out of sync.

Prefer the interactive reference for exact field names, casing, required/optional flags, and response shapes. This guide site covers concepts and workflows; the OpenAPI spec is the authoritative reference.

Conventions

  • Host and base path: https://api.bizcare.my, every endpoint under /api/v1. This is the only API host.
  • Auth: send your organization API key in the X-Api-Key header. Keys are environment-bound — see Authentication.
  • Casing: request and response bodies are camelCase throughout.
  • Envelopes: success responses are { success: true, data, message?, pagination? }; errors are { success: false, error, message, code?, details? } — see Error handling.
  • Pagination: list endpoints accept ?page=&per_page= (per_page is 1 to 1000, default 10) and return a pagination object (sibling of data) with page, perPage, total, totalPages, hasNextPage, hasPrevPage. GET /webhooks/:id/deliveries is the exception. It uses limit (1 to 100, default 25) and a before cursor.
  • Issue dates: invoiceDateTime.date and adjustmentNoteIssueDate must be today or up to 3 days back, never in the future. See Issue dates.

Integration behaviour worth knowing

These are the contract points integrators most often need. The OpenAPI spec carries the per-field detail.

Idempotent create

POST /invoices accepts an Idempotency-Key header (1–128 visible ASCII characters). Send a fresh key for each intended invoice and reuse it on every retry of that attempt.

  • The key is scoped to your company and the key's environment (test and live never collide).
  • A repeat returns 200 with the invoice the first request created and idempotentReplay: true. It never creates a second invoice and never submits again.
  • If the repeat carries a different body, the existing invoice is returned unchanged and the new body is ignored — a key names one attempt, not a payload.
  • The key is looked up before the invoice code, because a replay's code is held by the invoice it created.
  • Deleting the invoice frees its key.
  • POST /invoices/bulk takes the same value per row as idempotencyKey; a replayed row is reported as created with idempotentReplay: true.

An idempotency key is not an external id. Use externalId (≤255 chars) and externalIdPlatform (≤64) on create to store your record reference (an order or donation id). They are filterable but deliberately not unique.

Duplicate invoice codes

Invoice codes are unique per company for all time (no year component). Creating an invoice whose code is taken returns 409 with code: "CODE_EXISTS" and details.invoiceId — the id of the invoice that holds it, and details.environment (test or live) saying where it lives. The holder can be in the other environment: a test invoice with code 1001 blocks code 1001 in live. A bulk row returns DUPLICATE_CODE with existingInvoiceId. Because every create should carry an Idempotency-Key, a lost response is always a replay, so a 409 means a different document holds the code.

Reading LHDN references: latestSubmission

Invoice and consolidated-invoice objects carry environment and latestSubmission (or null if never submitted): id, uuid, longId, status (Submitted | Valid | Invalid | Cancelled), failReason, failDetails, submissionUid, submittedAt, updatedAt, cancelledAt, cancelReason. One call gives you the LHDN uuid, long id and rejection reason; no second request to submitted-documents is needed. For an invoice folded into a consolidated invoice, latestSubmission is null — read the status from the consolidated invoice.

failReason is LHDN's concise reason for an Invalid document (for example the innermost validation error such as DR328 Reference Document is missing) and failDetails holds the structured validation results.

List filters

  • GET /invoices: status, type, search, date_from, date_to (yyyy-mm-dd, Malaysian civil dates), invoiceCode (numeric), externalId, externalIdPlatform, consolidatedInvoiceId, plus page / per_page.
  • GET /adjustment-notes: view, status, type, search, plus page / per_page.
  • GET /consolidated-invoices: status, date_from, date_to. It has no search or type.

status matches the status string the API returns: Draft, Pending, Submitted, Valid, Invalid, Cancelled or AWAIT_SUBMISSION_TO_LHDN. Unknown query parameters are ignored, not rejected — check your parameter names against the spec.

Submitted documents

GET …/submitted-documents returns a typed record per LHDN submission (status, uuid, longId, failReason, failDetails, cancellation audit). The raw base64 document and the raw LHDN submit/status responses are no longer included on /api/v1. Use GET /submitted-documents/export or GET /submitted-documents/:id/export when you need them.

How statuses stay fresh (polling)

You normally do not poll LHDN yourself. BizCARE:

  1. polls LHDN every 60 seconds after a submit while LHDN reports "in progress";
  2. runs a sweep every half hour that re-checks any document still Submitted more than 10 minutes after submission (up to 30 days back), so a lost job cannot strand a document;
  3. pushes changes to your webhooks as soon as it sees them.

To force a check, call POST /submitted-documents/sync-status (all still-Submitted documents of your company in the key's environment, oldest first, at most 20 per call; returns { checked, updated }) or POST /submitted-documents/:id/sync-status for one document.

Cancelling

An LHDN document can be cancelled only within 72 hours of submission. After that, the cancel endpoints refuse with code: "CANCEL_FAILED" and details: { reason: "CANCEL_WINDOW_EXPIRED" }; issue a credit note instead. The cancel body needs a reason. See Cancel a document.

Plan quota (402)

402 QUOTA_EXCEEDED carries details: { currentUsage, limit, planName } on the invoice create and submit routes. POST /invoices/bulk is different: it creates the invoices unsubmitted and reports quota exhaustion in the 200 body (submission.error = "QUOTA_EXCEEDED" with the same fields).

Create an e-invoice request

POST /einvoice-requests records a buyer's request for an e-invoice against one of your invoice codes. The request starts as Pending. Approve or reject it with PUT /einvoice-requests/:id.

curl -X POST https://api.bizcare.my/api/v1/einvoice-requests \
  -H "X-Api-Key: $BIZCARE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "invoiceCode": "INV-0001",
    "buyer": {
      "name": "Buyer Sdn Bhd",
      "partyType": "business",
      "tin": "C1234567890",
      "registrationType": "BRN",
      "registrationNumber": "201901000001",
      "contactNumber": "0123456789",
      "email": "buyer@example.com"
    }
  }'

The values above are placeholders. Replace them with your own. A new request returns 201 with the request in the same shape as GET /einvoice-requests/:id. If a Pending request for the same invoiceCode already exists, a retry returns 200 with that request and idempotentReplay: true. A request that was approved or rejected does not block a new one.

Monthly consolidation

A company whose monthlyConsolidationDay is unset is consolidated on day 7 — the default and LHDN's statutory deadline — so no company is silently skipped. Consolidating with nothing to consolidate is a no-op.

Endpoint groups

Each group below says what it is for and shows one call. The OpenAPI spec has every field. All examples use https://api.bizcare.my/api/v1 and an X-Api-Key header.

Adjustment notes

Credit, debit and refund notes that correct a Valid invoice. Create one with POST /invoices/:id/adjustment-note (see Issue a credit note). Everything after creation lives under /adjustment-notes.

MethodPathPurpose
GET/adjustment-notesList (filters above)
GET PUT DELETE/adjustment-notes/:idRead, update or delete. Update and delete work only before submission.
POST/adjustment-notes/:id/submit-to-myinvoisSubmit to LHDN
POST/adjustment-notes/:id/cancel-latest-adjustment-note-submittedCancel (needs reason)
GET/adjustment-notes/:id/submitted-documentsLHDN submission history
GET/adjustment-notes/export, /adjustment-notes/:id/exportJSON export
curl "https://api.bizcare.my/api/v1/adjustment-notes?status=Valid&per_page=20" \
  -H "X-Api-Key: biz_test_your-api-key"

Credit-note create refusals (INVOICE_NOT_VALID, NOTE_IN_PROGRESS, OVER_CREDIT, DUPLICATE_CODE) are 409 responses. A submit refused because the parent invoice is no longer Valid is also 409 INVOICE_NOT_VALID.

Consolidated invoices

One LHDN document that rolls up many small invoices. BizCARE creates them on a monthly schedule (see Monthly consolidation). You read them here and can correct them with notes. POST /companies/consolidate-now runs the consolidation immediately.

MethodPathPurpose
GET/consolidated-invoicesList (status, date_from, date_to)
GET/consolidated-invoices/:idOne consolidated invoice, with latestSubmission
GET/consolidated-invoices/:id/submitted-documentsLHDN submission history
POST/consolidated-invoices/:id/adjustment-notesCreate a note against it (plural, unlike the invoice route)
GET/consolidated-invoices/export, /consolidated-invoices/:id/exportJSON export
curl "https://api.bizcare.my/api/v1/consolidated-invoices?status=Valid" \
  -H "X-Api-Key: biz_test_your-api-key"

Submitted documents

One record per LHDN submission, across invoices, notes and consolidated invoices. Use it to see the LHDN uuid, longId and rejection reason, or to force a status check.

MethodPathPurpose
GET/submitted-documentsList all submissions
GET/submitted-documents/:idOne submission
POST/submitted-documents/sync-statusRe-check all Submitted documents (max 20 per call)
POST/submitted-documents/:id/sync-statusRe-check one
GET/submitted-documents/:id/ubl-totalsUBL totals for one submitted document
GET/submitted-documents/export, /submitted-documents/:id/exportExport with the raw documents
curl -X POST https://api.bizcare.my/api/v1/submitted-documents/sync-status \
  -H "X-Api-Key: biz_test_your-api-key"

The company-wide sync returns 409 SYNC_IN_PROGRESS if another run is active. Wait and retry.

Exports

Export routes return a JSON file download (Content-Disposition: attachment). They exist for invoices, adjustment notes, consolidated invoices and submitted documents. The list-wide route is /export (for example GET /invoices/export). The single-document route is /:id/export. GET /invoices/export returns at most 1000 invoices.

curl -o invoice.json https://api.bizcare.my/api/v1/invoices/f7e6d5c4-b3a2-1098-7654-321fedcba098/export \
  -H "X-Api-Key: biz_test_your-api-key"

Bulk create and bulk submit

POST /invoices/bulk creates many invoices in one call. Each row takes the same fields as POST /invoices plus an optional idempotencyKey, and it never submits. POST /invoices/bulk-submit then submits invoices you already created. Send up to 100 ids. The invoices must be ready. The response has the same shape as a single submit.

curl -X POST https://api.bizcare.my/api/v1/invoices/bulk-submit \
  -H "X-Api-Key: biz_test_your-api-key" \
  -H "Content-Type: application/json" \
  -d '{ "invoiceIds": ["f7e6d5c4-b3a2-1098-7654-321fedcba098"] }'

Read data.accepted, data.rejected and each results[].outcome. HTTP 200 does not mean LHDN accepted every invoice. Plan quota can fail the call with 402 QUOTA_EXCEEDED.

Cancel a document

Cancel a Valid or Submitted LHDN document within 72 hours of submission.

MethodPath
POST/invoices/:id/cancel-latest-invoice-submitted
POST/adjustment-notes/:id/cancel-latest-adjustment-note-submitted

The body must carry a non-empty reason:

curl -X POST https://api.bizcare.my/api/v1/invoices/f7e6d5c4-b3a2-1098-7654-321fedcba098/cancel-latest-invoice-submitted \
  -H "X-Api-Key: biz_test_your-api-key" \
  -H "Content-Type: application/json" \
  -d '{ "reason": "Wrong buyer" }'

Refusals are 403 CANCEL_FAILED. The cases are: the 72-hour window passed (details.reason is CANCEL_WINDOW_EXPIRED), the invoice was never submitted, the document is already cancelled or invalid, or the invoice still has active adjustment notes (cancel the notes first). A successful cancel fires a document.cancelled webhook.

Usage

Shows how much of your plan quota the key's environment has used. test and live are counted separately.

curl https://api.bizcare.my/api/v1/usage -H "X-Api-Key: biz_live_your-api-key"

The response data has currentUsage, limit, planName, allowed and environment. POST /usage/event records a usage event (action), and fails with 402 QUOTA_EXCEEDED when the quota is full.

Reference data: MSIC codes

GET /reference/msic?q=software&page=1&perPage=10 searches LHDN MSIC business-activity codes. Use it to find the msic value for a supplier. perPage is capped at 50.

curl "https://api.bizcare.my/api/v1/reference/msic?q=software" \
  -H "X-Api-Key: biz_test_your-api-key"

Validation helpers

Besides TIN validation, these pre-checks avoid a failed create.

MethodPathResult
POST/validation/invoice-codeIs this invoice code free?
POST/validation/adjustment-note-codeIs this note code free?
POST/validation/company-nameIs this company name free? Body { "name": "..." }. 200 with available: true, or 409 CONFLICT if taken.
curl -X POST https://api.bizcare.my/api/v1/validation/company-name \
  -H "X-Api-Key: biz_test_your-api-key" \
  -H "Content-Type: application/json" \
  -d '{ "name": "Acme Sdn Bhd" }'

Credentials availability

GET /credentials/availability says whether pre-registered LHDN credentials are available to assign. The response data is { available, count }.

curl https://api.bizcare.my/api/v1/credentials/availability \
  -H "X-Api-Key: biz_test_your-api-key"

E-invoice requests

Requests a buyer raises asking you for an e-invoice. You can create, list, read, change the status of, and delete them. See Create an e-invoice request.

MethodPathPurpose
POST/einvoice-requestsCreate a Pending request
GET/einvoice-requestsList (page, per_page)
GET/einvoice-requests/:idRead one
PUT/einvoice-requests/:idSet status to Pending, Approve or Reject
DELETE/einvoice-requests/:idDelete
curl -X PUT https://api.bizcare.my/api/v1/einvoice-requests/REQUEST_ID \
  -H "X-Api-Key: biz_test_your-api-key" \
  -H "Content-Type: application/json" \
  -d '{ "status": "Approve" }'

Migrating from the v1 API? See the Migration guide.

On this page