Source: https://billingmart.com/developers/api/capture # Time, expenses, mileage and documents Work logged during the month, billed onto an invoice exactly once, and paper read from a photo or PDF into a draft a person files. A CSV of clients, items or open invoices can be dry-run here; a person runs the import in the console. [All sections](https://billingmart.com/developers/api) [Authentication](https://billingmart.com/developers/authentication) **On this page** - GET List - POST Create - GET List - POST Create - GET List - POST Create - PATCH Update - POST Start session - GET List sessions - GET Get session - POST Pause session - POST Resume session - POST Stop session - POST Stop all sessions - PATCH Update - PATCH Update - POST Create - GET List - POST Organize - GET Get - POST Confirm - POST Discard - POST Create - GET Get - DELETE Discard - OBJ TimeEntry - OBJ Expense - OBJ Mileage - OBJ FieldSession - OBJ OrganizerDoc - OBJ Import On this page - GET List - POST Create - GET List - POST Create - GET List - POST Create - PATCH Update - POST Start session - GET List sessions - GET Get session - POST Pause session - POST Resume session - POST Stop session - POST Stop all sessions - PATCH Update - PATCH Update - POST Create - GET List - POST Organize - GET Get - POST Confirm - POST Discard - POST Create - GET Get - DELETE Discard - OBJ TimeEntry - OBJ Expense - OBJ Mileage - OBJ FieldSession - OBJ OrganizerDoc - OBJ Import time ## List GET `/v1/time-entries` List time entries, newest first. Scope `time:read` Effect Read only MCP tool `list_time_entries` Retries Safe to repeat ### Parameters List parameters | Name | In | Type | Notes | | --- | --- | --- | --- | | `limit`optional | query | `integer` | min 1 · max 50 | | `cursor`optional | query | `string` | up to 256 chars | | `clientId`optional | query | `id` | — | | `jobId`optional | query | `id` | — | | `billed`optional | query | `boolean` | — | ### Returns `data`: [`TimeEntry[]`](https://billingmart.com/developers/api/capture#object-TimeEntry) . Pass `meta.cursor` back as `cursor` for the next page. Example request Sample ``` curl -X GET "https://api.billingmart.com/v1/time-entries" \ -H "Authorization: Bearer $BILLINGMART_API_KEY" ``` time ## Create POST `/v1/time-entries` Record time. Minutes are integers — never a decimal of hours. Scope `time:write` Effect Writes a draft or record MCP tool `record_time` Retries Send an Idempotency-Key; a repeat replays the stored answer for 24 hours ### Request body JSON. Money is an integer count of minor units beside its currency; quantities are thousandths. Create request body | Field | Type | Notes | | --- | --- | --- | | `date`required | `string` | matches `^\d{4}-\d{2}-\d{2}$` | | `minutes`required | `integer` | min 1 · max 1440 | | `billable`required | `boolean` | — | | `clientId`optional | `id` | — | | `projectRef`optional | `string` | up to 120 chars | | `description`optional | `string` | up to 2000 chars | | `hourlyRate`optional | `object` | — | | `hourlyRate.minorUnits`required | `integer` | — | | `hourlyRate.currency`required | `Currency` | — | | `businessId`optional | `id` | — | ### Returns `data`: `object` Example request Sample ``` curl -X POST "https://api.billingmart.com/v1/time-entries" \ -H "Authorization: Bearer $BILLINGMART_API_KEY" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{"date":"","minutes":0,"billable":false,"hourlyRate":{"minorUnits":0,"currency":"USD"}}' ``` expenses ## List GET `/v1/expenses` List expenses, newest first. Scope `expenses:read` Effect Read only MCP tool `list_expenses` Retries Safe to repeat ### Parameters List parameters | Name | In | Type | Notes | | --- | --- | --- | --- | | `limit`optional | query | `integer` | min 1 · max 50 | | `cursor`optional | query | `string` | up to 256 chars | | `clientId`optional | query | `id` | — | | `jobId`optional | query | `id` | — | | `billed`optional | query | `boolean` | — | ### Returns `data`: [`Expense[]`](https://billingmart.com/developers/api/capture#object-Expense) . Pass `meta.cursor` back as `cursor` for the next page. Example request Sample ``` curl -X GET "https://api.billingmart.com/v1/expenses" \ -H "Authorization: Bearer $BILLINGMART_API_KEY" ``` expenses ## Create POST `/v1/expenses` Record an expense. Scope `expenses:write` Effect Writes a draft or record MCP tool `record_expense` Retries Send an Idempotency-Key; a repeat replays the stored answer for 24 hours ### Request body JSON. Money is an integer count of minor units beside its currency; quantities are thousandths. Create request body | Field | Type | Notes | | --- | --- | --- | | `date`required | `string` | matches `^\d{4}-\d{2}-\d{2}$` | | `amount`required | `object` | — | | `amount.minorUnits`required | `integer` | — | | `amount.currency`required | `Currency` | — | | `rebillable`required | `boolean` | — | | `clientId`optional | `id` | — | | `projectRef`optional | `string` | up to 120 chars | | `category`optional | `string` | up to 80 chars | | `vendor`optional | `string` | up to 200 chars | | `description`optional | `string` | up to 2000 chars | | `markupBasisPoints`optional | `integer` | min 0 · max 100000 | | `businessId`optional | `id` | — | ### Returns `data`: `object` Example request Sample ``` curl -X POST "https://api.billingmart.com/v1/expenses" \ -H "Authorization: Bearer $BILLINGMART_API_KEY" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{"date":"","amount":{"minorUnits":0,"currency":"USD"},"rebillable":false}' ``` mileage ## List GET `/v1/mileage` List mileage entries, newest first. Scope `mileage:read` Effect Read only MCP tool `list_mileage` Retries Safe to repeat ### Parameters List parameters | Name | In | Type | Notes | | --- | --- | --- | --- | | `limit`optional | query | `integer` | min 1 · max 50 | | `cursor`optional | query | `string` | up to 256 chars | | `clientId`optional | query | `id` | — | | `jobId`optional | query | `id` | — | | `billed`optional | query | `boolean` | — | ### Returns `data`: [`Mileage[]`](https://billingmart.com/developers/api/capture#object-Mileage) . Pass `meta.cursor` back as `cursor` for the next page. Example request Sample ``` curl -X GET "https://api.billingmart.com/v1/mileage" \ -H "Authorization: Bearer $BILLINGMART_API_KEY" ``` mileage ## Create POST `/v1/mileage` Record a journey. Distance is in thousandths of the unit, as an integer. Scope `mileage:write` Effect Writes a draft or record MCP tool `record_mileage` Retries Send an Idempotency-Key; a repeat replays the stored answer for 24 hours ### Request body JSON. Money is an integer count of minor units beside its currency; quantities are thousandths. Create request body | Field | Type | Notes | | --- | --- | --- | | `date`required | `string` | matches `^\d{4}-\d{2}-\d{2}$` | | `distanceMilli`required | `integer` | min 1 | | `distanceUnit`required | `enum` | One of `mile`, `kilometre` | | `rebillable`required | `boolean` | — | | `clientId`optional | `id` | — | | `projectRef`optional | `string` | up to 120 chars | | `fromLocation`optional | `string` | up to 300 chars | | `toLocation`optional | `string` | up to 300 chars | | `purpose`optional | `string` | up to 500 chars | | `ratePerUnit`optional | `object` | — | | `ratePerUnit.minorUnits`required | `integer` | — | | `ratePerUnit.currency`required | `Currency` | — | | `businessId`optional | `id` | — | ### Returns `data`: `object` Example request Sample ``` curl -X POST "https://api.billingmart.com/v1/mileage" \ -H "Authorization: Bearer $BILLINGMART_API_KEY" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{"date":"","distanceMilli":0,"distanceUnit":"mile","rebillable":false,"ratePerUnit":{"minorUnits":0,"currency":"USD"}}' ``` time ## Update PATCH `/v1/time-entries/{entryId}` Correct an UNBILLED time entry. A billed one is refused; billed and billedInvoiceId are never accepted. Scope `time:write` Effect Writes a draft or record MCP tool `update_time_entry` Retries No Idempotency-Key on this route ### Parameters Update parameters | Name | In | Type | Notes | | --- | --- | --- | --- | | `entryId`required | path | `id` | — | ### Request body JSON. Money is an integer count of minor units beside its currency; quantities are thousandths. Update request body | Field | Type | Notes | | --- | --- | --- | | `date`optional | `string` | matches `^\d{4}-\d{2}-\d{2}$` | | `minutes`optional | `integer` | min 1 · max 1440 | | `billable`optional | `boolean` | — | | `clientId`optional | `id` | — | | `projectRef`optional | `string` | up to 120 chars | | `description`optional | `string` | up to 2000 chars | | `hourlyRate`optional | `object` | — | | `hourlyRate.minorUnits`required | `integer` | — | | `hourlyRate.currency`required | `Currency` | — | ### Returns `data`: [`TimeEntry`](https://billingmart.com/developers/api/capture#object-TimeEntry) Errors this route can answer besides the shared ones: `INVALID_TRANSITION` . Example request Sample ``` curl -X PATCH "https://api.billingmart.com/v1/time-entries/{entryId}" \ -H "Authorization: Bearer $BILLINGMART_API_KEY" \ -H "Content-Type: application/json" \ -d '{"hourlyRate":{"minorUnits":0,"currency":"USD"}}' ``` time ## Start session POST `/v1/time-sessions` Start a timer for the signed-in person (one running at a time). Stopping it records a time entry. A repeated startRequestId answers the same timer. Scope `time:write` Effect Writes a draft or record MCP tool `start_timer` Retries Send an Idempotency-Key; a repeat replays the stored answer for 24 hours ### Request body JSON. Money is an integer count of minor units beside its currency; quantities are thousandths. Start session request body | Field | Type | Notes | | --- | --- | --- | | `startRequestId`required | `id` | — | | `clientId`optional | `id` | — | | `jobId`optional | `id` | — | | `projectRef`optional | `string` | up to 120 chars | | `businessId`optional | `id` | — | ### Returns `data`: `object` Errors this route can answer besides the shared ones: `ALREADY_EXISTS` , `CLIENT_NOT_FOUND` , `CLIENT_ARCHIVED` , `PERMISSION_DENIED` . Example request Sample ``` curl -X POST "https://api.billingmart.com/v1/time-sessions" \ -H "Authorization: Bearer $BILLINGMART_API_KEY" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{"startRequestId":""}' ``` time ## List sessions GET `/v1/time-sessions` The signed-in person’s timers and trips: the open ones by default, or stopped ones. Filter by kind to list only timers or only trips. Scope `time:read` Effect Read only MCP tool `list_timers` Retries Safe to repeat ### Parameters List sessions parameters | Name | In | Type | Notes | | --- | --- | --- | --- | | `status`optional | query | `enum` | One of `running`, `stopped` | | `kind`optional | query | `enum` | One of `timer`, `trip` | ### Returns `data`: [`FieldSession[]`](https://billingmart.com/developers/api/capture#object-FieldSession) . Pass `meta.cursor` back as `cursor` for the next page. Errors this route can answer besides the shared ones: `PERMISSION_DENIED` . Example request Sample ``` curl -X GET "https://api.billingmart.com/v1/time-sessions" \ -H "Authorization: Bearer $BILLINGMART_API_KEY" ``` time ## Get session GET `/v1/time-sessions/{sessionId}` One of the signed-in person’s timers or trips by id: status, minutes, miles, client and job. Another person’s answers NOT_FOUND. Scope `time:read` Effect Read only MCP tool `get_timer` Retries Safe to repeat ### Parameters Get session parameters | Name | In | Type | Notes | | --- | --- | --- | --- | | `sessionId`required | path | `id` | — | ### Returns `data`: [`FieldSession`](https://billingmart.com/developers/api/capture#object-FieldSession) Errors this route can answer besides the shared ones: `NOT_FOUND` , `PERMISSION_DENIED` . Example request Sample ``` curl -X GET "https://api.billingmart.com/v1/time-sessions/{sessionId}" \ -H "Authorization: Bearer $BILLINGMART_API_KEY" ``` time ## Pause session POST `/v1/time-sessions/{sessionId}/pause` Pause one of the signed-in person’s timers: the clock stops counting until it is resumed. A second pause answers the first. A trip cannot be paused. Scope `time:write` Effect Writes a draft or record MCP tool `pause_timer` Retries Send an Idempotency-Key; a repeat replays the stored answer for 24 hours ### Parameters Pause session parameters | Name | In | Type | Notes | | --- | --- | --- | --- | | `sessionId`required | path | `id` | — | ### Returns `data`: `object` Errors this route can answer besides the shared ones: `NOT_FOUND` , `INVALID_TRANSITION` , `PERMISSION_DENIED` . Example request Sample ``` curl -X POST "https://api.billingmart.com/v1/time-sessions/{sessionId}/pause" \ -H "Authorization: Bearer $BILLINGMART_API_KEY" \ -H "Idempotency-Key: $(uuidgen)" ``` time ## Resume session POST `/v1/time-sessions/{sessionId}/resume` Resume one of the signed-in person’s paused timers. A second resume answers the first. Scope `time:write` Effect Writes a draft or record MCP tool `resume_timer` Retries Send an Idempotency-Key; a repeat replays the stored answer for 24 hours ### Parameters Resume session parameters | Name | In | Type | Notes | | --- | --- | --- | --- | | `sessionId`required | path | `id` | — | ### Returns `data`: `object` Errors this route can answer besides the shared ones: `NOT_FOUND` , `INVALID_TRANSITION` , `PERMISSION_DENIED` . Example request Sample ``` curl -X POST "https://api.billingmart.com/v1/time-sessions/{sessionId}/resume" \ -H "Authorization: Bearer $BILLINGMART_API_KEY" \ -H "Idempotency-Key: $(uuidgen)" ``` time ## Stop session POST `/v1/time-sessions/{sessionId}/stop` Stop one of the signed-in person’s timers, including one started on the phone; a minute or more records an unbilled time entry. A second stop answers the first. A mileage trip is refused (INVALID_KIND): it is stopped on the phone. Scope `time:write` Effect Writes a draft or record MCP tool `stop_timer` Retries Send an Idempotency-Key; a repeat replays the stored answer for 24 hours ### Parameters Stop session parameters | Name | In | Type | Notes | | --- | --- | --- | --- | | `sessionId`required | path | `id` | — | ### Returns `data`: `object` Errors this route can answer besides the shared ones: `NOT_FOUND` , `INVALID_TRANSITION` , `INVALID_KIND` , `PERMISSION_DENIED` . Example request Sample ``` curl -X POST "https://api.billingmart.com/v1/time-sessions/{sessionId}/stop" \ -H "Authorization: Bearer $BILLINGMART_API_KEY" \ -H "Idempotency-Key: $(uuidgen)" ``` time ## Stop all sessions POST `/v1/time-sessions/stop-all` Stop every open timer of the signed-in person, one after another; each of a minute or more records its own unbilled time entry. A timer that cannot stop stays open. A trip is not stopped. Scope `time:write` Effect Writes a draft or record MCP tool `stop_all_timers` Retries Send an Idempotency-Key; a repeat replays the stored answer for 24 hours ### Returns `data`: `object` Errors this route can answer besides the shared ones: `PERMISSION_DENIED` . Example request Sample ``` curl -X POST "https://api.billingmart.com/v1/time-sessions/stop-all" \ -H "Authorization: Bearer $BILLINGMART_API_KEY" \ -H "Idempotency-Key: $(uuidgen)" ``` expenses ## Update PATCH `/v1/expenses/{expenseId}` Correct an UNBILLED expense. A billed one is refused; billed and billedInvoiceId are never accepted. Scope `expenses:write` Effect Writes a draft or record MCP tool `update_expense` Retries No Idempotency-Key on this route ### Parameters Update parameters | Name | In | Type | Notes | | --- | --- | --- | --- | | `expenseId`required | path | `id` | — | ### Request body JSON. Money is an integer count of minor units beside its currency; quantities are thousandths. Update request body | Field | Type | Notes | | --- | --- | --- | | `date`optional | `string` | matches `^\d{4}-\d{2}-\d{2}$` | | `amount`optional | `object` | — | | `amount.minorUnits`required | `integer` | — | | `amount.currency`required | `Currency` | — | | `rebillable`optional | `boolean` | — | | `clientId`optional | `id` | — | | `projectRef`optional | `string` | up to 120 chars | | `category`optional | `string` | up to 80 chars | | `vendor`optional | `string` | up to 200 chars | | `description`optional | `string` | up to 2000 chars | | `markupBasisPoints`optional | `integer` | min 0 · max 100000 | ### Returns `data`: [`Expense`](https://billingmart.com/developers/api/capture#object-Expense) Errors this route can answer besides the shared ones: `INVALID_TRANSITION` . Example request Sample ``` curl -X PATCH "https://api.billingmart.com/v1/expenses/{expenseId}" \ -H "Authorization: Bearer $BILLINGMART_API_KEY" \ -H "Content-Type: application/json" \ -d '{"amount":{"minorUnits":0,"currency":"USD"}}' ``` mileage ## Update PATCH `/v1/mileage/{mileageId}` Correct an UNBILLED mileage entry. A billed one is refused; billed and billedInvoiceId are never accepted. Scope `mileage:write` Effect Writes a draft or record MCP tool `update_mileage` Retries No Idempotency-Key on this route ### Parameters Update parameters | Name | In | Type | Notes | | --- | --- | --- | --- | | `mileageId`required | path | `id` | — | ### Request body JSON. Money is an integer count of minor units beside its currency; quantities are thousandths. Update request body | Field | Type | Notes | | --- | --- | --- | | `date`optional | `string` | matches `^\d{4}-\d{2}-\d{2}$` | | `distanceMilli`optional | `integer` | min 1 | | `distanceUnit`optional | `enum` | One of `mile`, `kilometre` | | `rebillable`optional | `boolean` | — | | `clientId`optional | `id` | — | | `projectRef`optional | `string` | up to 120 chars | | `fromLocation`optional | `string` | up to 300 chars | | `toLocation`optional | `string` | up to 300 chars | | `purpose`optional | `string` | up to 500 chars | | `ratePerUnit`optional | `object` | — | | `ratePerUnit.minorUnits`required | `integer` | — | | `ratePerUnit.currency`required | `Currency` | — | ### Returns `data`: [`Mileage`](https://billingmart.com/developers/api/capture#object-Mileage) Errors this route can answer besides the shared ones: `INVALID_TRANSITION` . Example request Sample ``` curl -X PATCH "https://api.billingmart.com/v1/mileage/{mileageId}" \ -H "Authorization: Bearer $BILLINGMART_API_KEY" \ -H "Content-Type: application/json" \ -d '{"ratePerUnit":{"minorUnits":0,"currency":"USD"}}' ``` organizer ## Create POST `/v1/organizer` Add a document to the organizer: answers it and a short-lived signed URL to PUT its one file to (a PNG, JPEG, WebP or PDF, at most 5 MiB and 4 pages), with the headers to send. Nothing is read until organize is called. Refused with AI_CONSENT_REQUIRED until the person behind the credential (a connected app user, or the API key creator) has allowed Capture to send documents to Google Gemini in the product; an assistant cannot allow it for them. Scope `organizer:write` Effect Writes a draft or record MCP tool `add_to_organizer` Retries Send an Idempotency-Key; a repeat replays the stored answer for 24 hours ### Request body JSON. Money is an integer count of minor units beside its currency; quantities are thousandths. Create request body | Field | Type | Notes | | --- | --- | --- | | `contentType`required | `enum` | One of `image/png`, `image/jpeg`, `image/webp`, `application/pdf` | | `fileName`optional | `string` | up to 200 chars | | `businessId`optional | `id` | — | ### Returns `data`: `object` Errors this route can answer besides the shared ones: `BUSINESS_NOT_FOUND` , `AI_CONSENT_REQUIRED` . Example request Sample ``` curl -X POST "https://api.billingmart.com/v1/organizer" \ -H "Authorization: Bearer $BILLINGMART_API_KEY" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{"contentType":"image/png"}' ``` organizer ## List GET `/v1/organizer` Organizer documents, newest first, optionally by status. Scope `organizer:read` Effect Read only MCP tool `list_organizer_documents` Retries Safe to repeat ### Parameters List parameters | Name | In | Type | Notes | | --- | --- | --- | --- | | `limit`optional | query | `integer` | min 1 · max 50 | | `cursor`optional | query | `string` | up to 256 chars | | `status`optional | query | `enum` | One of `awaitingUpload`, `queued`, `reading`, `ready`, `failed`, `filing`, `filed` | ### Returns `data`: [`OrganizerDoc[]`](https://billingmart.com/developers/api/capture#object-OrganizerDoc) . Pass `meta.cursor` back as `cursor` for the next page. Example request Sample ``` curl -X GET "https://api.billingmart.com/v1/organizer" \ -H "Authorization: Bearer $BILLINGMART_API_KEY" ``` organizer ## Organize POST `/v1/organizer/{docId}/organize` Ask the model to read an uploaded document. Answers at once with status queued; the result lands on the document. The server refuses a missing file, another type than declared, more than 5 MiB or a PDF of more than 4 pages. Uses AI credits. Refused with AI_CONSENT_REQUIRED if that consent was withdrawn since the document was added. Scope `organizer:write` Effect Writes a draft or record MCP tool `organize_document` Retries Send an Idempotency-Key; a repeat replays the stored answer for 24 hours ### Parameters Organize parameters | Name | In | Type | Notes | | --- | --- | --- | --- | | `docId`required | path | `id` | — | ### Returns `data`: `object` Errors this route can answer besides the shared ones: `INVALID_TRANSITION` , `AI_CONSENT_REQUIRED` . Example request Sample ``` curl -X POST "https://api.billingmart.com/v1/organizer/{docId}/organize" \ -H "Authorization: Bearer $BILLINGMART_API_KEY" \ -H "Idempotency-Key: $(uuidgen)" ``` organizer ## Get GET `/v1/organizer/{docId}` One organizer document: its status, what the model read (a proposal, never a stored value) and the server's candidates. Candidates of a kind appear only with that kind's read scope. Scope `organizer:read` Effect Read only MCP tool `get_organizer_document` Retries Safe to repeat ### Parameters Get parameters | Name | In | Type | Notes | | --- | --- | --- | --- | | `docId`required | path | `id` | — | ### Returns `data`: [`OrganizerDoc`](https://billingmart.com/developers/api/capture#object-OrganizerDoc) Example request Sample ``` curl -X GET "https://api.billingmart.com/v1/organizer/{docId}" \ -H "Authorization: Bearer $BILLINGMART_API_KEY" ``` organizer ## Confirm POST `/v1/organizer/{docId}/confirm` File a document as a DRAFT invoice, estimate, job invoice, expense or bill, through the same cores as the console. The kind, every id and the amount are the caller's: candidates are never applied by themselves, the stored amount is the one submitted, and lines that do not add up to it are refused. Needs the kind's own write scope and plan feature. Never sends. Scope `organizer:write` Effect Writes a draft or record MCP tool `file_organizer_document` Retries Send an Idempotency-Key; a repeat replays the stored answer for 24 hours ### Parameters Confirm parameters | Name | In | Type | Notes | | --- | --- | --- | --- | | `docId`required | path | `id` | — | ### Request body JSON. Money is an integer count of minor units beside its currency; quantities are thousandths. Confirm request body | Field | Type | Notes | | --- | --- | --- | | `kind`required | `enum` | One of `invoice`, `estimate`, `job`, `receipt`, `bill` | | `amount`required | `object` | — | | `amount.minorUnits`required | `integer` | min 1 · max 100000000000 | | `amount.currency`required | `Currency` | — | | `businessId`optional | `id` | — | | `clientId`optional | `id` | — | | `vendorId`optional | `id` | — | | `vendorName`optional | `string` | up to 200 chars | | `jobId`optional | `id` | — | | `jobLabel`optional | `string` | up to 200 chars | | `depositBasisPoints`optional | `integer` | min 1 · max 10000 | | `description`optional | `string` | up to 300 chars | | `documentNumber`optional | `string` | up to 60 chars | | `issueDate`optional | `string` | — | | `dueDate`optional | `string` | — | | `category`optional | `string` | up to 80 chars | | `notes`optional | `string` | up to 2000 chars | | `rebillable`optional | `boolean` | — | | `lines[]`optional | `object[]` | up to 50 items · at least 1 | | `lines[].description`required | `string` | up to 300 chars | | `lines[].quantityMilli`required | `integer` | min 1 · max 1000000000 | | `lines[].unitPriceMinor`required | `integer` | min -100000000000 · max 100000000000 | ### Returns `data`: `object` Errors this route can answer besides the shared ones: `INVALID_TRANSITION` , `PLAN_FEATURE_REQUIRED` , `INSUFFICIENT_SCOPE` , `BUSINESS_NOT_FOUND` , `CLIENT_NOT_FOUND` , `CLIENT_ARCHIVED` . Example request Sample ``` curl -X POST "https://api.billingmart.com/v1/organizer/{docId}/confirm" \ -H "Authorization: Bearer $BILLINGMART_API_KEY" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{"kind":"invoice","amount":{"minorUnits":0,"currency":"USD"}}' ``` organizer ## Discard POST `/v1/organizer/{docId}/discard` Remove an organizer document that was not filed, and its file. A filed one is kept. Scope `organizer:write` Effect Writes a draft or record MCP tool `discard_organizer_document` Retries Send an Idempotency-Key; a repeat replays the stored answer for 24 hours ### Parameters Discard parameters | Name | In | Type | Notes | | --- | --- | --- | --- | | `docId`required | path | `id` | — | ### Returns `data`: `object` Errors this route can answer besides the shared ones: `INVALID_TRANSITION` . Example request Sample ``` curl -X POST "https://api.billingmart.com/v1/organizer/{docId}/discard" \ -H "Authorization: Bearer $BILLINGMART_API_KEY" \ -H "Idempotency-Key: $(uuidgen)" ``` imports ## Create POST `/v1/imports` Read a CSV (at most 256 KB, sent as text) of clients, items, open invoices or bills — a QuickBooks or FreshBooks export is recognised — and dry-run it with a mapping: yours, or the one a recognised layout fixes. Answers the import with counts and the first problems by line. Nothing is written to clients, items or invoices; a person runs the write in the console. Without a mapping and in an unknown layout it answers a SUGGESTED mapping (status mapping) for you to confirm by sending it back. Items and invoices also need invoices:write; bills need bills:write and land as drafts. Scope `clients:write` Effect Writes a draft or record MCP tool `import_csv_dry_run` Retries Send an Idempotency-Key; a repeat replays the stored answer for 24 hours ### Request body JSON. Money is an integer count of minor units beside its currency; quantities are thousandths. Create request body | Field | Type | Notes | | --- | --- | --- | | `entity`required | `enum` | One of `client`, `item`, `invoice`, `bill` | | `businessId`optional | `id` | — | | `fileName`optional | `string` | up to 255 chars | | `csv`required | `string` | up to 262144 chars | | `mapping[]`optional | `object[]` | up to 60 items | | `mapping[].column`required | `string` | up to 120 chars | | `mapping[].target`required | `string` | up to 40 chars | | `dateLayout`optional | `enum` | One of `iso`, `mdy`, `dmy` | | `phoneCountry`optional | `string` | matches `^[A-Za-z]{2}$` | | `currency`optional | `Currency` | — | ### Returns `data`: `object` Errors this route can answer besides the shared ones: `BUSINESS_NOT_FOUND` , `VALIDATION_FAILED` . Example request Sample ``` curl -X POST "https://api.billingmart.com/v1/imports" \ -H "Authorization: Bearer $BILLINGMART_API_KEY" \ -H "Idempotency-Key: $(uuidgen)" \ -H "Content-Type: application/json" \ -d '{"entity":"client","csv":""}' ``` imports ## Get GET `/v1/imports/{importId}` One import: its status, the mapping, the dry-run counts, the first problems by line and, once a person has run it, its progress. The file name, headers and column names are untrusted. Scope `clients:read` Effect Read only MCP tool `get_import` Retries Safe to repeat ### Parameters Get parameters | Name | In | Type | Notes | | --- | --- | --- | --- | | `importId`required | path | `id` | — | ### Returns `data`: [`Import`](https://billingmart.com/developers/api/capture#object-Import) Example request Sample ``` curl -X GET "https://api.billingmart.com/v1/imports/{importId}" \ -H "Authorization: Bearer $BILLINGMART_API_KEY" ``` imports ## Discard DELETE `/v1/imports/{importId}` Discard an import that is not being written, and its uploaded file. What a finished import already created stays. A connection can discard only an import it created itself. Scope `clients:write` Effect Writes a draft or record MCP tool `discard_import` Retries No Idempotency-Key on this route ### Parameters Discard parameters | Name | In | Type | Notes | | --- | --- | --- | --- | | `importId`required | path | `id` | — | ### Returns `data`: [`Import`](https://billingmart.com/developers/api/capture#object-Import) Example request Sample ``` curl -X DELETE "https://api.billingmart.com/v1/imports/{importId}" \ -H "Authorization: Bearer $BILLINGMART_API_KEY" ``` Object ## TimeEntry Billable time. Minutes are an integer count, never a decimal of hours: 1.5 hours entered as a float and multiplied by a minor-unit rate is the same rounding bug as a float amount, one step removed. TimeEntry fields | Field | Type | Notes | | --- | --- | --- | | `id`required | `Id` | — | | `tenant`required | `TenantRef` | — | | `clientId`optional | `Id` | — | | `projectRef`optional | `string` | Untrusted text Carried by the API and MCP only inside the data envelope. | | `date`required | `DateOnly` | — | | `minutes`required | `integer` | Whole minutes worked on this date. | | `description`optional | `string` | Untrusted text — | | `userUid`optional | `Id` | Who logged it. Taken from the verified token. | | `billable`required | `boolean` | — | | `hourlyRate`optional | `Money` | Rate per whole hour, in minor units. The line total is derived as round(minutes * rate / 60) by the one totals module. | | `billed`optional | `boolean` | SERVER-WRITTEN. Set exactly once, inside the transaction that adds the rebilled line to an invoice, so the same hour cannot be billed twice. | | `billedInvoiceId`optional | `Id` | — | | `source`optional | `EntrySource` | How this record was made. Optional: absent with no `fieldSessionId` is manual. A client may write `manual` on create and never `tracked`; no client may change it. | | `fieldSessionId`optional | `Id` | SERVER-WRITTEN: the field session that made this tracked record. | | `override`optional | `EntryOverride` | SERVER-WRITTEN: set when a person shortened this tracked record. | | `createdAt`required | `IsoTimestamp` | — | | `updatedAt`optional | `IsoTimestamp` | — | | `jobId`optional | `Id` | The work record this belongs to, when it does. | Object ## Expense A cost incurred, optionally rebilled to a client with a markup. Markup is basis points, not a float multiplier. Expense fields | Field | Type | Notes | | --- | --- | --- | | `id`required | `Id` | — | | `tenant`required | `TenantRef` | — | | `clientId`optional | `Id` | — | | `projectRef`optional | `string` | Untrusted text Rendered as text; carried by the API and MCP only inside the data envelope. | | `date`required | `DateOnly` | — | | `category`optional | `string` | Untrusted text — | | `taxCategory`optional | `TaxCategoryCode` | The tax category a person confirmed (a code of the taxonomy). Never set by a suggestion alone. | | `taxCategoryVersion`optional | `string` | The taxonomy edition `taxCategory` is a code of, e.g. `us-sch-c-2026`. | | `taxCategorySource`optional | `TaxCategorySource` | — | | `taxScheduleCode`optional | `TaxCategoryCode` | The TOP category `taxCategory` resolved to when it was confirmed (itself for a top category; a sub-category's parent), so a later rename or move never rewrites a past report. | | `taxScheduleLine`optional | `string` | The form line of `taxScheduleCode` at confirmation, e.g. `Line 8`; absent where the category is not a line of the form. | | `vendor`optional | `string` | Untrusted text — | | `description`optional | `string` | Untrusted text — | | `amount`required | `Money` | What was actually spent. | | `taxTreatment`optional | `TaxTreatment` | — | | `markupBasisPoints`optional | `BasisPoints` | Applied when rebilling. 15% is 1500. The rebilled amount is round(amount * (10000 + markup) / 10000) — the rounding is explicit and testable, never implicit in a float. | | `receiptStoragePath`optional | `string` | Cloud Storage path. Content-type and size are enforced by storage.rules, not just the UI. | | `rebillable`required | `boolean` | — | | `billed`optional | `boolean` | SERVER-WRITTEN. Set exactly once, inside the transaction that adds the rebilled line. | | `billedInvoiceId`optional | `Id` | — | | `userUid`optional | `Id` | — | | `createdAt`required | `IsoTimestamp` | — | | `updatedAt`optional | `IsoTimestamp` | — | | `jobId`optional | `Id` | The work record this belongs to, when it does. | | `billId`optional | `Id` | The vendor bill this expense is already on, so a cost is counted once: an expense with a `billId` is left out of a record's costs, the bill carries it. | Object ## Mileage A trip, optionally rebilled. Distance is an integer count of thousandths of the declared unit — 12.4 miles is 12400 — for the same reason quantities and amounts are integers. A float distance times a minor-unit rate is a rounding bug with a delivery date. Mileage fields | Field | Type | Notes | | --- | --- | --- | | `id`required | `Id` | — | | `tenant`required | `TenantRef` | — | | `clientId`optional | `Id` | — | | `projectRef`optional | `string` | Untrusted text Rendered as text; carried by the API and MCP only inside the data envelope. | | `date`required | `DateOnly` | — | | `fromLocation`optional | `string` | Untrusted text — | | `toLocation`optional | `string` | Untrusted text — | | `purpose`optional | `string` | Untrusted text — | | `distanceMilli`required | `integer` | Thousandths of distanceUnit. 12.4 miles is 12400. | | `distanceUnit`required | `DistanceUnit` | — | | `ratePerUnit`optional | `Money` | Rate per one whole mile or kilometre. | | `rebillable`required | `boolean` | — | | `billed`optional | `boolean` | SERVER-WRITTEN. Set exactly once. | | `billedInvoiceId`optional | `Id` | — | | `userUid`optional | `Id` | — | | `source`optional | `EntrySource` | How this record was made. Optional: absent with no `fieldSessionId` is manual. A client may write `manual` on create and never `tracked`; no client may change it. | | `fieldSessionId`optional | `Id` | SERVER-WRITTEN: the field session that made this tracked record. | | `override`optional | `EntryOverride` | SERVER-WRITTEN: set when a person shortened this tracked record. | | `createdAt`required | `IsoTimestamp` | — | | `updatedAt`optional | `IsoTimestamp` | — | | `jobId`optional | `Id` | The work record this belongs to, when it does. | Object ## FieldSession One stretch of field work tracked from a phone — a running timer or a GPS trip — from Start to Stop. The phone sends points and photos; every number here is derived by the SERVER: the minutes from its own clock, the distance from the filtered points. FieldSession fields | Field | Type | Notes | | --- | --- | --- | | `id`required | `Id` | — | | `tenant`required | `TenantRef` | — | | `ownerUid`required | `Id` | The person who started it: the verified ID token's uid, never the payload's. One person has at most one running session. | | `startRequestId`optional | `Id` | The id the phone minted for this Start, so a repeated Start (a retry after a dropped connection) returns this session instead of making a second one. | | `kind`required | `FieldSessionKind` | — | | `status`required | `FieldSessionStatus` | — | | `clientId`optional | `Id` | The client the work is for, when the person picked one. | | `jobRef`optional | `JobRef` | The job the work is for, copied from the invoice's job the person picked (a job is not a document: it is `invoice.jobRef`). | | `projectRef`optional | `string` | Untrusted text Rendered as text; carried by the API and MCP only inside the data envelope. | | `startedAt`required | `IsoTimestamp` | SERVER CLOCK at Start. The phone's counter runs from this, so it is right after a relaunch and with the phone locked. | | `pausedAt`optional | `IsoTimestamp` | SERVER CLOCK when the person last paused; present only while status is paused, removed at Resume. | | `pausedMs`optional | `integer` | now). The phone shows startedAt + pausedMs as the counter origin; Stop subtracts it from the minutes. Absent means 0. | | `endedAt`optional | `IsoTimestamp` | SERVER CLOCK at Stop (or at Discard). | | `lastPointAt`optional | `IsoTimestamp` | When the server last accepted points for a trip. | | `minutes`optional | `integer` | Whole minutes between startedAt and endedAt, derived by the server at Stop. | | `distanceMilli`optional | `integer` | Thousandths of distanceUnit, derived by the server at Stop over the accuracy- and speed-filtered points. A client-supplied distance is ignored. | | `distanceUnit`optional | `DistanceUnit` | The unit the distance was written in: the workspace's, at Stop. | | `pointCount`optional | `integer` | How many points the server accepted (after dropping implausible and low-accuracy ones). | | `trackPolyline`optional | `string` | The compacted track as an encoded polyline, written at Stop and kept after the raw points are purged. A person's location history: the REST API and MCP mask it unless the credential may read contacts. | | `startPhotos`optional | `FieldPhoto[]` | Photos added at Start, in the order added. At most 20, enforced by the server. | | `endPhotos`optional | `FieldPhoto[]` | Photos added at Stop, in the order added. At most 20, enforced by the server. | | `timeEntryId`optional | `Id` | The unbilled time entry Stop wrote, when it wrote one. | | `mileageId`optional | `Id` | The unbilled mileage record Stop wrote, when it wrote one. | | `rawPointsExpireAt`optional | `IsoTimestamp` | SERVER-WRITTEN at Stop: 90 days later, the raw points in `chunks` are deleted and only `trackPolyline` stays. | | `createdAt`required | `IsoTimestamp` | — | | `updatedAt`required | `IsoTimestamp` | — | | `jobId`optional | `Id` | The work record this belongs to, when it does. FUNCTION-WRITTEN: set by the callables that check the record is in the same business, never by a payload alone. | Object ## OrganizerDoc One document the organizer is reading or has read: a photo or file a person added, what the model read on it, and the server's candidates for where it belongs. OrganizerDoc fields | Field | Type | Notes | | --- | --- | --- | | `id`required | `Id` | — | | `tenantId`required | `Id` | — | | `ownerUid`required | `Id` | Who added it: the verified ID token's uid, or the credential id for an API key. Its files live under this id. | | `createdBy`required | `WriteActor` | The credential that added it, stamped by the server: the member, or the API key or grant. The model is run under it, so the AI credits and the audit are that actor's. SERVER-ONLY: REST v1 never returns it. | | `subjectUid`optional | `Id` | For a grant (an MCP connection or the Agent): the member it acts for, read from the live grant. SERVER-ONLY: REST v1 never returns it. | | `businessId`optional | `Id` | The business it was added for, when the person had one chosen. Filing may name another the person may act in. | | `source`required | `enum` | Where it was added: the console, a conversation with the Agent, a phone's Capture tab, or REST v1 and MCP. | | `agentSessionId`optional | `Id` | The Agent conversation it was dropped into, when it was. | | `status`required | `enum` | awaitingUpload: created, its files not yet checked by the server. queued: `organize` accepted it. reading: the model is reading it. ready: read, candidates found. | | `file`required | `OrganizerFile` | The one photo or file: an image, or a PDF of at most 4 pages. A document of several pages arrives as one PDF, so a receipt or bill keeps its one original file when it is filed. | | `classification`optional | `OrganizerClassification` | — | | `candidates`optional | `OrganizerCandidates` | — | | `failure`optional | `OrganizerFailure` | — | | `filed`optional | `OrganizerFiled` | — | | `attempts`optional | `integer` | How many times it was sent to the model. | | `leaseExpiresAt`optional | `IsoTimestamp` | While `reading` or filing: when a stalled run may be taken over. | | `expiresAt`optional | `IsoTimestamp` | When the 7-day sweep removes it and its files. Present until it is filed, then deleted. | | `createdAt`required | `IsoTimestamp` | — | | `updatedAt`required | `IsoTimestamp` | — | Object ## Import One file a person (or an API client) is bringing clients, items or open invoices in from: which file, how its columns are mapped, what a dry run found, and how far the write has got. FUNCTION-WRITTEN ONLY: create, update and delete are `if false` for clients, and staff read is the only client access. Import fields | Field | Type | Notes | | --- | --- | --- | | `id`required | `Id` | — | | `tenantId`required | `Id` | — | | `ownerUid`required | `Id` | Who brought the file in: the verified ID token's uid, or the credential id for an API key. The upload lives under this id. | | `createdBy`required | `WriteActor` | The credential that created the import, stamped by the server. SERVER-ONLY: REST v1 never returns it. | | `businessId`optional | `Id` | The business the clients, items or invoices belong to. Required for items and invoices. | | `source`required | `enum` | Where the file came in: an upload from the console, or CSV text sent to REST v1 or MCP. | | `entity`required | `enum` | What the file holds. One kind per file; an invoice or bill file is one row per line item. | | `layout`required | `enum` | The export layout the headers matched, or `csv` for anything else. A known layout fixes the date format and pre-fills the mapping; the person still confirms it. | | `status`required | `enum` | mapping: read, waiting for a confirmed mapping. planned: dry run done, nothing written. running: the write pass has started and may be resumed. done: every row is accounted for. | | `fileName`required | `string` | Untrusted text The file's name as uploaded. | | `fileHash`required | `string` | SHA-256 of the file's bytes. The write pass refuses a plan whose file no longer hashes to this, and every record it writes has an id derived from it, so the same file imported twice is the same records. | | `fileBytes`required | `integer` | — | | `rowCount`required | `integer` | Data rows read, malformed ones included, blank ones not. | | `delimiter`optional | `enum` | The separator the file used, by name. | | `headers`required | `string[]` | — | | `sample`optional | `ImportSampleRow[]` | At most five rows of MASKED values: each value reduced to its shape (letters a, digits 9). What a model is shown, and all of it. | | `currency`optional | `Currency` | The one currency of the file's amounts. A file that mixes currencies is refused with its rows named. | | `dateLayout`optional | `enum` | How the file writes dates: year-month-day, month/day/year, or day/month/year. Fixed by the layout or chosen by a person, never guessed per row. | | `phoneCountry`optional | `CountryCode` | The country a phone number written without a country code belongs to, for a clients file: chosen by a person, else the business's own address country, else US. | | `mapping`optional | `ImportMappingEntry[]` | — | | `mappingSource`optional | `enum` | Where the mapping came from. Whatever it was, a person confirmed it before the plan ran. | | `aiProposalId`optional | `Id` | The aiProposals record of the model's mapping, when one was asked for. | | `planId`optional | `string` | A hash of the file hash, the confirmed mapping and the settings. The write pass runs exactly this plan or refuses. | | `counts`optional | `ImportCounts` | — | | `errors`optional | `ImportRowError[]` | The first rows that cannot be imported, each with its line. The count of all of them is `counts.errors`. | | `warnings`optional | `ImportWarning[]` | — | | `progress`optional | `ImportProgress` | — | | `createdAt`required | `IsoTimestamp` | — | | `updatedAt`required | `IsoTimestamp` | — | | `plannedAt`optional | `IsoTimestamp` | — | | `finishedAt`optional | `IsoTimestamp` | — | | `leaseExpiresAt`optional | `IsoTimestamp` | While the write pass is running: when its claim lapses. A second pass cannot start before this, and a pass that died is taken over after it. Never set once the import is done. | Next section ## Bills and vendors The other side of the ledger: vendors, the bills they send, and what has been paid against them. [Continue to Bills and vendors →](https://billingmart.com/developers/api/payables) ## Try it against your sandbox Create a bmk_test_ key in the console. It can only reach your sandbox workspace. [Start free](https://app.billingmart.com/sign-up) [All sections](https://billingmart.com/developers/api) --- This is the Markdown copy of https://billingmart.com/developers/api/capture. Live plans, prices and limits are published as JSON at https://billingmart.com/plans.json and https://billingmart.com/agent-info.json.