CSV exports¶
Six lists download as a CSV file: leads, contacts, accounts, deals, tickets and invoices. Each is a
GET under the list's own root, served by a subclass of RecordExportView
(backend/common/csv_export.py). See Conventions for the query-string rules the
lists share and Errors for the shapes a refusal comes back in.
| Endpoint | View | File name | Rows come from |
|---|---|---|---|
GET /api/leads/export/ |
LeadExportView (leads/views/export_views.py) |
leads-YYYY-MM-DD.csv |
lead_list_queryset (leads/views/lead_views.py) |
GET /api/contacts/export/ |
ContactExportView (contacts/export_views.py) |
contacts-YYYY-MM-DD.csv |
contact_list_queryset (contacts/views.py) |
GET /api/accounts/export/ |
AccountExportView (accounts/export_views.py) |
accounts-YYYY-MM-DD.csv |
account_list_queryset (accounts/views.py) |
GET /api/opportunities/export/ |
DealExportView (opportunity/views/export_views.py) |
deals-YYYY-MM-DD.csv |
deal_list_queryset (opportunity/views/opportunity_views.py) |
GET /api/cases/export/ |
CaseExportView (cases/export_views.py) |
tickets-YYYY-MM-DD.csv |
case_list_queryset (cases/views.py) |
GET /api/invoices/export/ |
InvoiceExportView (invoices/export_views.py) |
invoices-YYYY-MM-DD.csv |
filter_invoices over visible_invoices_qs (invoices/api_views.py) |
The date in the file name is the day in the org's timezone (export_filename).
Who may export, and which rows¶
Any member of the org may export (permission_classes = (IsAuthenticated, HasOrgContext)), and
gets exactly the rows the matching list would show them for the same query string, every page of
it. That holds because each export's get_queryset calls the function its list view builds its
queryset with, the one named in the table above, and never filters on its own. So the read rule
that hides a colleague's lead from a member on GET /api/leads/ hides it from their file too, and
a ticket the caller cannot open is not in tickets-*.csv.
For personal access tokens and the organization API key the export is part of the list's resource:
GET /api/leads/export/ needs leads:read, the same scope as GET /api/leads/
(see Tokens and API keys).
Query parameters¶
An export takes its list's query parameters, with the list's meaning, and ignores paging: limit
and offset do not narrow a file, which always holds every matching row. For example,
GET /api/cases/export/?status=New&status=Pending&priority=High writes every high-priority ticket
in New or Pending that the caller can see. The parameters each list reads are listed on its own page
(Leads, Contacts, Accounts,
Opportunities, Cases, Invoices and estimates), and
are the same set a saved view may hold.
Two lists answer in halves that a file does not have, so the half is named in the query instead:
- Leads: the list returns open and closed leads separately.
?open=trueexports only the open ones, which is what the web list shows; without it both halves are written. - Accounts and contacts:
?is_active=trueor?is_active=falseexports one half. Without it, active and inactive rows are both written.
A malformed filter value (an id that is not a UUID, a day that does not exist, a number that is not
a number) is a 400 naming the parameter, returned before any byte of the file:
The response¶
A 200 is a streamed text/csv; charset=utf-8 body with
Content-Disposition: attachment; filename="<prefix>-<YYYY-MM-DD>.csv". Send Accept: text/csv
or no Accept at all; both are served (CSV_RENDERERS, common/renderers.py). The file:
- starts with a UTF-8 byte-order mark, so Excel reads accented names correctly;
- has one header row, then one row per record, in the list's own order;
- writes people as their email address and several values in one cell as a
;-separated list (owners, tags, linked accounts); - writes dates as
YYYY-MM-DDand timestamps as ISO 8601 in the org's timezone; - prefixes any text cell that starts with
=,+,-,@, a tab or a carriage return (also after leading spaces) with', so a record named=HYPERLINK(...)is text in a spreadsheet rather than a formula (safe_cell). Numbers the server formats are written as they are.
The body is generated while it streams, after the request's middleware has finished, so the view
sets the org's row-level security context and timezone again for exactly as long as it runs
(csv_response).
Columns¶
| File | Columns |
|---|---|
| Leads | ID, First name, Last name, Company, Job title, Email, Phone, Status, Source, Rating, Industry, Amount, Currency, Probability, Close date, Last contacted, Next follow-up, City, Country, Assigned to, Tags, Created |
| Contacts | ID, First name, Last name, Email, Phone, Title, Department, Organization, Account, Linked accounts, City, State, Country, Do not call, Active, Assigned to, Tags, Created |
| Accounts | ID, Name, Industry, Email, Phone, Website, City, State, Country, Employees, Annual revenue, Currency, Active, Assigned to, Tags, Created |
| Deals | ID, Name, Account, Pipeline, Stage, Amount, Currency, Probability, Close date, Type, Lead source, Assigned to, Tags, Created |
| Tickets | ID, Subject, Status, Priority, Type, Account, Assigned to, Tags, Created, First response, Resolved, Closed on |
| Invoices | ID, Number, Title, Status, Account, Client name, Client email, Issue date, Due date, Total, Paid, Due, Currency, Assigned to, Created |
Choice fields are written as their labels (In Process, not in process), and a deal's stage as
the label of that stage in the deal's own pipeline. The column lists are the columns method of
each view; those are the source of truth if this table and a file ever disagree.