Skip to main content

Custom API Integration

This guide provides technical details for integrating Eaternity Forecast with your custom POS, ERP, or kitchen management system via our REST API.

API Overview

Base URL

Production: https://api.eaternity.org/v1/forecast
Sandbox: https://sandbox-api.eaternity.org/v1/forecast

Recommendation: Develop and test in sandbox environment before production deployment.

Authentication

Supported Methods:

  1. OAuth 2.0 (recommended for user-facing applications)
  2. API Keys (recommended for server-to-server integration)

Security Requirements:

  • All requests must use HTTPS
  • TLS 1.2 or higher required
  • API keys must be stored securely (environment variables, secret managers)

See Eaternity API Documentation →

Rate Limits

Standard Limits:

  • 100 requests per minute
  • 10,000 requests per day
  • Burst allowance: 150 requests in 60 seconds

Custom Limits:

  • Available for high-volume integrations
  • Contact support for rate limit increase requests

Rate Limit Headers:

X-RateLimit-Limit: 100
X-RateLimit-Remaining: 87
X-RateLimit-Reset: 1705752000

Quick Start

1. Obtain API Credentials

Step 1: Contact Eaternity for API access

Step 2: Receive API credentials

  • API key for authentication
  • Kitchen ID for your location
  • Sandbox environment access

Step 3: Test authentication

curl -X GET "https://sandbox-api.eaternity.org/v1/forecast/ping" \
-H "Authorization: Bearer your_api_key_here"

# Success response:
{
"status": "success",
"message": "Authentication successful",
"kitchen_id": "your_kitchen_id"
}

2. Submit Historical Data

Endpoint: POST /v1/forecast/sales/bulk

Request Example:

curl -X POST "https://api.eaternity.org/v1/forecast/sales/bulk" \
-H "Authorization: Bearer your_api_key" \
-H "Content-Type: application/json" \
-d '{
"kitchen_id": "your_kitchen_id",
"start_date": "2023-10-01",
"end_date": "2024-01-15",
"sales": [
{
"date": "2023-10-01",
"service_period": "lunch",
"items": [
{
"item_id": "pasta_carbonara",
"name": "Pasta Carbonara",
"quantity_sold": 52,
"price": 14.50,
"category": "Main Course"
},
{
"item_id": "caesar_salad",
"name": "Caesar Salad",
"quantity_sold": 31,
"price": 9.00,
"category": "Starter"
}
]
}
]
}'

Response:

{
"status": "success",
"message": "Historical data import queued",
"import_id": "imp_1234567890",
"total_records": 92,
"estimated_processing_time": "15 minutes",
"status_url": "/v1/forecast/imports/imp_1234567890/status"
}

3. Check Import Status

Endpoint: GET /v1/forecast/imports/{import_id}/status

curl -X GET "https://api.eaternity.org/v1/forecast/imports/imp_1234567890/status" \
-H "Authorization: Bearer your_api_key"

Response:

{
"import_id": "imp_1234567890",
"status": "processing",
"progress": 65,
"records_processed": 60,
"records_total": 92,
"records_failed": 0,
"errors": [],
"estimated_completion": "2024-01-20T10:35:00Z"
}

Status Values:

  • queued — Waiting to process
  • processing — Currently importing
  • validating — Checking data quality
  • completed — Import successful
  • failed — Import encountered errors

4. Retrieve Predictions

Endpoint: GET /v1/forecast/predictions

curl -X GET "https://api.eaternity.org/v1/forecast/predictions?date=2024-01-20" \
-H "Authorization: Bearer your_api_key"

Response:

{
"kitchen_id": "your_kitchen_id",
"generated_at": "2024-01-20T03:15:42Z",
"predictions": [
{
"date": "2024-01-20",
"day_of_week": "Saturday",
"items": [
{
"item_id": "pasta_carbonara",
"name": "Pasta Carbonara",
"predicted_quantity": 52,
"confidence_interval": {
"lower": 48,
"upper": 56
},
"confidence_score": 0.92,
"accuracy_last_30_days": 94.2,
"factors": {
"day_of_week_effect": 1.05,
"weather_effect": 1.02,
"trend": "stable"
}
}
]
}
]
}

