logo

Contents

POS Integration API

A robust API for managing pharmacy product inventory - improving inventory data quality, accuracy and pharmacy operations.

Base URL

https://ingest.medaki.ca

Authentication

All requests require API key authentication via headers:

  • X-API-Key: Your API key
  • X-Signature: HMAC SHA-256 signature of the request payload

Signature Generation

The signature is generated using HMAC SHA-256 with your secret key:

const payload = JSON.stringify(requestBody);
const signature = hmacSHA256(payload, secretKey);

Endpoints

Health Check

Check the API and database health status.

GET /health

Response:

{
  "status": 200,
  "success": true,
  "message": "ok",
  "data": {
    "database": "healthy",
    "timestamp": "2024-03-15T10:30:00.000Z"
  },
  "elapsed_ms": 45
}

Get Products

Retrieve products with pagination and filtering options.

GET /products

Query Parameters:

  • pharmacy_id (string, required): Pharmacy identifier
  • gtin (string, optional): Filter by specific GTIN
  • skip (number, optional): Number of records to skip for pagination (default: 0)

Response:

{
  "status": 200,
  "success": true,
  "data": [
    {
      "id": 1,
      "gtin": "1234567890123",
      "name": "Product Name",
      "description": "Product description",
      "price": 19.99,
      "stock": 100
    }
  ],
  "pagination": {
    "count": 1000,
    "has_more": true,
    "cursor": "next_page_token"
  },
  "elapsed_ms": 234
}

Create/Update Products (Asynchronous)

Queue products for creation or update. Returns immediately with a request ID for tracking.

POST /products

Request Body:

{
  "pharmacy_id": "pharmacy123",
  "medaki_pharmacy_id": "medaki456",
  "action": "create",
  "products": [
    {
      "gtin": "1234567890123",
      "name": "Product Name",
      "description": "Product description",
      "brand": "Brand Name",
      "old_name": "Previous Product Name",
      "old_gtin": "0987654321098",
      "category": "Category",
      "image_url": "https://example.com/image.jpg",
      "stock": 100,
      "price": 19.99,
      "sale_price": 15.99,
      "distributor": "Distributor Name"
    }
  ]
}

Response:

{
  "status": 202,
  "success": true,
  "data": {
    "message": "Product ingest queued for processing",
    "requestId": "12345678",
    "totalProducts": 1500,
    "chunks": 3,
    "statusUrl": "/operations/12345678/status"
  },
  "elapsed_ms": 45,
  "request_id": "12345678"
}

Check Operation Status

Monitor the progress of asynchronous product operations.

GET /operations/{requestId}/status

Response:

{
  "status": 200,
  "success": true,
  "data": {
    "requestId": "12345678",
    "status": "processing",
    "totalChunks": 3,
    "completedChunks": 2,
    "totalProducts": 1500,
    "processedProducts": 1000,
    "failedProducts": 0,
    "progress": 67,
    "startTime": 1647329400000,
    "lastUpdate": 1647329430000
  }
}

Status Values:

  • queued: Operation queued but not yet started
  • processing: Currently processing chunks
  • completed: All chunks processed successfully
  • failed: Operation failed with errors

Product Schema

API Input Fields (Client Request)

Required for Create:

  • gtin: Global Trade Item Number (string)
  • name: Product name (string)
  • brand: Product brand (string)
  • stock: Stock quantity (number)
  • price: Product price (number)
  • sale_price: Sale price (number)

Required for Update:

  • gtin: Global Trade Item Number (string)

Optional Fields:

  • old_gtin: Previous GTIN for updates (string)
  • old_name: Previous name for updates (string)
  • description: Product description (string)
  • category: Product category (string)
  • image_url: Product image URL (string)
  • distributor: Distributor name (string)

Get Machine Operations

Retrieve recent operations for the authenticated machine.

GET /operations/machine

Query Parameters:

  • limit (number, optional): Number of operations to return (1-100, default: 50)

Response:

{
  "status": 200,
  "success": true,
  "data": {
    "machine_id": "b0586d19-4c05-4543-9bc8-ac9232a3f566",
    "operations": [
      {
        "request_id": "12345678",
        "status": "completed",
        "total_chunks": 3,
        "completed_chunks": 3,
        "total_products": 1500,
        "processed_products": 1500,
        "failed_products": 0,
        "start_time": 1647329400000,
        "last_update": 1647329430000,
        "end_time": 1647329450000,
        "error": null,
        "progress": 100
      }
    ],
    "total": 1,
    "limit": 50
  },
  "elapsed_ms": 45,
  "request_id": "87654321"
}

Retry Failed Operation

Manually retry a failed operation by re-queuing failed chunks.

POST /operations/{requestId}/retry

Response:

{
  "status": 200,
  "success": true,
  "data": {
    "message": "Operation retry initiated successfully",
    "request_id": "12345678",
    "failed_chunks_retried": 2,
    "retry_attempt": 1
  },
  "elapsed_ms": 45,
  "request_id": "12345678"
}

