Nonito API

The unified analytics and event ingestion API for the Nonito Platform. Track user behavior, manage customer profiles, send messaging templates, and build personalized experiences at scale.

Fast & Async

Asynchronous processing ensures fast response times regardless of database latency.

Easy Integration

Simple REST API works with any language or framework. Get started in minutes.

Secure by Default

Bearer token authentication with legacy API key fallback. Only authorized apps access your data.

Rich User Profiles

Capture default and custom attributes to build complete customer profiles.

Quick Start

Get up and running with the Nonito API in three simple steps.

1

Get your API Key

API keys are available in the Nonito dashboard.

2

Include the Authorization header

Add the Authorization: Bearer <token> header to all your requests.

3

Start tracking

Use /events to track behavior and /user-attributes to manage profiles.

Try it out

curl -X POST https://api.nonito.xyz/events \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer your-api-key" \
  -d '{"event_id": "550e8400-e29b-41d4-a716-446655440000", "event_name": "page_view", "event_params": {"page": "/home"}}'

Events Endpoint

Track analytics events with automatic deduplication. Processing is asynchronous — the server responds immediately.

POSThttps://api.nonito.xyz/events

Ingests a single frontend analytics event. Processing is asynchronous — the server responds immediately and stores the event in a background thread. Deduplication is handled by event_id.

Request Body

ParameterTypeDescription
event_idRequiredstringUnique identifier for the event. Used for deduplication — generate a UUID on the client side.
event_nameRequiredstringThe name/type of the event (e.g., "page_view", "button_click", "purchase")
event_paramsOptionalobjectArbitrary JSON object containing event metadata

User Attributes

Set or update user profile attributes with upsert behavior.

POSThttps://api.nonito.xyz/user-attributes

Create or update user profile attributes. New users are created, existing users are updated. Default attributes are overwritten; custom attributes are merged — set a value to null to remove it.

Request Body

ParameterTypeDescription
user_idRequiredstringUnique identifier for the user
attributesOptionalobjectObject containing user attributes to set

Default Attributes

These attributes are stored as dedicated columns:

AttributeTypeDescription
first_namestringUser's first name
last_namestringUser's last name
emailstringUser's email address
phonestringUser's phone number
genderstringUser's gender (m, f, o, u, n, p)
date_of_birthstringISO 8601 date (YYYY-MM-DD)
countrystringUser's country
languagestringPreferred language
home_citystringUser's home city
email_subscription_statestringopted_in, subscribed, or unsubscribed
push_subscription_statestringopted_in, subscribed, or unsubscribed

Custom Attributes

Any attribute not in the default list is stored as a custom attribute. Custom attributes support strings, numbers, booleans, dates (ISO 8601), arrays, and nested objects. Pass null to remove a custom attribute.

Try it out

curl -X POST https://api.nonito.xyz/user-attributes \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer your-api-key" \
  -d '{"user_id": "user_123", "attributes": {"first_name": "Alex", "email": "alex@example.com", "vip_level": 5}}'

Send Template

Send a WhatsApp or messaging template to a recipient.

POSThttps://api.nonito.xyz/sendtemplate

Send a messaging template to a recipient phone number. Provide your API key, the target phone number, the template name, and the template text content.

Request Body

ParameterTypeDescription
api_keyRequiredstringYour API key for authentication
phone_numberRequiredstringThe recipient's phone number in international format (e.g., "+1234567890")
template_nameRequiredstringThe name of the template to send (e.g., "welcome_message", "order_confirmation")
template_textRequiredstringThe text content of the template

Try it out

curl -X POST https://api.nonito.xyz/sendtemplate \
  -H "Content-Type: application/json" \
  -d '{"api_key": "your-api-key", "phone_number": "+1234567890", "template_name": "welcome_message", "template_text": "Hello {{name}}, welcome to our platform!"}'

Batch Event Ingestion

Ingest batches of strictly validated transactional events (orders, customers). Processing is synchronous — the full batch is validated and persisted before responding.

POSThttps://api.nonito.xyz/v1/events

Each event's properties are validated against Pydantic schemas matching the event_name. Persisted via the ingest_event_batch Supabase RPC function. A request_id is returned for tracing.

For automatic Odoo source tagging, use the /v1/odoo/events variant which injects "source": "odoo" into every event.

Request Body

json
{
  "batch": [
    {
      "event_id": "string (required — unique, for deduplication)",
      "user_id": "string (required — E.164 phone number)",
      "event_name": "string (required — must be an allowed event type)",
      "timestamp": "string (optional — ISO 8601, defaults to now)",
      "properties": { }
    }
  ]
}

Batch Event Fields

FieldTypeDescription
event_id RequiredstringUnique identifier for deduplication
user_id RequiredstringE.164 phone number (e.g., +201234567890)
event_name RequiredstringMust be an allowed event type (see Event Types)
timestamp OptionalstringISO 8601 timestamp. Defaults to current time
properties RequiredobjectEvent-specific data, validated against schema for the event type

Response (202 Accepted)

