Edge API Reference

Version: 1.0.0
Base URL: https://edge.notifito.com

This document describes the public API endpoints exposed by the edge service.


Authentication

notifito uses two types of API keys:

Key Type Prefix Location Purpose
Publishable pk_ Query parameter Widget authentication
Secret sk_ Bearer token Push API, server-to-server

Publishable Keys

Used for widget endpoints. Appear in page source by definition.

GET /v1/widget/schema?key=pk_live_xxx&item_type=sneaker

Secret Keys

Used for push API endpoints. Never exposed to browsers.

POST /v1/availability
Authorization: Bearer sk_live_xxx

Widget API

Get Widget Schema

Returns the attribute schema for rendering the subscription form.

Endpoint: GET /v1/widget/schema

Authentication: Publishable key

Rate Limit: 60 requests per minute per key

Parameters

Parameter Type Required Description
key string Yes Publishable API key
item_type string Yes Item type slug

Response

200 OK

{
    "tenant_name": "Sneaker Store",
    "item_type": {
        "id": "01a04f92-c67c-7093-88c8-28f1b313242c",
        "name": "Sneaker",
        "slug": "sneaker",
        "schema_version": 1,
        "attribute_schema": [
            {
                "key": "size",
                "label": "Size",
                "type": "enum",
                "options": ["40", "41", "42", "43"],
                "required": true,
                "matchable": true,
                "multiple": false
            }
        ]
    },
    "items": [
        {
            "id": "01a04f92-c68e-7333-b731-c691115aed23",
            "name": "Air Force 1",
            "external_ref": "SKU-AF1"
        }
    ]
}

403 Forbidden

{
    "message": "Origin not allowed."
}

404 Not Found

{
    "message": "Unknown item type."
}

Examples

cURL

curl "https://edge.notifito.com/v1/widget/schema?key=pk_live_xxx&item_type=sneaker"

JavaScript

const response = await fetch(
    'https://edge.notifito.com/v1/widget/schema?key=pk_live_xxx&item_type=sneaker'
);
const schema = await response.json();

PHP

use Illuminate\Support\Facades\Http;

$response = Http::get('https://edge.notifito.com/v1/widget/schema', [
    'key' => 'pk_live_xxx',
    'item_type' => 'sneaker',
]);

$schema = $response->json();

Python

import requests

response = requests.get(
    'https://edge.notifito.com/v1/widget/schema',
    params={'key': 'pk_live_xxx', 'item_type': 'sneaker'}
)
schema = response.json()

Create Subscription

Submits a new subscription request. Triggers double opt-in flow.

Endpoint: POST /v1/widget/subscriptions

Authentication: Publishable key

Rate Limit: 30 requests per minute per key

Request Body

{
    "key": "pk_live_xxx",
    "item_type": "sneaker",
    "item_id": "optional-item-uuid",
    "attributes": {
        "size": "42"
    },
    "contact_value": "consumer@example.com"
}
Field Type Required Description
key string Yes Publishable API key
item_type string Yes Item type slug
item_id string No Specific item UUID
attributes object No Subscription attributes
contact_value string Yes Email address

Response

202 Accepted

{
    "subscription_id": "sub-123",
    "status": "pending_confirmation"
}

422 Unprocessable Entity

{
    "message": "The given data was invalid.",
    "errors": {
        "attributes.size": ["Size must be one of: 40, 41, 42, 43."]
    }
}

Examples

cURL

curl -X POST "https://edge.notifito.com/v1/widget/subscriptions" \
  -H "Content-Type: application/json" \
  -d '{
    "key": "pk_live_xxx",
    "item_type": "sneaker",
    "attributes": {"size": "42"},
    "contact_value": "consumer@example.com"
  }'

JavaScript

const response = await fetch('https://edge.notifito.com/v1/widget/subscriptions', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
        key: 'pk_live_xxx',
        item_type: 'sneaker',
        attributes: { size: '42' },
        contact_value: 'consumer@example.com'
    })
});
const result = await response.json();

PHP

use Illuminate\Support\Facades\Http;

$response = Http::post('https://edge.notifito.com/v1/widget/subscriptions', [
    'key' => 'pk_live_xxx',
    'item_type' => 'sneaker',
    'attributes' => ['size' => '42'],
    'contact_value' => 'consumer@example.com',
]);

$result = $response->json();

Python

import requests

response = requests.post(
    'https://edge.notifito.com/v1/widget/subscriptions',
    json={
        'key': 'pk_live_xxx',
        'item_type': 'sneaker',
        'attributes': {'size': '42'},
        'contact_value': 'consumer@example.com'
    }
)
result = response.json()

Push API

Push Availability

Signals that items are now available. Triggers matching and notification.

Endpoint: POST /v1/availability

Authentication: Secret key (Bearer token)

Rate Limit: 100 requests per minute per key

Headers