API Endpoints

Sales Data Endpoints

Submit Daily Sales

POST /v1/forecast/sales

Submit sales data for a single day.

Request Body:

{
"kitchen_id": "your_kitchen_id",
"date": "2024-01-19",
"service_periods": [
{
"period": "lunch",
"items": [
{
"item_id": "pasta_carbonara",
"name": "Pasta Carbonara",
"quantity_sold": 52,
"price": 14.50,
"category": "Main Course"
}
]
},
{
"period": "dinner",
"items": [
{
"item_id": "grilled_salmon",
"name": "Grilled Salmon",
"quantity_sold": 38,
"price": 18.50,
"category": "Main Course"
}
]
}
]
}

Response:

{
"status": "success",
"date": "2024-01-19",
"items_processed": 12,
"next_prediction_update": "2024-01-20T03:00:00Z"
}

Status Codes:

  • 200 — Success
  • 400 — Invalid request data
  • 401 — Authentication failed
  • 422 — Data validation failed
  • 429 — Rate limit exceeded

Bulk Historical Import

POST /v1/forecast/sales/bulk

Import large volumes of historical data.

Request Body:

{
"kitchen_id": "your_kitchen_id",
"start_date": "2023-10-01",
"end_date": "2024-01-15",
"sales": [
{
"date": "2023-10-01",
"service_period": "lunch",
"items": [...]
}
]
}

Response:

{
"status": "success",
"import_id": "imp_1234567890",
"total_records": 92,
"status_url": "/v1/forecast/imports/imp_1234567890/status"
}

Best Practices:

  • Maximum 365 days per bulk import
  • Maximum 10,000 records per request
  • For larger datasets, split into multiple requests
  • Monitor import status via status endpoint

Update Sales Data

PATCH /v1/forecast/sales/{date}

Correct previously submitted sales data.

Request Body:

{
"kitchen_id": "your_kitchen_id",
"items": [
{
"item_id": "pasta_carbonara",
"quantity_sold": 54
}
]
}

Use Cases:

  • Correct data entry errors
  • Update with final end-of-day counts
  • Add missing service periods

Prediction Endpoints

Get Predictions for Date Range

GET /v1/forecast/predictions

Query Parameters:

  • date (required) — Date or start date (YYYY-MM-DD)
  • end_date (optional) — End date for range (YYYY-MM-DD)
  • items (optional) — Comma-separated item IDs to filter

Examples:

# Single date
GET /v1/forecast/predictions?date=2024-01-20

# Date range (next 7 days)
GET /v1/forecast/predictions?date=2024-01-20&end_date=2024-01-27

# Specific items only
GET /v1/forecast/predictions?date=2024-01-20&items=pasta_carbonara,grilled_salmon

Response:

{
"kitchen_id": "your_kitchen_id",
"generated_at": "2024-01-20T03:15:42Z",
"predictions": [
{
"date": "2024-01-20",
"day_of_week": "Saturday",
"items": [...]
},
{
"date": "2024-01-21",
"day_of_week": "Sunday",
"items": [...]
}
]
}

Get Prediction for Single Item

GET /v1/forecast/predictions/{item_id}

Query Parameters:

  • date (required) — Prediction date
  • days_ahead (optional) — Number of days to forecast (default: 7, max: 14)

Example:

GET /v1/forecast/predictions/pasta_carbonara?date=2024-01-20&days_ahead=7

Response:

{
"item_id": "pasta_carbonara",
"name": "Pasta Carbonara",
"predictions": [
{
"date": "2024-01-20",
"predicted_quantity": 52,
"confidence_interval": {
"lower": 48,
"upper": 56
},
"confidence_score": 0.92
}
],
"historical_accuracy": {
"last_7_days": 95.2,
"last_30_days": 94.2,
"all_time": 93.8
}
}

