ProdgatorDocs
SecurityUpload reports

Upload with the API

Upload a SARIF, CycloneDX or SPDX report with an API key in three calls: create, PUT, complete.

Availability

Team plan and above. Uploading needs the or role, or a custom role with .

From the API

You need an API key with the scope and a plan with API access. An upload takes three calls: create it, PUT the file, then complete it.

1. Create the upload with the file's name, size in bytes and SHA-256 (64 lowercase hex characters):

FILE=results.sarif
SIZE=$(wc -c < "$FILE" | tr -d ' ')
SHA256=$(sha256sum "$FILE" | cut -d' ' -f1)

curl -s -X POST https://api.prodgator.io/v1/security/uploads \
  -H "Authorization: Bearer $PRODGATOR_API_KEY" \
  -H "Content-Type: application/json" \
  -d "{\"filename\": \"$FILE\", \"size\": $SIZE, \"sha256\": \"$SHA256\", \"format\": \"sarif\",
       \"repository\": \"acme/api\", \"category\": \"codeql-js\", \"gitRef\": \"main\", \"commitSha\": \"$GITHUB_SHA\"}"
FieldRequiredNotes
Yes1 to 200 characters; only the base name is kept
YesExact size in bytes
YesHex SHA-256 of the file
Yes
No, or . Needed when the name is on more than one of your providers (see Same name on more than one provider)
NoThe provider's repository id: GitHub repository id, GitLab project id, Bitbucket repository UUID or Azure Repos repository GUID. Needs
No (default), , or . A file that does not match the given format fails. and are refused: send SARIF
NoLetters, digits and , up to 100 characters
NoBranch or tag the report was made from. Empty means the tracked branch
No7 to 40 hex characters
No opens findings whatever the branch; never does

The response is . holds a , (), the to send and (15 minutes). If the same file was already uploaded for the same repository, category and branch, is and nothing new is counted.

2. PUT the file to with exactly the headers in ( and ). The URL only accepts a file of the declared size and checksum:

curl -s -X PUT "$PUT_URL" \
  -H "Content-Type: application/json" \
  -H "x-amz-checksum-sha256: $CHECKSUM_HEADER" \
  --data-binary "@$FILE"

is the value from (the base64 form of the SHA-256).

3. Complete the upload to start reading it:

curl -s -X POST https://api.prodgator.io/v1/security/uploads/$UPLOAD_ID/complete \
  -H "Authorization: Bearer $PRODGATOR_API_KEY"

Prodgator checks the stored file's size and checksum. A mismatch deletes the file and fails the upload. Check progress with : moves from to to (or , with ), and gives the findings read.

Same name on more than one provider

Prodgator keeps findings per repository, and a repository is its provider plus the id the provider gives it (GitHub repository id, GitLab project id, Bitbucket repository UUID, Azure Repos repository GUID), not its name. on GitHub and on GitLab are two repositories: their findings, tracked branches and release policy counts never mix.

An upload that names only the repository is matched to a provider like this:

  • If exactly one of your providers has a repository with that name (or a linked account named like its owner), the report is for that repository.
  • If more than one does, the upload fails with . Send (and if you have it) to say which one.
  • If none does, the report is kept under the name alone until Prodgator sees the repository.

Reports sent by the Prodgator report action always carry the repository id from the run's OIDC token. A filter on the findings API follows the same rule: add (and optionally ) when the name is on more than one provider.

A complete script

Outside GitHub Actions, create an API key with the scope and upload the SARIF file in three calls (create, PUT, complete). This script needs , and :

FILE=trivy.sarif
SIZE=$(wc -c < "$FILE" | tr -d ' ')
SHA256=$(sha256sum "$FILE" | cut -d' ' -f1)
API=https://api.prodgator.io/v1/security/uploads

CREATED=$(curl -sf -X POST "$API" \
  -H "Authorization: Bearer $PRODGATOR_API_KEY" -H "Content-Type: application/json" \
  -d "{\"filename\": \"$FILE\", \"size\": $SIZE, \"sha256\": \"$SHA256\", \"format\": \"sarif\",
       \"repository\": \"acme/api\", \"category\": \"trivy\", \"gitRef\": \"main\", \"commitSha\": \"$COMMIT_SHA\"}")
UPLOAD_ID=$(echo "$CREATED" | jq -r '.data.upload.id')
PUT_URL=$(echo "$CREATED" | jq -r '.data.put.url // empty')

if [ -n "$PUT_URL" ]; then
  curl -sf -X PUT "$PUT_URL" \
    -H "Content-Type: $(echo "$CREATED" | jq -r '.data.put.headers["Content-Type"]')" \
    -H "x-amz-checksum-sha256: $(echo "$CREATED" | jq -r '.data.put.headers["x-amz-checksum-sha256"]')" \
    --data-binary "@$FILE"
  curl -sf -X POST "$API/$UPLOAD_ID/complete" -H "Authorization: Bearer $PRODGATOR_API_KEY"
fi

An empty means the same file was already uploaded for this repository, category and branch, and nothing new is counted. The fields and the status check are described above.

On this page