POS Integration API

A Cloudflare Workers API for managing pharmacy product inventory with authentication and batch processing capabilities

Base URL

https://positec-dev.curly-mode-08ce.workers.dev

Authentication

All API requests (except health check) require authentication using API keys and HMAC SHA-256 signature verification.

Headers Required

X-API-Key: {your_public_key}
X-Signature: {hmac_sha256_signature}
X-Timestamp: {unix_timestamp}
Content-Type: application/json
Content-Length: {request_body_length}

HMAC Signature

Generate HMAC SHA-256 signature using your secret key and the payload

Example payload for signing:

{"pharmacy_id":"123", "medaki_pharmacy_id":"med123", "action":"create","products":[...]}

Endpoints

GET /health

Health check endpoint that verifies database connectivity.

Authentication: Not required

Response:

{
  "status": 200,
  "success": true,
  "message": "ok",
  "data": {
    "database": "healthy",
    "timestamp": "2023-12-01T10:00:00.000Z"
  },
  "elapsed_ms": 45
}

Error Response (503):

{
  "status": 503,
  "success": false,
  "message": "Database unhealthy",
  "error": "Connection timeout",
  "elapsed_ms": 5000
}

GET /

Retrieve products with pagination and filtering.

Authentication: Required

Query Parameters:

  • pharmacy_id (required): Pharmacy identifier

  • gtin (optional): Product GTIN for specific product lookup

  • skip (optional): Number of records to skip for pagination (default: 0)

Example Request:

GET /?pharmacy_id=123&gtin=1234567890123&skip=0

Success Response (200):

{
  "status": 200,
  "success": true,
  "data": [
    {
      "ref_id": "550e8400-e29b-41d4-a716-446655440000",
      "old_gtin": "1234567890123",
      "gtin": "1234567890123",
      "brand": "ExampleBrand",
      "old_name": "Old Product Name",
      "name": "Product Name",
      "description": "Product description",
      "category": "Health",
      "image_url": "https://example.com/image.jpg",
      "stock": 100,
      "price": 29.99,
      "sale_price": 24.99,
      "distributor": "Distributor Name",
    }
  ],
  "pagination": {
    "count": 1,
    "has_more": false,
    "cursor": "next_page_items_offset"
  },
  "elapsed_ms": 145
}

Not Found Response (404):

{
  "status": 404,
  "success": false,
  "error": "Product with GTIN '1234567890123' not found for pharmacy with ID '123'",
  "elapsed_ms": 89
}

POST /

Create or update products in batches.

Authentication: Required Content-Type: application/json

Request Body:

{
  "pharmacy_id": "123",
  "medaki_pharmacy_id": "med123",
  "action": "create",
  "products": [
    {
      "old_gtin": "1234567890123",
      "gtin": "1234567890123",
      "brand": "ExampleBrand",
      "old_name": "Old Product Name",
      "name": "Product Name",
      "description": "Product description",
      "category": "Health",
      "image_url": "https://example.com/image.jpg",
      "stock": 100,
      "price": 29.99,
      "sale_price": 24.99,
      "distributor": "Distributor Name",
    }
  ]
}

Field Descriptions:

  • pharmacy_id: Pharmacy identifier

  • medaki_pharmacy_id: MedaKi internal pharmacy ID

  • action: Either "create" or "update"

  • products: Array of product objects

Product Object Fields:

  • old_gtin (required): Original GTIN

  • gtin (required): Current GTIN

  • name (required): Product name

  • stock (required): Stock quantity

  • price (required): Product price

  • sale_price (optional): Sale price

  • brand (optional): Product brand

  • old_name (optional): Previous product name

  • description (optional): Product description

  • category (optional): Product category

  • image_url (optional): Product image URL

  • distributor (optional): Distributor name

Success Response (200):

{
  "status": 200,
  "success": true,
  "data": {
    "message": "Successfully created product(s)",
    "elapsed_ms": 1250,
    "processed_items": 1,
    "chunks_processed": 1
  }
}

Error Responses

Authentication Errors

401 Unauthorized:

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

403 Forbidden:

{
  "status": 403,
  "success": false,
  "error": "Invalid signature"
}

Validation Errors

400 Bad Request:

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

413 Request Too Large:

{
  "status": 413,
  "message": "Request too large. Maximum size allowed: 5242880 bytes (5 MB)",
  "provided_size": 6000000,
  "max_size": 5242880
}

Timeout Errors

408 Request Timeout:

{
  "status": 408,
  "success": false,
  "error": "Request timed out during database operation",
  "elapsed_ms": 30000,
  "timeout_ms": 30000
}

Server Errors

500 Internal Server Error:

{
  "status": 500,
  "success": false,
  "error": "Internal server error",
  "elapsed_ms": 123
}

503 Service Unavailable:

{
  "status": 503,
  "success": false,
  "message": "Database is currently unavailable",
  "error": "Connection failed",
  "elapsed_ms": 5000
}

Rate Limits & Constraints

  • Request Size Limit: 5 MB

  • Batch Size: Processed in chunks of 100-250 products

  • Page Size: 1000 products per GET request

  • Request Timeout: 30 seconds total

  • Authentication Timeout: 5 seconds

  • Database Operation Timeout: Variable based on operation

Data Models

Product

interface Product {
  ref_id: string;
  old_gtin: string;
  gtin: string;
  brand?: string;
  old_name?: string;
  name: string;
  description?: string;
  category?: string;
  image_url?: string;
  stock: number;
  price: number;
  sale_price?: number;
  provider: "positec" | "autostar" | "maplewave";
  distributor?: string;
}

Code Examples

JavaScript/Node.js Authentication Example

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 timestamp = Math.floor(Date.now() / 1000);
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,
  'X-Timestamp': timestamp.toString(),
  'Content-Type': 'application/json',
  'Content-Length': Buffer.byteLength(body).toString()
};

cURL Example

# Health Check
curl -X GET https://positec-dev.curly-mode-08ce.workers.dev/health

# Get Products
curl -X GET \
  "https://positec-dev.curly-mode-08ce.workers.dev/?pharmacy_id=123&skip=0" \
  -H "X-API-Key: your_public_key" \
  -H "X-Signature: generated_signature" \
  -H "X-Timestamp: 1672531200"

# Create Products
curl -X POST \
  https://positec-dev.curly-mode-08ce.workers.dev/ \
  -H "Content-Type: application/json" \
  -H "X-API-Key: your_public_key" \
  -H "X-Signature: generated_signature" \
  -H "X-Timestamp: 1672531200" \
  -d '{
    "pharmacy_id": "123",
    "medaki_pharmacy_id": "med123",
    "action": "create",
    "products": [
      {
        "old_gtin": "1234567890123",
        "gtin": "1234567890123",
        "name": "Product Name",
        "stock": 100,
        "price": 29.99,
        ...
      }
    ]
  }'

Last updated