json
{
  "status": "queued",
  "request_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "message": "Successfully queued 1 events."
}

Example

curl -X POST https://api.nonito.xyz/v1/events \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer your-api-key" \
  -d '{"batch": [{"event_id": "ord_001", "user_id": "+201234567890", "event_name": "order_created", "timestamp": "2025-01-15T10:30:00Z", "properties": {"order_id": "ORD-1001", "channel": "online", "financials": {"subtotal": 250, "total_price": 256.5, "currency": "EGP", "payment_method": "visa"}, "customer": {"first_name": "Ahmed", "last_name": "Hassan", "phone": "+201234567890"}, "items": [{"product_id": "SKU-100", "name": "Classic T-Shirt", "quantity": 2, "unit_price": 125, "total_price": 250}]}}]}'

Allowed Event Types

Each event type maps to a specific Pydantic validation schema for the properties object.

Event NameDescription
order_createdA new order was placed
order_updatedAn existing order was modified
order_cancelledAn order was cancelled
order_packedAn order was packed for shipping
order_dispatchedAn order was dispatched
order_deliveredAn order was delivered
customer_createdA new customer was registered
customer_updatedCustomer info was updated

Validation Schemas

Each event type's properties object is validated against these Pydantic models. Invalid properties return a 400 with detailed per-field errors.

order_created

FieldTypeDescription
order_id RequiredstringUnique order identifier
channel Requiredstring"online" or "store"
financials RequiredFinancialsPayment and pricing details
branch ConditionalBranchInfoRequired when channel is "store"
customer RequiredCustomerInfoCustomer details
items RequiredOrderItem[]List of line items
note OptionalstringOrder note

order_updated

FieldTypeDescription
order_id RequiredstringOrder identifier
status RequiredstringNew order status
updated_at OptionalstringISO 8601 timestamp
note OptionalstringUpdate note
financials OptionalFinancialsUpdated financials
items OptionalOrderItem[]Updated line items
customer OptionalCustomerInfoUpdated customer info
branch OptionalBranchInfoUpdated branch info

order_cancelled

FieldTypeDescription
order_id RequiredstringOrder identifier
reason RequiredstringCancellation reason
refunded_amount OptionalfloatAmount refunded (default 0.0)
reference_number OptionalstringRefund transaction ID

order_packed / order_dispatched

FieldTypeDescription
order_id RequiredstringOrder identifier
delivery_company RequiredstringShipping carrier
tracking_number RequiredstringTracking number
tracking_link OptionalstringTracking URL
note OptionalstringPacking/dispatch note

order_delivered

FieldTypeDescription
order_id RequiredstringOrder identifier
delivery_company RequiredstringShipping carrier (e.g., Bosta, Aramex)
tracking_number RequiredstringTracking number
tracking_link OptionalstringTracking URL
delivery_agent OptionalstringDelivery agent name
received_by OptionalstringPerson who received the order

customer_created / customer_updated

FieldTypeDescription
first_name RequiredstringCustomer first name
last_name RequiredstringCustomer last name
email OptionalstringCustomer email
phone RequiredstringCustomer phone (E.164)
marketing_consent OptionalbooleanMarketing opt-in (default false)

Validation Error Format

json
{
  "error": "Validation Failed",
  "message": "One or more events in the batch are invalid.",
  "details": [
    {
      "index": 0,
      "errors": [
        "Missing required field: 'order_id'",
        "financials.total_price: field required"
      ]
    }
  ]
}

Nested Models

Shared data models referenced by the validation schemas above.

Financials

FieldTypeDefaultDescription
subtotal RequiredfloatPre-discount subtotal
total_discounts Optionalfloat0.0Total discounts applied
total_tax Optionalfloat0.0Total tax
total_price RequiredfloatFinal price
currency Optionalstring"EGP"Currency code
payment_method Requiredstringe.g., cash, visa, wallet
promo_codes Optionalstring[]Coupons applied

BranchInfo

FieldTypeDescription
branch_id RequiredstringBranch identifier
name RequiredstringBranch name
location OptionalstringPhysical address or coordinates
city OptionalstringCity / Governorate
phone OptionalstringBranch contact number

CustomerInfo

FieldTypeDescription
first_name RequiredstringFirst name
last_name RequiredstringLast name
email OptionalstringEmail address
phone RequiredstringPhone number

OrderItem

FieldTypeDefaultDescription
product_id RequiredstringProduct identifier / SKU
name RequiredstringProduct name
quantity RequiredintegerQuantity ordered
unit_price RequiredfloatPrice per unit
total_discount Optionalfloat0.0Discount on this item
total_tax Optionalfloat0.0Tax on this item
total_price RequiredfloatFinal line item price
category OptionalCategoryInfoProduct category

CategoryInfo

FieldTypeDescription
id RequiredstringCategory identifier
name RequiredstringCategory name

Authentication

All endpoints require authentication via API key.

API keys are validated against the nonito_api_keys table in Supabase. A valid key resolves to a nonito_id (tenant identifier).