Error Responses

Authentication Error

{
  "status": 401,
  "success": false,
  "error": "Invalid API key",
  "request_id": "12345678"
}

Validation Error

{
  "status": 400,
  "success": false,
  "error": "Validation failed: gtin is required",
  "request_id": "12345678"
}

Duplicate GTIN Error

{
  "status": 400,
  "success": false,
  "error": "Duplicate GTINs found in payload: 1234567890123, 9876543210987",
  "request_id": "12345678"
}

Operation Not Found

{
  "status": 404,
  "success": false,
  "error": "Operation not found",
  "request_id": "12345678"
}

Database Timeout Error

{
  "status": 408,
  "success": false,
  "error": "Database operation timed out",
  "timeout_ms": 12000,
  "request_id": "12345678"
}

Request Too Large

{
  "status": 413,
  "success": false,
  "error": "Request too large. Maximum size allowed: 10485760 bytes (10 MB)",
  "provided_size": 15728640,
  "max_size": 10485760,
  "request_id": "12345678"
}

Database Unavailable

{
  "status": 503,
  "success": false,
  "error": "Database is currently unavailable",
  "request_id": "12345678"
}

Server Error

{
  "status": 500,
  "success": false,
  "error": "Internal server error",
  "request_id": "12345678"
}

Rate Limits & Constraints

  • Maximum request size: 10 MB
  • Maximum products per request: 25,000
  • No timeout limits (asynchronous processing)
  • Immediate response with tracking capabilities

Workflow Example

# 1. Submit large batch
POST /products
→ 202 Accepted { requestId: "abc123", chunks: 10 }

# 2. Monitor progress
GET /operations/abc123/status
→ { status: "processing", progress: 60 }

# 3. Check completion
GET /operations/abc123/status
→ { status: "completed", processedProducts: 5000 }

Performance Notes

  • Chunk processing: ~2-5 seconds per 500 products
  • Queue throughput: Up to 3 concurrent chunks per operation
  • Database timeouts: 12 seconds with automatic retry
  • Status updates: Real-time
  • Retry attempts: Up to 3 retries with exponential backoff and dead letter queue backup

Error Handling & Retry Logic

The API implements intelligent error classification and retry logic:

  • Timeout Errors: Automatically retried with 2x longer delays
  • Connection Errors: Retried with exponential backoff
  • Authentication Errors: Not retried (permanent failure)
  • Validation Errors: Not retried (permanent failure)
  • Database Constraint Errors: Not retried (permanent failure)

Code Examples

JavaScript/Node.js Authentication

const crypto = require('crypto');

async function sha256Hash(data, key) {
    const encoder = new TextEncoder();
    const encodedData = encoder.encode(data);
    const encodedKey = encoder.encode(key);
    const cryptoKey = await crypto.subtle.importKey('raw', encodedKey, { name: 'HMAC', hash: 'SHA-256' }, false, ['sign']);
    const signature = await crypto.subtle.sign('HMAC', cryptoKey, encodedData);
    return Array.from(new Uint8Array(signature))
        .map((byte) => byte.toString(16).padStart(2, '0'))
        .join('');
}

const body = JSON.stringify({
  pharmacy_id: "123",
  medaki_pharmacy_id: "med123",
  action: "create",
  products: [/* product data */]
});

const signature = await sha256Hash(body, secretKey);

const headers = {
  'X-API-Key': publicKey,
  'X-Signature': signature,
  'Content-Type': 'application/json'
};

cURL Examples

# Health Check
curl -X GET https://ingest.medaki.ca/health

# Get Products
curl -X GET \
  "https://ingest.medaki.ca/?pharmacy_id=123&skip=0" \
  -H "X-API-Key: your_public_key" \
  -H "X-Signature: generated_signature"

# Create Products (Async)
curl -X POST \
  https://ingest.medaki.ca/ \
  -H "Content-Type: application/json" \
  -H "X-API-Key: your_public_key" \
  -H "X-Signature: generated_signature" \
  -d '{
    "pharmacy_id": "123",
    "medaki_pharmacy_id": "med123",
    "action": "create",
    "products": [
      {
        "gtin": "1234567890123",
        "name": "Product Name",
        "brand": "Brand Name",
        "stock": 100,
        "price": 29.99,
        "sale_price": 24.99
      }
    ]
  }'

# Check Operation Status
curl -X GET \
  "https://ingest.medaki.ca/operations/12345678/status" \
  -H "X-API-Key: your_public_key" \
  -H "X-Signature: generated_signature"

# Get Machine Operations
curl -X GET \
  "https://ingest.medaki.ca/operations/machine?limit=10" \
  -H "X-API-Key: your_public_key" \
  -H "X-Signature: generated_signature"

# Retry Failed Operation
curl -X POST \
  "https://ingest.medaki.ca/operations/12345678/retry" \
  -H "X-API-Key: your_public_key" \
  -H "X-Signature: generated_signature"