Build with the API
Files, versions and reports
Upload, list, download and share files; add versions, compare them and read reports.
Everything you can do with files over HTTP: upload, list, download, share, keep versions, compare them and read quality reports. The ideas are explained in the dashboard guides (Files and uploads, Versions and comparing, Quality reports, Sharing and access). This page is about the calls.
Upload, list and download
What you can upload
| Format | Notes |
|---|---|
| CSV | Any delimiter (comma, semicolon, tab) is detected for you. UTF-8, with or without a byte-order mark. |
| XLSX | Excel's current format. The first sheet that has any rows is read. |
| XLS | Excel's older format. It is stored and can be downloaded, but it is not profiled: its report says unsupported. |
Files can be up to 25 MB. An empty file is refused, and so is anything that is not a spreadsheet.
Upload a file
/filesNeeds the files:write scope when you use an API key.filefilerequired- The spreadsheet, sent as
multipart/form-data. visibilitycompany | restrictedoptional- Who may see it. Defaults to
company: everyone in your company. See Sharing and access. grantedUserIdsstring[]optional- With
restricted: the colleagues who may also see it. You and every admin always can. Repeat the field, or send a JSON array.
curl https://gridline-data-analysis-app.duckdns.org/api/files \
-H "Authorization: Bearer gl_live_YOUR_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-F "file=@sales-q3.csv" \
-F "visibility=restricted" \
-F "grantedUserIds=USER_ID"The answer is 201 with the file. Its quality report is already queued: see Quality reports.
When you are over your quota
Each plan includes a number of files per billing period. Past it, what happens depends on the plan:
- Free and Basic refuse the upload with
402 Payment Required. The message names your plan, how many you have uploaded, and the date the quota resets. - Premium accepts it and charges for the extra file. The response carries an
X-Gridline-Quota-Warningheader that says so.
A refused or failed upload uses none of your quota. Two uploads racing for the last slot are handled one after the other, so you can never exceed the limit by accident.
Find your files
/filesNeeds files:read.The list shows each file once, as its newest version, newest upload first. It returns only files you are allowed to see. Long lists use a cursor: see Pagination.
limit1–100optional- How many per page. Default 20.
cursorstringoptional- From
meta.nextCursorof the previous page. sort-createdAt | createdAtoptional- Newest first (default) or oldest first.
mimeTypestringoptional- Only CSV, XLS or XLSX files.
visibilitycompany | restrictedoptional- Only files shared this way.
uploaderIduuidoptional- Only what one person uploaded.
uploadedAfter / uploadedBeforeISO date-timeoptional- A time window: after (inclusive) and before (exclusive).
allVersionsbooleanoptionaltruelists every version you can see, not just the newest of each file.
/files/{id}One file. A file you may not see is a 404.Download a file
/files/{id}/downloadGridline never serves your file through the API. It checks that you may see it, then hands you a short-lived link to fetch it directly.
{
"url": "https://…",
"expiresAt": "2026-10-02T09:19:03.000Z"
}The link works for 5 minutes and needs no Authorization header. Ask again for a fresh one.
Change who can see a file
/files/{id}Uploader or admin. Needs files:write.curl -X PATCH https://gridline-data-analysis-app.duckdns.org/api/files/FILE_ID \
-H "Authorization: Bearer gl_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{ "visibility": "restricted", "grantedUserIds": ["USER_ID"] }'grantedUserIds replaces the list of people who have access; it does not add to it. Switching to company clears it. New people on the list are notified that a file was shared with them.
Delete a file
/files/{id}Uploader or admin. Needs files:write.Deleting removes the file from every list and download. Two things to know:
- It does not give back the upload to your quota: the upload happened.
- If you delete the newest version of a file, the version before it becomes the newest again. Version numbers are never reused.
What can go wrong
| You see | It means |
|---|---|
400 | The file is not a CSV, XLS or XLSX, is empty, or has a field the endpoint does not accept. |
402 | You are over your plan's file quota, or the company has not chosen a plan yet. |
404 | The file does not exist, or you may not see it. Gridline does not say which. |
413 | The file is over 25 MB. |
429 | Your company has used its requests for this minute. See Rate limits. |
Sharing
Hidden means not found
If you ask for a file you may not see, Gridline answers:
{ "statusCode": 404, "message": "File not found" }The same answer is given for a file that does not exist. That is deliberate: even saying "forbidden" would tell you that a file with that id exists. You only get 403 Forbidden when you can see a file but are not allowed to change it (you are not its uploader or an admin).
Share a file with specific people
Set visibility to restricted when you upload, or change it later with PATCH /files/{id}. Pick colleagues with GET /companies/me/members, which returns just id and fullName for the active people in your company: enough to choose from, nothing more. It is for someone signed in with a session; an API key cannot list colleagues, so a program has to be given the ids.
# Who can I share with? (signed in as a person)
curl https://gridline-data-analysis-app.duckdns.org/api/companies/me/members \
-H "Authorization: Bearer ACCESS_TOKEN"
# Share only with them
curl -X PATCH https://gridline-data-analysis-app.duckdns.org/api/files/FILE_ID \
-H "Authorization: Bearer gl_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{ "visibility": "restricted", "grantedUserIds": ["USER_ID"] }'- People you list must be active members of your company.
- Only the uploader and admins can see who a file is shared with (
grantedUserIds), and only when they ask for that single file. - Each person added is notified. The person who shared is not.
- Sharing a file never gives anyone any other file, and mentioning someone in a comment never gives them access.
Versions
A new version starts with the same access as the file it joins, plus the person who uploaded it. After that, each version's access is its own.
Versions and comparing
Add a version
/files/{id}/versionsUploader or admin. Needs files:write.Send the new file in a field called file, exactly as for a normal upload. There are no other fields: a version takes its access from the file it joins.
curl https://gridline-data-analysis-app.duckdns.org/api/files/FILE_ID/versions \
-H "Authorization: Bearer gl_live_YOUR_KEY" \
-F "file=@sales-q4.csv"Use the id of any version of the file you want to add to. The answer is the new version: version is the next number, and isLatest is true.
What a version is
- A real upload. It counts toward your file quota and gets its own quality report.
- Numbered 1, 2, 3 and so on. A number is never reused, even if a version is deleted.
- Shared like the file it joins. It takes the file's visibility and its list of people, and also the person who uploaded it, so an admin adding version 2 never locks out the employee who uploaded version 1. Colleagues are not notified again.
- Limited by plan. Free keeps up to 5 versions of a file, Basic 50, Premium any number. Past the limit you get
409with the way forward: delete an old version or upgrade.
See every version
/files/{id}/versionsNeeds files:read.Lists the versions of the file that you may see, newest first. Pass ?page= and ?limit= to move through them. In GET /files, add allVersions=true to list versions alongside other files.
Compare two versions
/files/{id}/compare/{otherId}Needs files:read.Gridline compares the stored reports of the two versions, so nothing is read again and it answers immediately. Put the earlier version first.
curl https://gridline-data-analysis-app.duckdns.org/api/files/VERSION_1_ID/compare/VERSION_2_ID \
-H "Authorization: Bearer gl_live_YOUR_KEY"{
"from": { "fileId": "…", "version": 1, "originalName": "sales-q3.csv" },
"to": { "fileId": "…", "version": 2, "originalName": "sales-q4.csv" },
"columnsAdded": ["region"],
"columnsRemoved": [],
"typeChanges": [{ "column": "amount", "from": "integer", "to": "string" }],
"nullPercentChanges": [{ "column": "email", "from": 1.2, "to": 9.8, "delta": 8.6 }],
"rowCount": { "from": 1204, "to": 1311, "delta": 107 },
"columnCount": { "from": 6, "to": 7, "delta": 1 },
"duplicateRows": { "from": 3, "to": 3, "delta": 0 },
"qualityScore": { "from": 92, "to": 81, "delta": -11 },
"schemaChanged": true
}columnsAdded / columnsRemovedstring[]optional- Matched by name, ignoring case. A renamed header is one removed and one added.
typeChangesarrayoptional- Columns whose dominant type changed, such as numbers that became text. A column that is empty on either side has no type to compare.
nullPercentChangesarrayoptional- Columns whose share of empty cells moved by 5 percentage points or more.
rowCount, columnCount, duplicateRows, qualityScorefrom / to / deltaoptional- How each number moved.
schemaChangedbooleanoptionaltruewhen a column was removed or changed type: the changes that break whatever reads the data. New columns and extra blanks do not count.
When a comparison cannot be made
| Answer | Why |
|---|---|
404 | You may not see one of the files. |
422 | The files are not versions of the same file, or one report failed or is unsupported (an old .xls). |
409 | One report is still being built. Ask again in a moment. |
Quality reports
Get a report
/files/{id}/reportNeeds files:read. Same access rule as the file.Reports are built in the background, usually in a few seconds, so check status first:
| status | Meaning |
|---|---|
queued | Saved and waiting its turn. |
profiling | Being read right now. |
ready | Done. metrics, narrative and qualityScore are filled in. |
failed | The file could not be read (for example it is corrupt). errorMessage says why. Reading the same bytes again would not help, so it will not retry. |
unsupported | A legacy .xls file. It is stored and downloadable, but not profiled. |
Rather than asking repeatedly, you can watch it live or get a webhook when it is ready.
What is measured
A ready report has these numbers for the whole file:
rowCountnumberoptional- Data rows, not counting the header.
columnCountnumberoptional- Columns in the header.
emptyRowsnumberoptional- Rows with nothing in any column.
duplicateRowsnumberoptional- Rows identical to an earlier row.
raggedRowsnumberoptional- Rows with more or fewer cells than the header.
headerIssuesstring[]optional- Problems with the header row: blank or repeated column names.
truncatedbooleanoptionaltruewhen the file was longer than Gridline profiles in one go. The numbers then cover the first 100,000 rows and 200 columns.
And these for each column:
namestringoptional- The header.
nullCount, nullPercentnumberoptional- How many cells are empty, and what percent of the column that is. A cell with only spaces counts as empty.
inferredTypestringoptional- What most values are:
integer,number,boolean,date,string, oremptyfor a column with nothing in it. typeCountsobjectoptional- How many cells of each kind there were.
inconsistent, inconsistentPercentboolean, numberoptional- Whether the column mixes kinds (numbers and text, say), and what percent of its values disagree with the dominant kind.
numericobject | nulloptional- For a numeric column: its
min,maxandmean.
The quality score
qualityScore is a number from 0 to 100 that says how the file did against your rules. It is the share of the rules that applied to this file and passed. A failed error rule counts twice as much as a failed warning. If no rule applied (you have none, or none matched this file) the score is null, because there is nothing to score against.
Each report lists one result per rule in ruleResults: passed, failed or skipped, with a sentence saying what was found and what was required. See Quality rules.
The plain-language summary
When it is available, narrative holds a short summary and a few recommendations, written by an AI model, and says which model wrote it. It is optional: if no model is configured, or it produces nothing usable, narrative is null and everything else in the report is unaffected.
Preview the first rows
/files/{id}/previewNeeds files:read.Returns the first 50 rows and 50 columns of the file, so you can glance at it without downloading. Long cells are cut at 200 characters and dates come back as ISO strings. The preview is saved when the report is built, so asking for it never reads the file again.
{
"columns": [{ "index": 0, "name": "order_id" }, { "index": 1, "name": "amount" }],
"rows": [["A-1001", 49.9], ["A-1002", 12]],
"totalRows": 1204,
"truncated": true
}It answers 409 while the report is still being built, and 422 (with the reason) if the report failed or the file is unsupported.
Check a file against today's rules
/files/{id}/report/rebuildUploader or admin. Needs files:write.A report remembers the rules as they were when it was built, so changing a rule never rewrites an old report. After you change your rules, rebuild a report to check that file against the new ones.
curl -X POST https://gridline-data-analysis-app.duckdns.org/api/files/FILE_ID/report/rebuild \
-H "Authorization: Bearer gl_live_YOUR_KEY"The report goes back to queued and then ready. If one is already queued or running, you get 409: it cannot be queued twice.