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:
| Field | Type | Required | Description |
|---|---|---|---|
file | File | Yes | The CSV (.csv) or JSON (.json) file to upload |
schemaType | String | Yes | The 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
| Status | Description |
|---|---|
pending | Upload received, waiting to start processing |
validating | File is being validated against schema |
processing | Data is being transformed and imported |
completed | All rows processed successfully |
partial | Processing completed with some row failures |
failed | Processing 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:
- 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"}
]
- 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"}
]
}
- 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:
| Column | Type | Description |
|---|---|---|
transaction_id | String | Unique transaction identifier |
amount | Decimal | Transaction amount (positive value) |
date | Date | Transaction date (YYYY-MM-DD format) |
store_id | String | Store identifier matching Piing store ID |
Optional Columns:
| Column | Type | Description |
|---|---|---|
customer_id | String | Customer identifier |
payment_method | String | Payment type (cash, card, etc.) |
items | Integer | Number of items |
tax | Decimal | Tax amount |
discount | Decimal | Discount amount |
Error Handling
Error Response Format
All errors follow this format:
{
"error": "error_code",
"error_description": "Human-readable error message"
}
Authentication Errors
| Status | Error Code | Description |
|---|---|---|
| 400 | invalid_request | Missing client_id or client_secret |
| 401 | invalid_credentials | Invalid client_id or client_secret |
| 403 | credential_revoked | Your credentials have been revoked |
Upload Errors
| Status | Error Code | Description |
|---|---|---|
| 400 | invalid_request | Missing required fields |
| 400 | invalid_file | File is not a valid CSV or JSON |
| 400 | invalid_schema | Schema not found or not enabled for your organization |
| 400 | validation_failed | Pre-flight validation found critical errors |
| 401 | unauthorized | Missing or invalid access token |
| 409 | duplicate_upload | Idempotency key already used |
| 413 | file_too_large | File 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
| Endpoint | Limit |
|---|---|
/auth/token | 10 requests per minute per client_id |
/upload | 60 requests per hour per organization |
/upload/:id/status | 120 requests per minute per organization |
/schemas | 60 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