Skip to main content

Piing Data Upload API

Overview

The Piing Data Upload API allows you to programmatically upload transaction and analytics data to the Piing platform. The API supports both CSV and JSON file formats and uses a secure client credentials authentication flow with various data schemas for different point-of-sale systems.

Base URL: https://api.piing.ai (production) | https://api-staging.piing.ai (staging)

Supported File Formats: CSV (.csv) and JSON (.json)


Quick Start

1. Get Your Credentials

Contact your Piing administrator to obtain your API credentials:

  • client_id - Your unique client identifier (e.g., piing_abc123...)
  • client_secret - Your secret key (shown only once during creation)

2. Authenticate

curl -X POST https://api.piing.ai/api/external/auth/token \
-H "Content-Type: application/json" \
-d '{
"client_id": "your_client_id",
"client_secret": "your_client_secret"
}'

3. Upload a File

CSV:

curl -X POST https://api.piing.ai/api/external/upload \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-F "file=@transactions.csv" \
-F "schemaType=generic_transactions"

JSON:

curl -X POST https://api.piing.ai/api/external/upload \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-F "file=@transactions.json" \
-F "schemaType=generic_transactions"

4. Check Status

curl https://api.piing.ai/api/external/upload/UPLOAD_ID/status \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"

Authentication

POST /api/external/auth/token

Authenticate with your client credentials to receive an access token.

Request:

{
"client_id": "piing_abc123def456...",
"client_secret": "your_secret_key"
}

Response (200 OK):

{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "Bearer",
"expires_in": 3600,
"expires_at": "2026-01-07T15:00:00.000Z"
}

Token Usage: Include the token in all subsequent requests:

Authorization: Bearer YOUR_ACCESS_TOKEN

Token Expiration: Tokens expire after the time specified in expires_in (default: 1 hour). Request a new token before expiration to maintain uninterrupted access.


Uploading Files

POST /api/external/upload

Upload a CSV or JSON file for processing. The file type is automatically detected based on the file extension.

Headers:

Authorization: Bearer YOUR_ACCESS_TOKEN
Content-Type: multipart/form-data

Form Fields:

FieldTypeRequiredDescription
fileFileYesThe CSV (.csv) or JSON (.json) file to upload
schemaTypeStringYesThe data schema name (see Available Schemas)

Example Request (CSV):

curl -X POST https://api.piing.ai/api/external/upload \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-F "file=@/path/to/transactions.csv" \
-F "schemaType=generic_transactions" \

Example Request (JSON):

curl -X POST https://api.piing.ai/api/external/upload \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-F "file=@/path/to/transactions.json" \
-F "schemaType=generic_transactions" \

Response (202 Accepted):

{
"uploadId": "550e8400-e29b-41d4-a716-446655440000",
"status": "pending",
"message": "File uploaded successfully, processing started",
"validationSummary": {
"totalRows": 1500,
"preflightValidatedRows": 100,
"preflightErrors": []
}
}

Response with Warnings (202 Accepted):

{
"uploadId": "550e8400-e29b-41d4-a716-446655440000",
"status": "pending",
"message": "File uploaded with warnings, processing started",
"validationSummary": {
"totalRows": 1500,
"preflightValidatedRows": 100,
"preflightErrors": [
{"row": 45, "field": "amount", "error": "Invalid number format"}
]
}
}

Checking Upload Status

GET /api/external/upload/:uploadId/status

Check the processing status of an upload.

Example Request:

curl https://api.piing.ai/api/external/upload/550e8400-e29b-41d4-a716-446655440000/status \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"

Response - Processing:

{
"uploadId": "550e8400-e29b-41d4-a716-446655440000",
"status": "processing",
"originalFilename": "transactions_2026_01.csv",
"schemaType": "generic_transactions",
"fileSizeBytes": 1048576,
"rowCount": 1500,
"processedRowCount": 750,
"failedRowCount": 0,
"createdAt": "2026-01-07T12:00:00.000Z",
"processingStartedAt": "2026-01-07T12:00:05.000Z"
}

Response - Completed:

{
"uploadId": "550e8400-e29b-41d4-a716-446655440000",
"status": "completed",
"originalFilename": "transactions_2026_01.csv",
"schemaType": "generic_transactions",
"fileSizeBytes": 1048576,
"rowCount": 1500,
"processedRowCount": 1500,
"failedRowCount": 0,
"createdAt": "2026-01-07T12:00:00.000Z",
"processingStartedAt": "2026-01-07T12:00:05.000Z",
"processingCompletedAt": "2026-01-07T12:01:30.000Z"
}

Response - Partial Success:

{
"uploadId": "550e8400-e29b-41d4-a716-446655440000",
"status": "partial",
"originalFilename": "transactions_2026_01.csv",
"schemaType": "generic_transactions",
"fileSizeBytes": 1048576,
"rowCount": 1500,
"processedRowCount": 1400,
"failedRowCount": 100,
"errorSummary": {
"totalErrors": 100,
"errorTypes": {
"invalid_amount": 45,
"missing_required_field": 30,
"invalid_date": 25
}
},
"createdAt": "2026-01-07T12:00:00.000Z",
"processingStartedAt": "2026-01-07T12:00:05.000Z",
"processingCompletedAt": "2026-01-07T12:01:30.000Z"
}