Override Prediction

POST /v1/forecast/predictions/override

Manually override a prediction for specific circumstances.

Request Body:

{
"kitchen_id": "your_kitchen_id",
"date": "2024-01-25",
"item_id": "pasta_carbonara",
"override_quantity": 75,
"reason": "Conference group booking (50 pax confirmed)",
"preserve_ratios": true
}

Parameters:

  • override_quantity (required) — New predicted quantity
  • reason (required) — Explanation for override (used for learning)
  • preserve_ratios (optional) — Adjust related items proportionally

Response:

{
"status": "success",
"original_prediction": 52,
"override_quantity": 75,
"affected_items": [
{
"item_id": "caesar_salad",
"original": 31,
"adjusted": 45
}
]
}

Analytics Endpoints

Get Accuracy Report

GET /v1/forecast/analytics/accuracy

Query Parameters:

  • start_date (required) — Report start date
  • end_date (required) — Report end date
  • group_by (optional) — day, week, month, or item

Example:

GET /v1/forecast/analytics/accuracy?start_date=2024-01-01&end_date=2024-01-31&group_by=week

Response:

{
"period": {
"start": "2024-01-01",
"end": "2024-01-31"
},
"overall_mape": 12.3,
"by_week": [
{
"week": 1,
"start_date": "2024-01-01",
"mape": 13.5,
"items_within_10_percent": 52,
"total_items": 65
}
],
"top_performers": [
{
"item_id": "pasta_carbonara",
"mape": 8.2
}
],
"needs_attention": [
{
"item_id": "daily_special",
"mape": 22.1,
"reason": "High variance, new items frequently"
}
]
}

Get Waste Reduction Report

GET /v1/forecast/analytics/waste-reduction

Track food waste savings since using Forecast.

Query Parameters:

  • baseline_start (required) — Baseline period start (pre-Forecast)
  • baseline_end (required) — Baseline period end
  • comparison_start (required) — Forecast usage period start
  • comparison_end (required) — Forecast usage period end

Response:

{
"baseline": {
"period": "2023-10-01 to 2023-12-31",
"waste_rate": 12.8,
"total_waste_portions": 3845,
"estimated_cost": 21148.50
},
"comparison": {
"period": "2024-01-01 to 2024-03-31",
"waste_rate": 7.2,
"total_waste_portions": 2156,
"estimated_cost": 11858.00
},
"improvement": {
"waste_rate_reduction": 43.8,
"portions_saved": 1689,
"cost_savings": 9290.50,
"annualized_savings": 11748.60
}
}

Webhooks

Overview

Webhooks allow Forecast to push notifications to your system instead of requiring polling.

Use Cases:

  • Receive notifications when new predictions are ready
  • Get alerts for large variance between prediction and actual
  • Monitor data quality issues
  • Track model retraining events

Setup

Configure Webhook Endpoint:

POST /v1/forecast/webhooks

{
"kitchen_id": "your_kitchen_id",
"url": "https://your-system.com/webhooks/forecast",
"events": [
"predictions.generated",
"variance.large",
"data.quality_issue",
"model.retrained"
],
"secret": "your_webhook_secret_for_signature_validation"
}

Response:

{
"webhook_id": "wh_1234567890",
"status": "active",
"events": ["predictions.generated", "variance.large"],
"created_at": "2024-01-20T10:00:00Z"
}

Webhook Events

predictions.generated

Triggered when new predictions are generated (daily at 3:00 AM).

Payload:

{
"event": "predictions.generated",
"timestamp": "2024-01-20T03:15:42Z",
"kitchen_id": "your_kitchen_id",
"data": {
"date_range": {
"start": "2024-01-20",
"end": "2024-01-27"
},
"total_items": 65,
"prediction_url": "/v1/forecast/predictions?date=2024-01-20"
}
}

variance.large

Triggered when actual sales differ significantly from prediction (>20% by default).

Payload:

