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

FormatNotes
CSVAny delimiter (comma, semicolon, tab) is detected for you. UTF-8, with or without a byte-order mark.
XLSXExcel's current format. The first sheet that has any rows is read.
XLSExcel'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

POST/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-Warning header 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

GET/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.nextCursor of 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).
allVersionsbooleanoptional
true lists every version you can see, not just the newest of each file.
GET/files/{id}One file. A file you may not see is a 404.

Download a file

GET/files/{id}/download

Gridline 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.

Response
{
  "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

PATCH/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

DELETE/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 seeIt means
400The file is not a CSV, XLS or XLSX, is empty, or has a field the endpoint does not accept.
402You are over your plan's file quota, or the company has not chosen a plan yet.
404The file does not exist, or you may not see it. Gridline does not say which.
413The file is over 25 MB.
429Your 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:

Response
{ "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

POST/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 409 with the way forward: delete an old version or upgrade.

See every version

GET/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

GET/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"
Response
{
  "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.
schemaChangedbooleanoptional
true when 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

AnswerWhy
404You may not see one of the files.
422The files are not versions of the same file, or one report failed or is unsupported (an old .xls).
409One report is still being built. Ask again in a moment.

Quality reports

Get a report

GET/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:

statusMeaning
queuedSaved and waiting its turn.
profilingBeing read right now.
readyDone. metrics, narrative and qualityScore are filled in.
failedThe 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.
unsupportedA 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.
truncatedbooleanoptional
true when 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, or empty for 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, max and mean.

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

GET/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.

Response
{
  "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

POST/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.