Header Required Description
Authorization Yes Bearer sk_live_xxx
Idempotency-Key Yes Unique request ID for deduplication
Content-Type Yes application/json

Request Body

{
    "item_type": "sneaker",
    "item_id": "optional-item-uuid",
    "attributes": {
        "size": "42"
    },
    "capacity": 3,
    "available_from": "2026-09-01T00:00:00+00:00",
    "available_until": "2026-09-08T00:00:00+00:00"
}
Field Type Required Description
item_type string Yes Item type slug
item_id string No Specific item UUID
attributes object No Event attributes (wildcard if omitted)
capacity integer No Available capacity
available_from string No Availability window start (ISO 8601)
available_until string No Availability window end (ISO 8601)

Response

202 Accepted

{
    "event_id": "evt-123",
    "duplicate": false
}

202 Accepted (Duplicate)

{
    "event_id": "evt-123",
    "duplicate": true
}

422 Unprocessable Entity

{
    "message": "The given data was invalid.",
    "errors": {
        "attributes.size": ["Size must be one of: 40, 41, 42, 43."]
    }
}

Examples

cURL

curl -X POST "https://edge.notifito.com/v1/availability" \
  -H "Authorization: Bearer sk_live_xxx" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "item_type": "sneaker",
    "attributes": {"size": "42"},
    "capacity": 3
  }'

JavaScript

const response = await fetch('https://edge.notifito.com/v1/availability', {
    method: 'POST',
    headers: {
        'Authorization': 'Bearer sk_live_xxx',
        'Idempotency-Key': crypto.randomUUID(),
        'Content-Type': 'application/json'
    },
    body: JSON.stringify({
        item_type: 'sneaker',
        attributes: { size: '42' },
        capacity: 3
    })
});
const result = await response.json();

PHP

use Illuminate\Support\Facades\Http;
use Illuminate\Support\Str;

$response = Http::withToken('sk_live_xxx')
    ->withHeaders(['Idempotency-Key' => Str::uuid()])
    ->post('https://edge.notifito.com/v1/availability', [
        'item_type' => 'sneaker',
        'attributes' => ['size' => '42'],
        'capacity' => 3,
    ]);

$result = $response->json();

Python

import requests
import uuid

response = requests.post(
    'https://edge.notifito.com/v1/availability',
    headers={
        'Authorization': 'Bearer sk_live_xxx',
        'Idempotency-Key': str(uuid.uuid4()),
        'Content-Type': 'application/json'
    },
    json={
        'item_type': 'sneaker',
        'attributes': {'size': '42'},
        'capacity': 3
    }
)
result = response.json()

Consent Pages

Confirm Subscription

Confirms a subscription via token link. No authentication required.

Endpoint: GET /c/{token}

Token Format: 32 alphanumeric characters

Response

200 OK — Confirmation page

404 Not Found — Invalid or expired token

410 Gone — Token already used


Unsubscribe

Unsubscribes via token link. No authentication required.

Endpoint: GET /u/{token} or POST /u/{token}

Token Format: 32 alphanumeric characters

Response

200 OK — Unsubscribe confirmation page

404 Not Found — Invalid token


Webhooks

Postmark Webhook

Receives delivery status updates from Postmark.

Endpoint: POST /webhooks/postmark

Authentication: HTTP Basic (Postmark credentials)

Request Body

Postmark webhook payload. See Postmark Documentation.

Response

200 OK — Webhook processed


Rate Limiting

Endpoint Limit Window
GET /v1/widget/schema 60 requests 1 minute
POST /v1/widget/subscriptions 30 requests 1 minute
POST /v1/availability 100 requests 1 minute

Rate limits are applied per API key.

Rate Limit Headers

X-RateLimit-Limit: 60
X-RateLimit-Remaining: 59
X-RateLimit-Reset: 1693000000

Rate Limit Exceeded

429 Too Many Requests

{
    "message": "Too Many Requests."
}

CORS

Widget endpoints support Cross-Origin Resource Sharing.

Allowed Origins

Origins are checked against the tenant's registered domains:

{
    "domains": ["shop.example.com", "www.shop.example.com"]
}

Subdomains are allowed if explicitly registered.

CORS Headers

Access-Control-Allow-Origin: https://shop.example.com
Access-Control-Allow-Methods: GET, POST, OPTIONS
Access-Control-Allow-Headers: Content-Type, Authorization, Idempotency-Key
Access-Control-Max-Age: 86400

Preflight Requests

Endpoint: OPTIONS /v1/widget/*

Returns CORS headers without requiring authentication.


Error Responses

All errors follow a consistent format:

{
    "message": "Human-readable error message",
    "errors": {
        "field": ["Validation error message"]
    }
}

HTTP Status Codes

Code Meaning
200 Success
202 Accepted (async processing)
401 Unauthorized (invalid key)
403 Forbidden (origin not allowed)
404 Not Found
422 Validation Error
429 Rate Limit Exceeded
500 Internal Server Error

Related Documentation