Upload Statuses

StatusDescription
pendingUpload received, waiting to start processing
validatingFile is being validated against schema
processingData is being transformed and imported
completedAll rows processed successfully
partialProcessing completed with some row failures
failedProcessing failed completely

Available Schemas

GET /api/external/schemas

List the data schemas available for your organization.

Example Request:

curl https://api.piing.ai/api/external/schemas \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"

Response:

{
"schemas": [
{
"name": "generic_transactions",
"displayName": "Generic Transactions",
"description": "Standard transaction data format",
"requiredColumns": ["transaction_id", "amount", "date", "store_id"],
"optionalColumns": ["customer_id", "payment_method", "items", "tax", "discount"]
},
{
"name": "square_pos",
"displayName": "Square POS",
"description": "Transaction data from Square point of sale systems",
"requiredColumns": ["transaction_id", "gross_sales", "created_at", "location_id"],
"optionalColumns": ["customer_id", "tender_type", "net_sales", "tax"]
}
]
}

File Formats

General Requirements

  • Encoding: UTF-8
  • Maximum File Size: 100 MB
  • Maximum Rows: 1,000,000

CSV Format

  • Delimiter: Comma (,)
  • Header Row: Required (first row must contain column names)
  • Quoted Fields: Supported for values containing commas

Example CSV:

transaction_id,amount,date,store_id,customer_id,payment_method,items
TXN001,125.50,2026-01-07,store_123,cust_456,card,3
TXN002,45.00,2026-01-07,store_123,,cash,1
TXN003,89.99,2026-01-07,store_789,cust_789,card,2

JSON Format

JSON files must contain an array of objects. The API also supports wrapper objects with common data property names.

Supported JSON Structures:

  1. Array of objects (recommended):
[
{"transaction_id": "TXN001", "amount": 125.50, "date": "2026-01-07", "store_id": "store_123"},
{"transaction_id": "TXN002", "amount": 45.00, "date": "2026-01-07", "store_id": "store_123"}
]
  1. Object with data array:
{
"data": [
{"transaction_id": "TXN001", "amount": 125.50, "date": "2026-01-07", "store_id": "store_123"},
{"transaction_id": "TXN002", "amount": 45.00, "date": "2026-01-07", "store_id": "store_123"}
]
}
  1. Object with records/items/rows array:
{
"records": [
{"transaction_id": "TXN001", "amount": 125.50}
]
}

Nested Objects:

Nested objects are flattened using dot notation:

{"customer": {"id": "C001", "name": "John"}, "amount": 100}

Becomes columns: customer.id, customer.name, amount

Arrays in Values:

Arrays are JSON-stringified:

{"items": ["apple", "banana"], "total": 50}

The items column will contain: ["apple","banana"]


Schema Columns: Generic Transactions

Required Columns:

ColumnTypeDescription
transaction_idStringUnique transaction identifier
amountDecimalTransaction amount (positive value)
dateDateTransaction date (YYYY-MM-DD format)
store_idStringStore identifier matching Piing store ID

Optional Columns:

ColumnTypeDescription
customer_idStringCustomer identifier
payment_methodStringPayment type (cash, card, etc.)
itemsIntegerNumber of items
taxDecimalTax amount
discountDecimalDiscount amount

Error Handling

Error Response Format

All errors follow this format:

{
"error": "error_code",
"error_description": "Human-readable error message"
}

Authentication Errors

StatusError CodeDescription
400invalid_requestMissing client_id or client_secret
401invalid_credentialsInvalid client_id or client_secret
403credential_revokedYour credentials have been revoked

Upload Errors

StatusError CodeDescription
400invalid_requestMissing required fields
400invalid_fileFile is not a valid CSV or JSON
400invalid_schemaSchema not found or not enabled for your organization
400validation_failedPre-flight validation found critical errors
401unauthorizedMissing or invalid access token
409duplicate_uploadIdempotency key already used
413file_too_largeFile exceeds 100 MB limit

Validation Error Response

When pre-flight validation fails:

{
"error": "validation_failed",
"error_description": "Pre-flight validation failed",
"validationErrors": [
{"row": 1, "error": "Missing required column: transaction_id"},
{"row": 5, "field": "amount", "error": "Invalid number format: 'abc'"},
{"row": 12, "field": "date", "error": "Invalid date format, expected YYYY-MM-DD"}
]
}

Code Examples

Python

import requests

# Configuration
BASE_URL = "https://api.piing.ai"
CLIENT_ID = "your_client_id"
CLIENT_SECRET = "your_client_secret"

# Step 1: Authenticate
auth_response = requests.post(
f"{BASE_URL}/api/external/auth/token",
json={"client_id": CLIENT_ID, "client_secret": CLIENT_SECRET}
)
token = auth_response.json()["access_token"]