{
"event": "variance.large",
"timestamp": "2024-01-19T21:30:00Z",
"kitchen_id": "your_kitchen_id",
"data": {
"date": "2024-01-19",
"item_id": "grilled_salmon",
"predicted": 28,
"actual": 42,
"variance_percent": 50.0,
"possible_causes": ["weather_warmer_than_forecast", "event_nearby"]
}
}

data.quality_issue

Triggered when data quality problems detected.

Payload:

{
"event": "data.quality_issue",
"timestamp": "2024-01-20T04:00:00Z",
"kitchen_id": "your_kitchen_id",
"data": {
"issue_type": "missing_data",
"severity": "warning",
"description": "No sales data received for 2024-01-19",
"recommendation": "Submit sales data for 2024-01-19 to maintain prediction accuracy"
}
}

model.retrained

Triggered when model is retrained with new data (weekly).

Payload:

{
"event": "model.retrained",
"timestamp": "2024-01-20T04:30:00Z",
"kitchen_id": "your_kitchen_id",
"data": {
"training_data_range": {
"start": "2023-10-01",
"end": "2024-01-19"
},
"accuracy_improvement": 1.2,
"new_mape": 12.1,
"previous_mape": 13.3
}
}

Webhook Security

Signature Validation:

All webhook requests include X-Forecast-Signature header for verification.

Verification Example (Python):

import hmac
import hashlib

def verify_webhook_signature(payload, signature, secret):
expected_signature = hmac.new(
secret.encode('utf-8'),
payload.encode('utf-8'),
hashlib.sha256
).hexdigest()

return hmac.compare_digest(signature, expected_signature)

# In your webhook handler:
payload = request.body
signature = request.headers['X-Forecast-Signature']
secret = 'your_webhook_secret'

if verify_webhook_signature(payload, signature, secret):
# Process webhook
pass
else:
# Reject request (potential security issue)
return 401

Data Formats and Validation

Item ID Requirements

Format:

  • Alphanumeric characters, underscores, hyphens only
  • Maximum 100 characters
  • Case-sensitive
  • Must be consistent across all requests

Good Examples:

  • pasta_carbonara
  • grilled-salmon-lemon
  • ITEM_12345

Bad Examples:

  • Pasta Carbonara (contains spaces)
  • item#12345 (contains special character)
  • Different casing: Pasta_Carbonara vs pasta_carbonara (inconsistent)

Date Format

Required Format: YYYY-MM-DD (ISO 8601)

Valid:

  • 2024-01-20
  • 2024-12-31

Invalid:

  • 20-01-2024 (wrong order)
  • 2024/01/20 (slashes instead of hyphens)
  • 2024-1-20 (not zero-padded)

Quantity Validation

Requirements:

  • Integer values only
  • Minimum: 0
  • Maximum: 10,000 (contact support for higher limits)
  • Negative values rejected

Price Validation

Requirements:

  • Decimal values (2 decimal places)
  • Minimum: 0.00
  • Maximum: 999.99
  • Currency not specified (use consistent currency across all data)

Error Handling

Error Response Format

{
"status": "error",
"error": {
"code": "VALIDATION_ERROR",
"message": "Invalid date format",
"details": {
"field": "date",
"value": "20-01-2024",
"expected": "YYYY-MM-DD"
}
}
}

Common Error Codes

CodeHTTP StatusDescriptionSolution
AUTHENTICATION_FAILED401Invalid API keyVerify API key is correct
RATE_LIMIT_EXCEEDED429Too many requestsWait and retry, or request limit increase
VALIDATION_ERROR422Invalid data formatCheck request format against docs
NOT_FOUND404Resource not foundVerify kitchen_id or item_id
INTERNAL_ERROR500Server errorRetry with exponential backoff

Retry Logic

Recommended Retry Strategy:

import time
import requests

def make_request_with_retry(url, max_retries=3):
for attempt in range(max_retries):
try:
response = requests.get(url)

