REST API

Sheets

Convert Google Sheets, XLSX or CSV into a credited XLSX workbook.

Convert links throughout a Google Sheet or uploaded workbook using your account's bearer token. Output is always .xlsx, including for CSV input. Links are replaced in place, with your account's credit and optional campaign and label as described in Links.

Google Sheet input

Submit a Google Sheets URL that NinjaBuy can read. Conversion reads every tab, not just the display preview, and freezes a snapshot for this job.

curl -sS -D sheet-headers.txt https://www.ninjabuy.com/api/v1/sheets \
  -H "Authorization: Bearer $NINJABUY_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  --data '{"url":"https://docs.google.com/spreadsheets/d/SHEET_ID/edit","campaign":"spring-haul","label":"finds"}'

XLSX or CSV input

First reserve an upload. filename must end in .xlsx or .csv and contain 1 to 255 characters after trimming. bytes is the exact file size in bytes, from 1 to 20,971,520.

curl -sS https://www.ninjabuy.com/api/v1/uploads \
  -H "Authorization: Bearer $NINJABUY_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  --data '{"filename":"finds.xlsx","bytes":12345}'

The 201 response contains upload_id, a signed PUT url, expires_at (Unix epoch milliseconds), max_bytes and request_id. The reservation expires after 15 minutes. PUT the raw file to the returned URL, then submit its upload id:

curl --fail-with-body -X PUT "$UPLOAD_URL" \
  --data-binary @finds.xlsx

curl -sS -D sheet-headers.txt https://www.ninjabuy.com/api/v1/sheets \
  -H "Authorization: Bearer $NINJABUY_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  --data '{"upload_id":"UPLOAD_ID","campaign":"spring-haul","label":"finds"}'

The direct PUT uses the storage service's response contract. NinjaBuy validates the uploaded bytes when you submit the conversion. Unknown or other accounts' upload ids return 404. The stored object must match the declared byte count; an object larger than declared or the maximum returns 413 upload-too-large, and a smaller (truncated) object returns 422 invalid-workbook with the byte-count mismatch explanation. These checks happen before downloading and also apply if the bytes change after the metadata check. Refused raw uploads are deleted, and accepted raw uploads are deleted once their validated snapshot is recorded.

Send exactly one source: url or upload_id. Both accept optional campaign and label; no async field is needed. Both POST routes accept Idempotency-Key and each consumes one slot of the shared 30-per-minute create window. Child link conversions consume no extra slots.

Job and download

A sheet submission returns 202 with Location: /api/v1/jobs/JOB_ID, Retry-After: 5 and this body:

{
  "job": {
    "id": "JOB_ID",
    "kind": "sheet_conversion",
    "status": "queued",
    "poll_after_seconds": 5
  },
  "request_id": "REQUEST_ID"
}

Poll Jobs with the same account's token until done or failed, respecting poll_after_seconds. A completed job.result has kind: "sheet_conversion", counts and a signed download_url valid for 15 minutes. Poll again for a fresh download signature. A failed job has error and no partial download.

For a Google Sheet, job.result may also include copy_url, a view-only link to an exact Google Sheets copy with your links in place; anyone with the link can keep their own with File → Make a copy. copy_notes lists what a reader of that copy should know, such as pictures made with =IMAGE() needing one Allow access click in their copy. download_url still returns the XLSX. copy_counts gives the same counts for the copy; when the XLSX cannot be made, the result keeps copy_url and has no download_url, and copy_notes says why. download_notes lists, line by line, what the XLSX cannot hold of the sheet, such as charts or a second link in a cell.

Snapshots and result files are retained for seven days after the job finishes or fails. After cleanup, the job remains readable with its status and counts, and job.result.download_url is omitted. Shared snapshots stay while another job still needs them. Abandoned raw uploads are deleted one hour after the upload reservation expires; a validated upload that never starts a job loses its snapshot seven days after reservation expiry. Cleanup runs hourly.

CountMeaning
links_foundDiscovered link occurrences, including repeated targets.
convertedOccurrences replaced with credited links.
left_by_hostCounts grouped by host for links intentionally left unchanged.
unresolvedOccurrences that could not be converted and remain unchanged.
unresolved_examplesExample unresolved URLs.
coverage_gapMissing conversion answers; always 0 in a produced file.

Coverage satisfies links_found = converted + sum(left_by_host) + unresolved. Social links, Google Sheets links, same-workbook navigation and existing NinjaBuy links stay unchanged. Every conversion candidate must receive an answer before output is written; missing answers fail the job.

Workbook preservation

The output preserves styles, merges, row and column sizes, and pictures as represented by the parsed grid. Rich-text runs survive in cells without a cell hyperlink; linked cells use one text label and hyperlink. It is a newly serialized workbook: charts, macros and other features outside that grid are not preserved.

XLSX supports one hyperlink per cell. The conversion replaces links in place instead of adding a second link beside them. A cell with two or more different non-picture link targets keeps the first as its hyperlink and lists the others in the cell's note, so no target is lost.

Limits

ResourceMaximum
Uploaded XLSX or CSV file20 MiB; must match the declared byte count
Expanded XLSX or combined Google source HTML64 MiB
Cells across the conversion500,000
Discovered link occurrences, including repeats100,000
Each picture10 MiB
Retained unique pictures combined32 MiB
Serialized XLSX output64 MiB
Upload reservation / download signature15 minutes each

Exceeding a parser or writer bound fails explicitly. See Errors for invalid-workbook, upload-expired and upload-too-large.