Supported Auth Methods

MethodHeaderPriority
Bearer Token (standard)Authorization: Bearer <token>Primary
LegacyX-API-Key: <token>Fallback

Validation Flow

1

Extract token

From Authorization: Bearer header (primary) or X-API-Key header (fallback).

2

Query database

Match token against the api_key column in nonito_api_keys table.

3

Authenticate

If found, the nonito_id is used as the tenant identifier. If not, return 401 Unauthorized.

Example

http
Authorization: Bearer 550e8400-e29b-41d4-a716-446655440000

Code Examples

Example implementations in popular languages.

Events API

cURL

bash
curl -X POST https://api.nonito.xyz/events \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer your-api-key" \
  -d '{
    "event_id": "550e8400-e29b-41d4-a716-446655440000",
    "event_name": "button_click",
    "event_params": {
      "user_id": "12345",
      "button_id": "signup_btn",
      "page": "/home"
    }
  }'

JavaScript / TypeScript

javascript
const API_KEY = 'your-api-key';
const BASE_URL = 'https://api.nonito.xyz';

const trackEvent = async (eventName, params = {}) => {
  const response = await fetch(`${BASE_URL}/events`, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${API_KEY}`,
    },
    body: JSON.stringify({
      event_id: crypto.randomUUID(),
      event_name: eventName,
      event_params: params,
    }),
  });

  return response.json();
};

// Example usage
trackEvent('page_view', {
  page: '/dashboard',
  user_id: 'u_123',
});

Python

python
import requests
import uuid

API_KEY = 'your-api-key'
BASE_URL = 'https://api.nonito.xyz'
HEADERS = {
    'Content-Type': 'application/json',
    'Authorization': f'Bearer {API_KEY}',
}

def track_event(event_name, params=None):
    return requests.post(
        f'{BASE_URL}/events',
        headers=HEADERS,
        json={
            'event_id': str(uuid.uuid4()),
            'event_name': event_name,
            'event_params': params or {},
        },
    ).json()

# Example usage
track_event("user_signup", {
    "user_id": "12345",
    "plan": "premium",
    "source": "google_ads",
})

Swift (iOS)

swift
import Foundation

func trackEvent(eventName: String, eventParams: [String: Any]) async throws {
    let url = URL(string: "https://api.nonito.xyz/events")!
    var request = URLRequest(url: url)
    request.httpMethod = "POST"
    request.setValue("application/json", forHTTPHeaderField: "Content-Type")
    request.setValue("Bearer your-api-key", forHTTPHeaderField: "Authorization")

    let payload: [String: Any] = [
        "event_id": UUID().uuidString,
        "event_name": eventName,
        "event_params": eventParams
    ]

    request.httpBody = try JSONSerialization.data(withJSONObject: payload)

    let (data, _) = try await URLSession.shared.data(for: request)
    print(String(data: data, encoding: .utf8) ?? "")
}

User Attributes API

cURL

bash
curl -X POST https://api.nonito.xyz/user-attributes \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer your-api-key" \
  -d '{
    "user_id": "user_123",
    "attributes": {
      "first_name": "Alex",
      "email": "alex@example.com",
      "country": "United States",
      "vip_level": 5,
      "tags": ["premium", "early_adopter"]
    }
  }'

JavaScript / TypeScript

javascript
const setUserAttributes = async (userId, attributes) => {
  const response = await fetch('https://api.nonito.xyz/user-attributes', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': 'Bearer your-api-key',
    },
    body: JSON.stringify({
      user_id: userId,
      attributes: attributes,
    }),
  });

  return response.json();
};

// Set default and custom attributes
setUserAttributes('user_123', {
  first_name: 'Alex',
  email: 'alex@example.com',
  vip_level: 5,
  interests: ['tech', 'gaming'],
});

// Unset a custom attribute
setUserAttributes('user_123', {
  vip_level: null,  // removes the attribute
});

Response

API response format and status codes. Legacy endpoints return 202 Accepted to indicate the request was received and queued for async processing.

Events Response

json
{
  "status": "accepted",
  "event_id": "550e8400-e29b-41d4-a716-446655440000"
}

User Attributes Response

json
{
  "status": "accepted",
  "user_id": "user_123"
}

Send Template Response

json
{
  "status": "accepted",
  "template_name": "welcome_message"
}

Status Codes

202Accepted — Request received and queued for processing
400Bad Request — Missing required fields or invalid JSON
401Unauthorized — Missing or invalid API key
500Internal Error — Database connection or ingestion failure

Standard Error Format

json
{
  "error": "Error type",
  "message": "Human-readable description"
}

Common Event Examples

Suggested event names and parameters for common use cases.

Event NameSuggested ParamsUse Case
page_viewpage, referrer, user_idTrack page visits
button_clickbutton_id, page, user_idTrack UI interactions
user_signupuser_id, plan, sourceTrack new registrations
purchaseuser_id, amount, product_idTrack transactions
app_opendevice, version, user_idTrack app launches
errormessage, stack, user_idTrack errors