Skip to content
Last updated

Quickstart

This walkthrough validates authentication, catalog access, file upload, and result delivery in sandbox.

1. Prepare credentials

This quickstart uses the source-IP-allowlist path. Obtain the sandbox API token, confirm the calling IP is allowlisted, and configure a public HTTPS webhook endpoint with Percents. To use mTLS instead, complete the mTLS quickstart and use https://mtls.test.percents.com as the base URL.

export PERCENTS_API_BASE='https://sandbox.percents.com'
export PERCENTS_API_TOKEN='tok_your_id:api_your_secret'

2. Read merchants and offers

curl --fail-with-body \
  --header "Authorization: token ${PERCENTS_API_TOKEN}" \
  "${PERCENTS_API_BASE}/api/v1/omni/merchant"

curl --fail-with-body \
  --header "Authorization: token ${PERCENTS_API_TOKEN}" \
  "${PERCENTS_API_BASE}/api/v1/omni/offer"

Persist merchant and offer UUIDs exactly as returned. Do not infer access to a merchant from its name.

3. Request a native upload URL

curl --fail-with-body --get \
  --header "Authorization: token ${PERCENTS_API_TOKEN}" \
  --data-urlencode 'content=percents-txn-stream' \
  --data-urlencode 'fileType=csv' \
  --data-urlencode 'fileName=transactions-2026-10-01-001.csv' \
  "${PERCENTS_API_BASE}/api/fileUpload"

The response contains a presigned URL that expires after 15 minutes:

{
  "url": "https://object-storage.example/presigned-upload",
  "fileId": "fu_77777777-7777-4777-8777-777777777777",
  "expiresAt": "2026-10-01T12:15:00.000Z",
  "headers": {}
}

4. Upload the file

Create the CSV using the native row contract. Send every header returned in the upload response; Content-Type is optional for native CSV. Use a unique filename for each reservation, including retries after a failed or expired file.

curl --fail-with-body \
  --request PUT \
  --data-binary @transactions.csv \
  'PRESIGNED_URL_FROM_THE_PREVIOUS_RESPONSE'

Do not send the Percents API token to the presigned URL. The URL itself grants temporary permission to upload that single object.

5. Receive the result

Return a successful 2xx response after validating the webhook signature and durably recording the event. Process each webhookId idempotently.

Wait for the signed FileUploadProcessingResult webhook or poll GET /api/v1/omni/files/{fileId} with your API token. Successful sandbox validation reports processingStatus=validation_only and validationStatus=passed. Failed validation reports processingStatus=failed and validationStatus=failed. Production ingests valid files automatically and reports ingestion completion/failure. Neither result is final approval of individual transactions, rewards, or invoices.