if response.status_code == 200:
return response.json()
elif response.status_code == 429:
# Rate limited - wait and retry
retry_after = int(response.headers.get('Retry-After', 60))
time.sleep(retry_after)
elif response.status_code >= 500:
# Server error - exponential backoff
wait_time = 2 ** attempt
time.sleep(wait_time)
else:
# Client error - don't retry
raise Exception(f"Error {response.status_code}: {response.text}")

except requests.exceptions.RequestException as e:
if attempt == max_retries - 1:
raise
time.sleep(2 ** attempt)

raise Exception("Max retries exceeded")

SDK and Client Libraries

Official SDKs

Python SDK (Recommended):

pip install eaternity-forecast

Usage:

from eaternity_forecast import ForecastClient

client = ForecastClient(api_key='your_api_key')

# Submit daily sales
client.sales.submit(
date='2024-01-19',
items=[
{'item_id': 'pasta_carbonara', 'quantity_sold': 52}
]
)

# Get predictions
predictions = client.predictions.get(date='2024-01-20')

JavaScript/TypeScript SDK:

npm install @eaternity/forecast-sdk

Usage:

import { ForecastClient } from '@eaternity/forecast-sdk';

const client = new ForecastClient({ apiKey: 'your_api_key' });

// Get predictions
const predictions = await client.predictions.get({ date: '2024-01-20' });

Community SDKs:

  • Ruby: gem install eaternity-forecast
  • PHP: composer require eaternity/forecast-sdk
  • Go: go get github.com/eaternity/forecast-go

View SDK Documentation →

Testing

Sandbox Environment

Purpose: Test integration without affecting production data

Base URL: https://sandbox-api.eaternity.org/v1/forecast

Features:

  • Separate authentication credentials
  • Test data can be reset
  • Same API as production
  • Faster prediction generation (for testing)

Limitations:

  • Predictions may be less accurate (using synthetic data)
  • No SLA guarantees
  • Data reset weekly

Example Integration Test

import unittest
from eaternity_forecast import ForecastClient

class TestForecastIntegration(unittest.TestCase):
def setUp(self):
self.client = ForecastClient(
api_key='sandbox_api_key',
base_url='https://sandbox-api.eaternity.org/v1/forecast'
)

def test_submit_sales_and_get_predictions(self):
# Submit historical data
response = self.client.sales.submit(
date='2024-01-19',
items=[
{'item_id': 'test_item_1', 'quantity_sold': 50},
{'item_id': 'test_item_2', 'quantity_sold': 30}
]
)
self.assertEqual(response['status'], 'success')

# Wait for processing (sandbox is faster)
time.sleep(10)

# Get predictions
predictions = self.client.predictions.get(date='2024-01-20')
self.assertIsNotNone(predictions)
self.assertGreater(len(predictions['predictions']), 0)

Production Deployment Checklist

Pre-Launch

  • Tested all endpoints in sandbox environment
  • Implemented error handling and retry logic
  • Configured webhook endpoints (if using)
  • Verified webhook signature validation
  • Set up monitoring and logging
  • Documented integration for team
  • Obtained production API credentials
  • Imported historical data successfully
  • Verified data quality score >80%
  • Completed model training

Go-Live

  • Switch from sandbox to production URLs
  • Update API credentials to production
  • Monitor first 24 hours of predictions
  • Verify daily sales submission working
  • Confirm prediction retrieval working
  • Check webhook delivery (if configured)

Post-Launch

  • Track accuracy metrics weekly
  • Review and respond to large variance alerts
  • Provide feedback on prediction quality
  • Optimize preparation workflows based on confidence
  • Schedule monthly performance reviews

Support

Technical Support

Email: forecast-api@eaternity.org

Response Times:

  • Critical (production down): 4 hours
  • High (degraded performance): 24 hours
  • Medium (questions, bugs): 48 hours
  • Low (enhancements): 1 week

Include in Support Requests:

  • Kitchen ID
  • API endpoint and method
  • Request/response examples
  • Error messages
  • Timestamp of issue

Documentation

See Also