# Step 2: Upload file
headers = {"Authorization": f"Bearer {token}"}
with open("transactions.csv", "rb") as f:
upload_response = requests.post(
f"{BASE_URL}/api/external/upload",
headers=headers,
files={"file": f},
data={"schemaType": "generic_transactions"}
)

upload_id = upload_response.json()["uploadId"]
print(f"Upload started: {upload_id}")

# Step 3: Poll for status
import time
while True:
status_response = requests.get(
f"{BASE_URL}/api/external/upload/{upload_id}/status",
headers=headers
)
status = status_response.json()["status"]
print(f"Status: {status}")

if status in ["completed", "partial", "failed"]:
break
time.sleep(5)

print(f"Final result: {status_response.json()}")

Node.js

const axios = require('axios');
const FormData = require('form-data');
const fs = require('fs');

const BASE_URL = 'https://api.piing.ai';
const CLIENT_ID = 'your_client_id';
const CLIENT_SECRET = 'your_client_secret';

async function uploadTransactions(filePath) {
// Step 1: Authenticate
const authResponse = await axios.post(`${BASE_URL}/api/external/auth/token`, {
client_id: CLIENT_ID,
client_secret: CLIENT_SECRET
});
const token = authResponse.data.access_token;

// Step 2: Upload file
const form = new FormData();
form.append('file', fs.createReadStream(filePath));
form.append('schemaType', 'generic_transactions');

const uploadResponse = await axios.post(
`${BASE_URL}/api/external/upload`,
form,
{
headers: {
...form.getHeaders(),
'Authorization': `Bearer ${token}`
}
}
);

const uploadId = uploadResponse.data.uploadId;
console.log(`Upload started: ${uploadId}`);

// Step 3: Poll for status
let status = 'pending';
while (!['completed', 'partial', 'failed'].includes(status)) {
await new Promise(resolve => setTimeout(resolve, 5000));

const statusResponse = await axios.get(
`${BASE_URL}/api/external/upload/${uploadId}/status`,
{ headers: { 'Authorization': `Bearer ${token}` } }
);

status = statusResponse.data.status;
console.log(`Status: ${status}`);
}

return status;
}

uploadTransactions('./transactions.csv')
.then(result => console.log('Done:', result))
.catch(err => console.error('Error:', err.message));

cURL (Bash Script)

#!/bin/bash

BASE_URL="https://api.piing.ai"
CLIENT_ID="your_client_id"
CLIENT_SECRET="your_client_secret"
FILE_PATH="./transactions.csv"

# Step 1: Authenticate
TOKEN=$(curl -s -X POST "$BASE_URL/api/external/auth/token" \
-H "Content-Type: application/json" \
-d "{\"client_id\":\"$CLIENT_ID\",\"client_secret\":\"$CLIENT_SECRET\"}" \
| jq -r '.access_token')

echo "Token obtained"

# Step 2: Upload file
UPLOAD_RESPONSE=$(curl -s -X POST "$BASE_URL/api/external/upload" \
-H "Authorization: Bearer $TOKEN" \
-F "file=@$FILE_PATH" \
-F "schemaType=generic_transactions")

UPLOAD_ID=$(echo $UPLOAD_RESPONSE | jq -r '.uploadId')
echo "Upload started: $UPLOAD_ID"

# Step 3: Poll for status
while true; do
STATUS=$(curl -s "$BASE_URL/api/external/upload/$UPLOAD_ID/status" \
-H "Authorization: Bearer $TOKEN" \
| jq -r '.status')

echo "Status: $STATUS"

if [[ "$STATUS" == "completed" || "$STATUS" == "partial" || "$STATUS" == "failed" ]]; then
break
fi

sleep 5
done

echo "Upload complete with status: $STATUS"

Best Practices

1. Token Management

  • Cache tokens and reuse until near expiration
  • Implement automatic token refresh before expiration
  • Never log or expose tokens in error messages

2. Error Handling

  • Implement exponential backoff for retries
  • Use idempotency keys for all uploads
  • Log upload IDs for troubleshooting

3. File Preparation

  • Validate CSV/JSON format before uploading
  • Remove BOM characters from UTF-8 files
  • Ensure date formats match schema requirements
  • For JSON: Use arrays of flat objects when possible for best performance

4. Monitoring

  • Poll status endpoint at reasonable intervals (5-10 seconds)
  • Set up alerts for failed uploads
  • Track processing times for SLA compliance

Rate Limits

EndpointLimit
/auth/token10 requests per minute per client_id
/upload60 requests per hour per organization
/upload/:id/status120 requests per minute per organization
/schemas60 requests per minute per organization

When rate limited, you'll receive a 429 Too Many Requests response. Implement exponential backoff and retry after the time specified in the Retry-After header.


Support

For API support, contact your Piing administrator or email support@piing.ai with:

  • Your organization ID
  • Upload ID (for upload-related issues)
  • Error messages received
  • Timestamp of the issue