The Minute7 API lets you pull your account's time and expense data as reports, and bulk-load reference data into your account from CSV. It is a JSON HTTP API for Minute7 customers building integrations against their own data.
Every request is authenticated and scoped to a single Minute7 account: it only ever accesses data belonging to that account, never another. The reporting endpoints return JSON and can also stream CSV, XLSX, and PDF downloads; the import endpoints accept CSV uploads. If you are just getting started, read Authentication and Conventions first, then use the contents below to jump to the section you need.
There are two separate authentication mechanisms. The reporting endpoints use a per-user login token (described first). The bulk data-import endpoints use a separate account token (described last). They are not interchangeable.
The reporting endpoints authenticate with a per-user login token (a JWT). You obtain it by logging in, or a user with API access can copy it from My Settings in the Minute7 web app (it appears there as the user's TOKEN).
Send a POST to the login endpoint with the credentials nested under a
user
key:
POST https://www.minute7.com/api/login
Content-Type: application/json
{ "user": { "email": "you@example.com", "password": "your-password" } }
On success the response contains a
logininfo
object. The token is the value at
logininfo.session.session_id
(the companion
logininfo.session.session_name
is the literal string
IMPRESARIO).
The same JWT is also returned in the
Authorization
response header. The
logininfo
object also carries
AccountUser
and
Account
siblings alongside
session
— read the token specifically from
logininfo.session.session_id,
not from the body as a whole. A success response looks like:
{"logininfo":{"session":{"session_name":"IMPRESARIO","session_id":"<jwt>"},"AccountUser":{...},"Account":{...}}}
On invalid credentials the response is
{"errors":[{"session":"Invalid credentials"}]}
with HTTP 401; a locked account returns
{"errors":[{"session":"User account locked"}]}
with HTTP 403. Login is rate limited per IP address (up to 10 attempts per IP per 10 minutes); exceeding it returns HTTP 429 (see Conventions > Rate limiting).
There are two accepted ways to present the token; both are accepted interchangeably (the
IMPRESARIO
parameter is rewritten into the
Authorization
header before the request is authenticated).
Authorization: Bearer <token>
IMPRESARIO
parameter, sent either in the query string or in the request body — e.g.
?IMPRESARIO=<token>
The
IMPRESARIO
parameter exists for clients (such as the Minute7 web front-end) that cannot easily set request headers, and is fully supported. As a best practice, prefer the
Authorization: Bearer
header where you can — it keeps the token out of URLs, query strings, and browser history.
Example read call using the header:
curl -H 'Authorization: Bearer <token>' \ 'https://www.minute7.com/api/reports/time_json?report_customer_id=&report_employee_id=&report_qb_class_id=&report_inventory_item_id=&report_payroll_item_id='
The same call using the parameter form:
https://www.minute7.com/api/reports/time_json?IMPRESARIO=<token>&report_customer_id=&report_employee_id=&report_qb_class_id=&report_inventory_item_id=&report_payroll_item_id=
The bulk CSV import and ID-map endpoints (see "Data Import" below) use a separate, account-scoped token. There is no self-service screen for this token — Minute7 issues it for your account on request, so
contact support
to obtain one. Once you have it, send it in the
Authorization
header in either of these forms:
Authorization: Bearer <your-token> Authorization: Token token=<your-token>
Both are accepted and behave identically; the
Bearer
form is the simpler of the two. This token is separate from the login token, and the
IMPRESARIO
query parameter is not accepted here.
Each token belongs to exactly one account; everything imported under it is attributed to that account. Tokens are named and can be deactivated, and only active tokens authenticate. Tokens are stored hashed — the raw value is shown once when the token is created, so treat it like a password.
Holding a valid token is not always sufficient — the calling user must also be permitted to use the API. Account administrators have API access by default; other users are granted it under their permission settings.
The report endpoints in particular require that the user either manages the account or has the "communicate with API" permission enabled, and report results are limited to the employees that user is allowed to see. An administrator configures this per user: allow the user to view reports, choose which employees they may view, and allow them to communicate with the API.
https://www.minute7.com
application/json.
Export endpoints instead stream a file (CSV, XLSX, PDF, or ZIP) as a download.
id
field (and id-typed fields) are rendered as strings, not numbers.
created
(its creation time) and
modified
(its last-update time). The two report endpoints (time and expense JSON reports) are an exception — see their Response sections.
"Yes"
or
"No".
start_date
and
end_date)
as
yyyy-mm-dd.
{"errors":[ ... ]};
the import and ID-map endpoints instead use a single
{"error":"..."}
string.
Retry-After
header indicating how many seconds to wait, and
X-RateLimit-Limit
X-RateLimit-Remaining
X-RateLimit-Reset
headers; the body is
{"error":"Rate limit exceeded. Please try again later.", ...}.
Honor
Retry-After
and back off.
GET https://www.minute7.com/api/reports/time_json
Returns the account's time entries as JSON, filtered by date range and any of several optional filters. Requires account-management rights or the "communicate with API" permission; results are limited to the employees the calling user may view.
Note: the
report_*
filter parameters must be present on the request (they may be sent empty). Sending an empty value applies no filter for that dimension; omitting the parameter entirely results in a 400.
"true" to activate the date range below.filter_by_date="true".filter_by_date="true"._v (e.g. 12_v); bare ids are employees."billable" or "all"."taxable" or "all"."synced" or "all"."locked" or "all"."approved" or "all".audit_log (see "Optional attributes" below); unknown values are ignored.{"time_data": [ entry, ... ]}.
Each entry has these attributes (this report uses a dedicated field set;
uuid
is the only id-like field, and there are no
created
modified
timestamps here):
include)audit_log
— Returned when include=audit_log is passed. An array of every logged action on the entry, in chronological order (oldest first). Empty array ([]) for entries with no audit history. Each event is an object with three fields:
action
— string identifying the type of action. Common values: add (creation), edit (content change), delete, approve, disapprove, locked, billable, non billable, taxable, non taxable, sync (QuickBooks sync). Strings are emitted as stored — filter and interpret on your side.
timestamp
— ISO-8601 UTC timestamp of when the action occurred, e.g. "2026-05-15T18:42:31.123Z".
actor
— Identifier of the user who performed the action, as a string. For regular customer users, the format is "email (Full Name)" — e.g. "jane.doe@acme.example (Jane Doe)". Email is included so consumers can disambiguate same-named users and correlate with their own systems. Two sentinel values to be aware of: "<deleted>" if the user account has been removed since the action was logged, and "Minute7 Support" for any action performed by Minute7 internal staff (their personal email and name are masked to protect employee privacy).
action == "add" to find the creation event — its timestamp is when the entry was first entered (useful for late-submission auditing — compare against the entry's date field).
max(timestamp) across the array to detect "anything changed since my last pull" (useful for incremental sync).
action in ['edit', 'delete'] before taking the max. Actions like sync, approve, locked, billable are administrative-state changes that don't alter what the entry represents — exclude them if you don't want sync events to look like edits.
[]. Entries created via QuickBooks sync / bulk imports may have only a sync event with no add.
GET https://www.minute7.com/api/reports/time_csv , https://www.minute7.com/api/reports/time_xlsx , https://www.minute7.com/api/reports/time_pdf
Streams the same filtered time entries as a file download. The CSV and XLSX exports take an
export_time_fields
parameter that chooses and orders the columns; the PDF export does not. The downloaded file is named after your account (e.g. <account name>_time_export.csv). These exports require only authentication; they accept the same date and
report_*
filter parameters as the JSON report.
uuid, date, employee_id, employee_number, customer_id, inventory_item_id, qb_class_id, payroll_item_id, payrate, billrate, description, duration, approved, billable, taxable, exported, locked, type, start_time, end_time, hours_off_duty, billable_cost, note.POST https://www.minute7.com/api/reports/segment_time
Aggregates the filtered time entries into a nested tree of duration sums, grouped by one or more dimensions.
customer_id, employee_id, inventory_item_id, payroll_item_id, qb_class_id, vendor_id. employee_id splits internally into employee vs. vendor sub-groups (vendor keys suffixed _v). Pass ["no_segment"] for an ungrouped total.{"SegmentedTime": { ... }}
— a nested object keyed by segment id (vendor keys suffixed _v). Each node has
sum
(total duration for the group) and
label
(the record's name, or "None selected"); deeper segments nest as additional keys.
GET https://www.minute7.com/api/reports/expenses
Returns the account's expense entries as JSON, filtered by date range and optional filters. Requires account-management rights or the "communicate with API" permission. As with the time report, the
report_*
filter parameters must be present (they may be empty).
"true" to activate the date range.filter_by_date="true"."with_attachment" or "all"."reimbursable" or "credit_card" are honored."billable"/"all"."synced"/"all"."locked"/"all"."approved"/"all".{"expense_data": [ entry, ... ]}.
Each entry has these attributes (like the time report, this uses a dedicated field set;
uuid
is the only id-like field, and there are no
created
modified
timestamps here). Note that
attachments
on this report is a single URL string (the first attachment's location), or an empty string when there are none:
GET https://www.minute7.com/api/reports/expenses_csv , https://www.minute7.com/api/reports/expenses_xlsx , https://www.minute7.com/api/reports/expenses_pdf
Streams the same filtered expense entries as a file download. CSV and XLSX take an
export_expense_fields
parameter selecting the columns; PDF does not. The file is named after your account (e.g. <account name>_expenses_export.csv). The same date and filter parameters as the expense JSON report apply.
uuid, date, vendor_id, customer_id, qb_class_id, expenseable_id, expense_grouping_id, description, attachments, miles, reimbursement_rate, amount, approved, billable, exported, locked, expense_type, note, payment_account_name.report_* filters as the expense JSON report : optional.GET https://www.minute7.com/api/reports/download_expense_attachments
Bundles the receipt/attachment files of the filtered expense entries into a single ZIP and streams it as a download (filename <account name>_attachment_export.zip). Accepts the same date and filter parameters as the expense report.
{"errors":[ ... ]}.
POST https://www.minute7.com/api/reports/segment_expenses
Aggregates the filtered expense entries into a nested tree of amount sums, grouped by one or more dimensions.
customer_id, vendor_id, qb_class_id, qb_account_id, expense_grouping_id, inventory_item_id, expenseable_id. Pass ["no_segment"] for an ungrouped total.report_* filters as the expense JSON report : optional (must be present, may be empty).{"SegmentedExpenses": { ... }}
— a nested object keyed by segment id; each node has
sum
(total amount) and
label,
with deeper segments nested as additional keys.
The import endpoints bulk-load your reference data (customers, vendors, employees, items, payroll items, QuickBooks accounts and classes) into your account from CSV. The ID-map endpoint remaps the external ids stored on your records — useful when you move your data to a different accounting system. All of these use the
account token
described under Authentication —
Authorization: Bearer <your-token>
— not the login token. The column names below follow Minute7's QuickBooks-based field naming; map your source data into the columns shown for each list type.
Send the CSV one of two ways: as a multipart form upload in a
file
field, or as the raw CSV in the request body with
Content-Type: text/csv.
The first row must be a header row; column order does not matter.
You may include any subset of an importer's columns — missing columns are simply not set. Any column whose name is
not
one of the importer's recognized headers causes the whole request to be rejected (see Response). Each import also accepts an optional
inactivate_missing
form/query parameter (default
false) — see "Removing records" below.
list_id
— string. The record's external id and the key the import matches on: the QuickBooks ListID for records that originated in QuickBooks, or any unique string of your choosing for new records. A row updates the existing record in your account that has this list_id, or creates a new one if none matches. Always include it — a row without a list_id cannot be matched reliably.
is_active
— string. Y marks the record active. Any other value (or blank) marks it inactive (hidden) in your account.
Each row is an
upsert
keyed on
list_id,
scoped to the token's account: existing records are updated in place, new
list_id
values are created. Records are never deleted by an import.
Pass
inactivate_missing=true
to treat the upload as the full set: after applying the rows, any currently-active record of that type in your account whose
list_id
is
not
present in the file is marked inactive (hidden). With the default
false,
records absent from the file are left untouched.
Imports run asynchronously. A well-formed request returns
202 Accepted
immediately and the rows are applied by a background job; there is no status endpoint to poll. This means only
request-level
problems are reported in the response (a missing or malformed CSV, or an unrecognized column — all
400). Problems with individual rows during processing are recorded on the server, not returned to you. Confirm the result by reading it back through the
reports
or in the Minute7 web app.
Some records reference others by
list_id,
so import the referenced records first. In particular, import
payroll items
before
employees
(an employee row's
payroll_item_list_ids
are resolved against payroll items already in your account).
A minimal customers upload (raw text/csv body):
list_id,is_active,full_name,bill_address_city,bill_address_state 80000001-1A2B3C4D,Y,Acme Corporation,Portland,OR
Each endpoint below accepts the two common columns above plus the entity-specific columns listed with it.
POST
https://www.minute7.com/api/v1/import/accounts
— your QuickBooks chart of accounts. full_name is used as the name when present, otherwise name.
POST https://www.minute7.com/api/v1/import/classes — your QuickBooks classes.
POST
https://www.minute7.com/api/v1/import/customers
— customers. full_name populates both the customer name and company name.
POST
https://www.minute7.com/api/v1/import/employees
— employees. print_as becomes the employee's display name. payroll_item_list_ids is a comma-separated list of payroll-item list_ids and is resolved against payroll items already imported into your account.
POST https://www.minute7.com/api/v1/import/inventory-items — inventory items.
POST
https://www.minute7.com/api/v1/import/payroll-items
— payroll items. wage_type accepts one of: None, Bonus, Commission, HourlyOvertime, HourlyRegular, HourlySick, HourlyVacation, SalaryRegular, SalarySick, SalaryVacation, RegularPay, OverTimePay, LeavePay.
POST https://www.minute7.com/api/v1/import/service-items — service items.
POST
https://www.minute7.com/api/v1/import/vendors
— vendors. name_on_check sets the print-as name; is_vendor_eligible_for1099 = Y flags the vendor as 1099-eligible.
{"status":"processing"}
— the import has been queued.
{"error":"No CSV data provided"}
(no file or body),
{"error":"Invalid CSV format: ..."}
(the CSV could not be parsed), or
{"error":"Invalid CSV headers"}
(the file contains a column the importer does not recognize).
POST https://www.minute7.com/api/v1/idmap
Updates the external id stored on existing records (their
list_id)
when the system those ids come from changes. It is intended for accounts that have moved off QuickBooks to another accounting system that Minute7 does not sync with directly, and need their Minute7 records to carry the new system's identifiers — for example, after switching from QuickBooks to Sage 100, remapping each record from its old QuickBooks id to its new id.
For each row it finds the record of the given
type
in your account whose current
list_id
equals
old_id,
and changes that record's
list_id
to
new_id.
Supply the CSV as a multipart
file
upload or a raw
text/csv
body.
type
— string. Which record set to update. One of accounts, classes, customers, employees, payroll-items, inventory-items, service-items, vendors (note the hyphens on the last three).
old_id
— string. The record's current list_id.
new_id
— string. The list_id to set.
Rows with any blank field are skipped.
type,old_id,new_id customers,80000001-OLD,80000045-NEW vendors,80000010-OLD,80000099-NEW
{"status":"processing"}
— the remapping has been queued.
{"error":"Invalid CSV headers. Required: type, old_id, new_id"}
(when those columns are not all present),
{"error":"No CSV data provided"},
or
{"error":"Invalid CSV format: ..."}.
The remapping runs in the background, so as with imports, a row whose
old_id
matches no record is recorded server-side and not returned in the response.
Minute7 synchronizes time and expense data with QuickBooks Online and QuickBooks Desktop. This synchronization is handled within the Minute7 application itself and is not part of the integration API described here; there are no integration endpoints for driving or configuring it.