Skip to content

public-website ​

← All modules

34 endpoints.

GET /api/v1/public/website/resolve ​

Resolve Instance

Parameters

NameInRequiredTypeDescription
domainqueryyesstring
x-website-secretheaderno

200 — Successful Response

json
{}

422 — Validation Error

json
{
  "properties": {
    "detail": {
      "items": {
        "properties": {
          "loc": {
            "items": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "integer"
                }
              ]
            },
            "type": "array",
            "title": "Location"
          },
          "msg": {
            "type": "string",
            "title": "Message"
          },
          "type": {
            "type": "string",
            "title": "Error Type"
          }
        },
        "type": "object",
        "required": [
          "loc",
          "msg",
          "type"
        ],
        "title": "ValidationError"
      },
      "type": "array",
      "title": "Detail"
    }
  },
  "type": "object",
  "title": "HTTPValidationError"
}

GET /api/v1/public/website/config ​

Get Config

Parameters

NameInRequiredTypeDescription
instancequeryyesstring
x-website-secretheaderno

200 — Successful Response

json
{}

422 — Validation Error

json
{
  "properties": {
    "detail": {
      "items": {
        "properties": {
          "loc": {
            "items": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "integer"
                }
              ]
            },
            "type": "array",
            "title": "Location"
          },
          "msg": {
            "type": "string",
            "title": "Message"
          },
          "type": {
            "type": "string",
            "title": "Error Type"
          }
        },
        "type": "object",
        "required": [
          "loc",
          "msg",
          "type"
        ],
        "title": "ValidationError"
      },
      "type": "array",
      "title": "Detail"
    }
  },
  "type": "object",
  "title": "HTTPValidationError"
}

POST /api/v1/public/website/auth/send-otp ​

Send Otp

Generate a 6-digit OTP, store its bcrypt hash, and email it.

Always returns 200 regardless of whether the email exists — prevents account enumeration. If no customer matches, a bare AccCustomer row is created (email only, status=active) so the OTP flow works for new shoppers too.

Parameters

NameInRequiredTypeDescription
instancequeryyesstring
x-website-secretheaderno

Request body

json
{
  "properties": {
    "email": {
      "type": "string",
      "maxLength": 255,
      "minLength": 3,
      "title": "Email"
    }
  },
  "type": "object",
  "required": [
    "email"
  ],
  "title": "SendOtpIn"
}

200 — Successful Response

json
{}

422 — Validation Error

json
{
  "properties": {
    "detail": {
      "items": {
        "properties": {
          "loc": {
            "items": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "integer"
                }
              ]
            },
            "type": "array",
            "title": "Location"
          },
          "msg": {
            "type": "string",
            "title": "Message"
          },
          "type": {
            "type": "string",
            "title": "Error Type"
          }
        },
        "type": "object",
        "required": [
          "loc",
          "msg",
          "type"
        ],
        "title": "ValidationError"
      },
      "type": "array",
      "title": "Detail"
    }
  },
  "type": "object",
  "title": "HTTPValidationError"
}

POST /api/v1/public/website/auth/verify-otp ​

Verify Otp

Verify a 6-digit OTP and return a JWT + customer profile.

Parameters

NameInRequiredTypeDescription
instancequeryyesstring
x-website-secretheaderno

Request body

json
{
  "properties": {
    "email": {
      "type": "string",
      "maxLength": 255,
      "minLength": 3,
      "title": "Email"
    },
    "code": {
      "type": "string",
      "maxLength": 6,
      "minLength": 6,
      "title": "Code"
    }
  },
  "type": "object",
  "required": [
    "email",
    "code"
  ],
  "title": "VerifyOtpIn"
}

200 — Successful Response

json
{}

422 — Validation Error

json
{
  "properties": {
    "detail": {
      "items": {
        "properties": {
          "loc": {
            "items": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "integer"
                }
              ]
            },
            "type": "array",
            "title": "Location"
          },
          "msg": {
            "type": "string",
            "title": "Message"
          },
          "type": {
            "type": "string",
            "title": "Error Type"
          }
        },
        "type": "object",
        "required": [
          "loc",
          "msg",
          "type"
        ],
        "title": "ValidationError"
      },
      "type": "array",
      "title": "Detail"
    }
  },
  "type": "object",
  "title": "HTTPValidationError"
}

GET /api/v1/public/website/auth/me ​

Auth Me

Return the authenticated customer's profile, or 401.

Parameters

NameInRequiredTypeDescription
instancequeryyesstring
authorizationheaderno
x-website-secretheaderno

200 — Successful Response

json
{
  "properties": {
    "email": {
      "type": "string",
      "title": "Email"
    },
    "name": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "title": "Name"
    },
    "company_name": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "title": "Company Name"
    },
    "phone": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "title": "Phone"
    },
    "customer_group": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "title": "Customer Group"
    },
    "is_reseller": {
      "type": "boolean",
      "title": "Is Reseller",
      "default": false
    }
  },
  "type": "object",
  "required": [
    "email"
  ],
  "title": "WebsiteCustomerInfo"
}

422 — Validation Error

json
{
  "properties": {
    "detail": {
      "items": {
        "properties": {
          "loc": {
            "items": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "integer"
                }
              ]
            },
            "type": "array",
            "title": "Location"
          },
          "msg": {
            "type": "string",
            "title": "Message"
          },
          "type": {
            "type": "string",
            "title": "Error Type"
          }
        },
        "type": "object",
        "required": [
          "loc",
          "msg",
          "type"
        ],
        "title": "ValidationError"
      },
      "type": "array",
      "title": "Detail"
    }
  },
  "type": "object",
  "title": "HTTPValidationError"
}

Portal Link

A fresh one-time link that opens the portal already signed in, for a customer who is ALREADY signed in on the website.

/auth/login hands back portal_url once, and it is good for 60 seconds — so it only helps the moment the password is typed. Owner, 2026-09-21: "your portal button must auto log into the portal": the header's Customer Portal item is clicked hours or days later, on a 30-day website session, and until this it opened the portal's front door, which asked for the password again. This mints the same link on demand from the website session alone.

Authenticated by delegating to :func:auth_me, so the token checks are the one set — valid, bound to this site, not revoked by a sign-out, customer still on the books — and an ERP-backed tenant is proxied to the books the same way, where the signing key lives. portal_url is null (not an error) whenever it cannot be minted, exactly as at login: the caller then just opens the portal's front door.

Parameters

NameInRequiredTypeDescription
instancequeryyesstring
authorizationheaderno
x-website-secretheaderno

200 — Successful Response

json
{}

422 — Validation Error

json
{
  "properties": {
    "detail": {
      "items": {
        "properties": {
          "loc": {
            "items": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "integer"
                }
              ]
            },
            "type": "array",
            "title": "Location"
          },
          "msg": {
            "type": "string",
            "title": "Message"
          },
          "type": {
            "type": "string",
            "title": "Error Type"
          }
        },
        "type": "object",
        "required": [
          "loc",
          "msg",
          "type"
        ],
        "title": "ValidationError"
      },
      "type": "array",
      "title": "Detail"
    }
  },
  "type": "object",
  "title": "HTTPValidationError"
}

POST /api/v1/public/website/auth/login ​

Login

Authenticate with the SAME credential as the customer portal.

Owner, 2026-09-09: "sign in on website and portal must be one, and must not be an sms, but a username and password. username = mobile number" (for VehicleDB; "all sites except vehicle db will work with email as username"). So this is the one door: the portal's own User row is the credential store for both, and a successful sign-in here also hands back portal_url — a 60-second one-time link that opens the portal already signed in, which is what the website's "Login to Portal" button opens in a new tab.

Cross-DB by nature: the credential lives in the tenant's portal identity store (portal_identity_sessionmaker — its own portal site for bs/lubb/ vdb, a remote DSN for tc, whose portal moved to the pbx box), while the customer row lives on the books. Neither is necessarily this site's own database.

Parameters

NameInRequiredTypeDescription
instancequeryyesstring
x-website-secretheaderno

Request body

json
{
  "properties": {
    "email": {
      "anyOf": [
        {
          "type": "string",
          "maxLength": 255
        },
        {
          "type": "null"
        }
      ],
      "title": "Email"
    },
    "username": {
      "anyOf": [
        {
          "type": "string",
          "maxLength": 255
        },
        {
          "type": "null"
        }
      ],
      "title": "Username"
    },
    "password": {
      "type": "string",
      "minLength": 1,
      "title": "Password"
    }
  },
  "type": "object",
  "required": [
    "password"
  ],
  "title": "LoginIn",
  "description": "One credential, whatever the tenant calls it.\n\n``username`` is what the website sends now; ``email`` is the field name\nit sent before 2026-09-09 and is still accepted, because the value was\nalways just \"what the customer typed\" and renaming a wire field is not\nworth breaking a release that is mid-deploy. Either may carry an EMAIL\nADDRESS (every tenant but VehicleDB) or a MOBILE NUMBER (VehicleDB, whose\ncustomers have no address — see app.services.user_identity, which owns the\nrule for telling the two apart)."
}

200 — Successful Response

json
{}

422 — Validation Error

json
{
  "properties": {
    "detail": {
      "items": {
        "properties": {
          "loc": {
            "items": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "integer"
                }
              ]
            },
            "type": "array",
            "title": "Location"
          },
          "msg": {
            "type": "string",
            "title": "Message"
          },
          "type": {
            "type": "string",
            "title": "Error Type"
          }
        },
        "type": "object",
        "required": [
          "loc",
          "msg",
          "type"
        ],
        "title": "ValidationError"
      },
      "type": "array",
      "title": "Detail"
    }
  },
  "type": "object",
  "title": "HTTPValidationError"
}

POST /api/v1/public/website/auth/forgot-password ​

Forgot Password

Email a customer a link to set a new website password.

Owner, 2026-09-21: Cloud Tools (no portal) had no way to recover a password, and its resellers sign in on a password that lives on their customer row (login step 1a). This resets THAT credential. It does not touch a portal user's password — those are reset on the portal, which is why the sign-in dialog only offers this where there is no portal.

Parameters

NameInRequiredTypeDescription
instancequeryyesstring
x-website-secretheaderno

Request body

json
{
  "properties": {
    "email": {
      "type": "string",
      "maxLength": 255,
      "minLength": 3,
      "title": "Email"
    },
    "origin": {
      "type": "string",
      "maxLength": 255,
      "minLength": 8,
      "title": "Origin"
    },
    "store_name": {
      "anyOf": [
        {
          "type": "string",
          "maxLength": 200
        },
        {
          "type": "null"
        }
      ],
      "title": "Store Name"
    }
  },
  "type": "object",
  "required": [
    "email",
    "origin"
  ],
  "title": "ForgotPasswordIn"
}

200 — Successful Response

json
{}

422 — Validation Error

json
{
  "properties": {
    "detail": {
      "items": {
        "properties": {
          "loc": {
            "items": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "integer"
                }
              ]
            },
            "type": "array",
            "title": "Location"
          },
          "msg": {
            "type": "string",
            "title": "Message"
          },
          "type": {
            "type": "string",
            "title": "Error Type"
          }
        },
        "type": "object",
        "required": [
          "loc",
          "msg",
          "type"
        ],
        "title": "ValidationError"
      },
      "type": "array",
      "title": "Detail"
    }
  },
  "type": "object",
  "title": "HTTPValidationError"
}

POST /api/v1/public/website/auth/reset-password ​

Reset Password

Set a new website password from a link :func:forgot_password mailed.

Parameters

NameInRequiredTypeDescription
instancequeryyesstring
x-website-secretheaderno

Request body

json
{
  "properties": {
    "token": {
      "type": "string",
      "maxLength": 4000,
      "minLength": 20,
      "title": "Token"
    },
    "password": {
      "type": "string",
      "maxLength": 128,
      "minLength": 8,
      "title": "Password"
    }
  },
  "type": "object",
  "required": [
    "token",
    "password"
  ],
  "title": "ResetPasswordIn"
}

200 — Successful Response

json
{}

422 — Validation Error

json
{
  "properties": {
    "detail": {
      "items": {
        "properties": {
          "loc": {
            "items": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "integer"
                }
              ]
            },
            "type": "array",
            "title": "Location"
          },
          "msg": {
            "type": "string",
            "title": "Message"
          },
          "type": {
            "type": "string",
            "title": "Error Type"
          }
        },
        "type": "object",
        "required": [
          "loc",
          "msg",
          "type"
        ],
        "title": "ValidationError"
      },
      "type": "array",
      "title": "Detail"
    }
  },
  "type": "object",
  "title": "HTTPValidationError"
}

POST /api/v1/public/website/auth/sso-exchange ​

Sso Exchange

Exchange a valid portal JWT for a website JWT — enables SSO from the portal to the website without re-entering credentials.

Parameters

NameInRequiredTypeDescription
instancequeryyesstring
x-website-secretheaderno

Request body

json
{
  "properties": {
    "portal_token": {
      "type": "string",
      "title": "Portal Token"
    }
  },
  "type": "object",
  "required": [
    "portal_token"
  ],
  "title": "SsoExchangeIn"
}

200 — Successful Response

json
{}

422 — Validation Error

json
{
  "properties": {
    "detail": {
      "items": {
        "properties": {
          "loc": {
            "items": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "integer"
                }
              ]
            },
            "type": "array",
            "title": "Location"
          },
          "msg": {
            "type": "string",
            "title": "Message"
          },
          "type": {
            "type": "string",
            "title": "Error Type"
          }
        },
        "type": "object",
        "required": [
          "loc",
          "msg",
          "type"
        ],
        "title": "ValidationError"
      },
      "type": "array",
      "title": "Detail"
    }
  },
  "type": "object",
  "title": "HTTPValidationError"
}

GET /api/v1/public/website/reseller/users ​

List Reseller Users

The signed-in reseller's own end-users, for the checkout's "who is this order for?" picker.

Parameters

NameInRequiredTypeDescription
instancequeryyesstring
authorizationheaderno
x-website-secretheaderno

200 — Successful Response

json
{}

422 — Validation Error

json
{
  "properties": {
    "detail": {
      "items": {
        "properties": {
          "loc": {
            "items": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "integer"
                }
              ]
            },
            "type": "array",
            "title": "Location"
          },
          "msg": {
            "type": "string",
            "title": "Message"
          },
          "type": {
            "type": "string",
            "title": "Error Type"
          }
        },
        "type": "object",
        "required": [
          "loc",
          "msg",
          "type"
        ],
        "title": "ValidationError"
      },
      "type": "array",
      "title": "Detail"
    }
  },
  "type": "object",
  "title": "HTTPValidationError"
}

POST /api/v1/public/website/reseller/users ​

Create Reseller User From Website

Create a new end-user under the SIGNED-IN reseller, from the shop.

Mirrors app.api.reseller_settings.create_reseller_user — portal login (User + Tenant + owner Membership) on the portal site, AccCustomer with customer_group="Reseller User" + reseller_id on the books site (_books_db(site) — site's own database when they're the same, cross-site otherwise), plus the separate customer-portal site fronting that books site (_portal_instance_for), so up to three different databases can be touched here rather than one coming from Depends(get_db).

Parameters

NameInRequiredTypeDescription
instancequeryyesstring
authorizationheaderno
x-website-secretheaderno

Request body

json
{
  "properties": {
    "name": {
      "type": "string",
      "maxLength": 255,
      "minLength": 1,
      "title": "Name"
    },
    "email": {
      "type": "string",
      "maxLength": 255,
      "minLength": 3,
      "title": "Email"
    },
    "phone": {
      "anyOf": [
        {
          "type": "string",
          "maxLength": 64
        },
        {
          "type": "null"
        }
      ],
      "title": "Phone"
    },
    "password": {
      "anyOf": [
        {
          "type": "string",
          "maxLength": 200,
          "minLength": 8
        },
        {
          "type": "null"
        }
      ],
      "title": "Password"
    }
  },
  "type": "object",
  "required": [
    "name",
    "email"
  ],
  "title": "NewResellerEndUserIn"
}

201 — Successful Response

json
{}

422 — Validation Error

json
{
  "properties": {
    "detail": {
      "items": {
        "properties": {
          "loc": {
            "items": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "integer"
                }
              ]
            },
            "type": "array",
            "title": "Location"
          },
          "msg": {
            "type": "string",
            "title": "Message"
          },
          "type": {
            "type": "string",
            "title": "Error Type"
          }
        },
        "type": "object",
        "required": [
          "loc",
          "msg",
          "type"
        ],
        "title": "ValidationError"
      },
      "type": "array",
      "title": "Detail"
    }
  },
  "type": "object",
  "title": "HTTPValidationError"
}

GET /api/v1/public/website/pages/{slug} ​

Get Page

One enabled content page's full HTML body, for storefront-web's /[slug] route. 404 if the page isn't a known standard page, isn't built, or is disabled.

Parameters

NameInRequiredTypeDescription
slugpathyesstring
instancequeryyesstring
x-website-secretheaderno

200 — Successful Response

json
{}

422 — Validation Error

json
{
  "properties": {
    "detail": {
      "items": {
        "properties": {
          "loc": {
            "items": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "integer"
                }
              ]
            },
            "type": "array",
            "title": "Location"
          },
          "msg": {
            "type": "string",
            "title": "Message"
          },
          "type": {
            "type": "string",
            "title": "Error Type"
          }
        },
        "type": "object",
        "required": [
          "loc",
          "msg",
          "type"
        ],
        "title": "ValidationError"
      },
      "type": "array",
      "title": "Detail"
    }
  },
  "type": "object",
  "title": "HTTPValidationError"
}

GET /api/v1/public/website/articles ​

List Articles

Published blog articles, newest first — lubb-sites' /blog listing and the "Latest articles" rail on a home page. Cards only (excerpt, not body); the full article comes from /articles/{slug}.

Parameters

NameInRequiredTypeDescription
instancequeryyesstring
limitqueryno
x-website-secretheaderno

200 — Successful Response

json
{}

422 — Validation Error

json
{
  "properties": {
    "detail": {
      "items": {
        "properties": {
          "loc": {
            "items": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "integer"
                }
              ]
            },
            "type": "array",
            "title": "Location"
          },
          "msg": {
            "type": "string",
            "title": "Message"
          },
          "type": {
            "type": "string",
            "title": "Error Type"
          }
        },
        "type": "object",
        "required": [
          "loc",
          "msg",
          "type"
        ],
        "title": "ValidationError"
      },
      "type": "array",
      "title": "Detail"
    }
  },
  "type": "object",
  "title": "HTTPValidationError"
}

GET /api/v1/public/website/articles/{slug} ​

Get Article

One published article's full HTML body, for /blog/{slug}. 404 on an unknown slug or an article that is still a draft.

Parameters

NameInRequiredTypeDescription
slugpathyesstring
instancequeryyesstring
x-website-secretheaderno

200 — Successful Response

json
{}

422 — Validation Error

json
{
  "properties": {
    "detail": {
      "items": {
        "properties": {
          "loc": {
            "items": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "integer"
                }
              ]
            },
            "type": "array",
            "title": "Location"
          },
          "msg": {
            "type": "string",
            "title": "Message"
          },
          "type": {
            "type": "string",
            "title": "Error Type"
          }
        },
        "type": "object",
        "required": [
          "loc",
          "msg",
          "type"
        ],
        "title": "ValidationError"
      },
      "type": "array",
      "title": "Detail"
    }
  },
  "type": "object",
  "title": "HTTPValidationError"
}

GET /api/v1/public/website/catalogue ​

Get Catalogue

Parameters

NameInRequiredTypeDescription
instancequeryyesstring
price_listquerynoQuote every item off THIS price list by name instead of the public customer waterfall — e.g. 'Reseller' for the reseller programme page, which advertises trade rates on purpose.
books_onlyquerynobooleanPrice this site's catalogue off the BOOKS alone, with no local website row. For a websites-instance refresher pulling its read model: the website — display_mode, branding, pages — lives on that instance now, and this hub may have no website row at all.
vat_multiplierquerynoThe CALLER's VAT display multiplier (see _vat_multiplier). Only read with books_only, and for the same reason as hide_prices_for_anonymous: it is derived from the website's branding, which is website data this hub may no longer hold.
hide_prices_for_anonymousquerynobooleanThe CALLER's auth_required_for_pricing. Only read with books_only: the policy belongs to the website, so the website has to state it rather than have this endpoint look it up.
x-website-secretheaderno
authorizationheaderno

200 — Successful Response

json
{
  "type": "array",
  "items": {
    "properties": {
      "item_code": {
        "type": "string",
        "title": "Item Code"
      },
      "name": {
        "anyOf": [
          {
            "type": "string"
          },
          {
            "type": "null"
          }
        ],
        "title": "Name"
      },
      "description": {
        "anyOf": [
          {
            "type": "string"
          },
          {
            "type": "null"
          }
        ],
        "title": "Description"
      },
      "seo_title": {
        "anyOf": [
          {
            "type": "string"
          },
          {
            "type": "null"
          }
        ],
        "title": "Seo Title"
      },
      "seo_description": {
        "anyOf": [
          {
            "type": "string"
          },
          {
            "type": "null"
          }
        ],
        "title": "Seo Description"
      },
      "image_url": {
        "anyOf": [
          {
            "type": "string"
          },
          {
            "type": "null"
          }
        ],
        "title": "Image Url"
      },
      "category": {
        "anyOf": [
          {
            "type": "string"
          },
          {
            "type": "null"
          }
        ],
        "title": "Category"
      },
      "product_line": {
        "anyOf": [
          {
            "type": "string"
          },
          {
            "type": "null"
          }
        ],
        "title": "Product Line"
      },
      "price": {
        "anyOf": [
          {
            "type": "number"
          },
          {
            "type": "null"
          }
        ],
        "title": "Price"
      },
      "currency": {
        "anyOf": [
          {
            "type": "string"
          },
          {
            "type": "null"
          }
        ],
        "title": "Currency"
      },
      "annual_price": {
        "anyOf": [
          {
            "type": "number"
          },
          {
            "type": "null"
          }
        ],
        "title": "Annual Price"
      },
      "price_bands": {
        "anyOf": [
          {
            "items": {
              "additionalProperties": true,
              "type": "object"
            },
            "type": "array"
          },
          {
            "type": "null"
          }
        ],
        "title": "Price Bands"
      },
      "annual_price_bands": {
        "anyOf": [
          {
            "items": {
              "additionalProperties": true,
              "type": "object"
            },
            "type": "array"
          },
          {
            "type": "null"
          }
        ],
        "title": "Annual Price Bands"
      },
      "band_prices": {
        "anyOf": [
          {
            "additionalProperties": {
              "type": "number"
            },
            "type": "object"
          },
          {
            "type": "null"
          }
        ],
        "title": "Band Prices"
      },
      "uom": {
        "anyOf": [
          {
            "type": "string"
          },
          {
            "type": "null"
          }
        ],
        "title": "Uom"
      },
      "requires_shipping": {
        "type": "boolean",
        "title": "Requires Shipping",
        "default": false
      },
      "stock_qty": {
        "anyOf": [
          {
            "type": "number"
          },
          {
            "type": "null"
          }
        ],
        "title": "Stock Qty"
      },
      "features": {
        "items": {
          "type": "string"
        },
        "type": "array",
        "title": "Features",
        "default": []
      },
      "feature_values": {
        "anyOf": [
          {
            "additionalProperties": {
              "type": "string"
            },
            "type": "object"
          },
          {
            "type": "null"
          }
        ],
        "title": "Feature Values"
      },
      "badge": {
        "anyOf": [
          {
            "type": "string"
          },
          {
            "type": "null"
          }
        ],
        "title": "Badge"
      },
      "setup_fee": {
        "anyOf": [
          {
            "type": "number"
          },
          {
            "type": "null"
          }
        ],
        "title": "Setup Fee"
      },
      "video_url": {
        "anyOf": [
          {
            "type": "string"
          },
          {
            "type": "null"
          }
        ],
        "title": "Video Url"
      },
      "video_caption": {
        "anyOf": [
          {
            "type": "string"
          },
          {
            "type": "null"
          }
        ],
        "title": "Video Caption"
      },
      "options": {
        "items": {
          "properties": {
            "label": {
              "type": "string",
              "title": "Label"
            },
            "choices": {
              "items": {
                "$ref": "#/components/schemas/CatalogueOptionChoice"
              },
              "type": "array",
              "title": "Choices"
            }
          },
          "type": "object",
          "required": [
            "label",
            "choices"
          ],
          "title": "CatalogueOptionGroup",
          "description": "One picker on a product page — every ``AccItemOption`` row sharing a\n``group_label`` on one parent item. Single-select: the website removes a\nsibling choice from the cart when another is picked."
        },
        "type": "array",
        "title": "Options",
        "default": []
      }
    },
    "type": "object",
    "required": [
      "item_code",
      "name",
      "description",
      "image_url",
      "category",
      "product_line",
      "price",
      "currency"
    ],
    "title": "CatalogueItem"
  },
  "title": "Response Get Catalogue Api V1 Public Website Catalogue Get"
}

422 — Validation Error

json
{
  "properties": {
    "detail": {
      "items": {
        "properties": {
          "loc": {
            "items": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "integer"
                }
              ]
            },
            "type": "array",
            "title": "Location"
          },
          "msg": {
            "type": "string",
            "title": "Message"
          },
          "type": {
            "type": "string",
            "title": "Error Type"
          }
        },
        "type": "object",
        "required": [
          "loc",
          "msg",
          "type"
        ],
        "title": "ValidationError"
      },
      "type": "array",
      "title": "Detail"
    }
  },
  "type": "object",
  "title": "HTTPValidationError"
}

POST /api/v1/public/website/checkout ​

Checkout

Parameters

NameInRequiredTypeDescription
instancequeryyesstring
x-website-secretheaderno
authorizationheaderno

Request body

json
{
  "properties": {
    "items": {
      "items": {
        "properties": {
          "item_code": {
            "type": "string",
            "title": "Item Code"
          },
          "qty": {
            "type": "number",
            "exclusiveMinimum": 0,
            "title": "Qty"
          },
          "billing_period": {
            "type": "string",
            "enum": [
              "monthly",
              "annual"
            ],
            "title": "Billing Period",
            "default": "monthly"
          },
          "line_note": {
            "anyOf": [
              {
                "type": "string",
                "maxLength": 140
              },
              {
                "type": "null"
              }
            ],
            "title": "Line Note"
          }
        },
        "type": "object",
        "required": [
          "item_code",
          "qty"
        ],
        "title": "CheckoutItemIn"
      },
      "type": "array",
      "maxItems": 100,
      "minItems": 1,
      "title": "Items"
    },
    "email": {
      "type": "string",
      "maxLength": 255,
      "title": "Email"
    },
    "name": {
      "anyOf": [
        {
          "type": "string",
          "maxLength": 255
        },
        {
          "type": "null"
        }
      ],
      "title": "Name"
    },
    "phone": {
      "anyOf": [
        {
          "type": "string",
          "maxLength": 64
        },
        {
          "type": "null"
        }
      ],
      "title": "Phone"
    },
    "shipping_option": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "title": "Shipping Option"
    },
    "shipping_address": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "title": "Shipping Address"
    },
    "origin": {
      "type": "string",
      "maxLength": 500,
      "title": "Origin"
    },
    "recurring": {
      "type": "boolean",
      "title": "Recurring",
      "default": false
    },
    "reseller_user_id": {
      "anyOf": [
        {
          "type": "string",
          "maxLength": 36
        },
        {
          "type": "null"
        }
      ],
      "title": "Reseller User Id"
    }
  },
  "type": "object",
  "required": [
    "items",
    "email",
    "origin"
  ],
  "title": "CheckoutIn"
}

200 — Successful Response

json
{}

422 — Validation Error

json
{
  "properties": {
    "detail": {
      "items": {
        "properties": {
          "loc": {
            "items": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "integer"
                }
              ]
            },
            "type": "array",
            "title": "Location"
          },
          "msg": {
            "type": "string",
            "title": "Message"
          },
          "type": {
            "type": "string",
            "title": "Error Type"
          }
        },
        "type": "object",
        "required": [
          "loc",
          "msg",
          "type"
        ],
        "title": "ValidationError"
      },
      "type": "array",
      "title": "Detail"
    }
  },
  "type": "object",
  "title": "HTTPValidationError"
}

GET /api/v1/public/website/credits/account ​

Credits Account

Does this mobile number already have a VehicleDB account?

The checkout asks before taking payment, because the mobile number IS the app's identity (:class:app.models.vehicledb.VdbAppUser) — bought credits land on the wallet behind this number, so the buyer needs to see whether that is an account they already have (top-up) or a new one being opened. Matching on the email address instead would happily credit a wallet nobody can sign in to.

Deliberately thin: whether an account exists, a display name, and a MASKED email. This route is reachable with nothing but the website secret (held by the renderer, not the browser), and a full name+email per number would make it a lookup service for anyone who ever obtains that secret. Rate limited on top of that, since the answer is a yes/no keyed by phone number.

Parameters

NameInRequiredTypeDescription
instancequeryyesstring
mobilequeryyesstring
x-website-secretheaderno

200 — Successful Response

json
{}

422 — Validation Error

json
{
  "properties": {
    "detail": {
      "items": {
        "properties": {
          "loc": {
            "items": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "integer"
                }
              ]
            },
            "type": "array",
            "title": "Location"
          },
          "msg": {
            "type": "string",
            "title": "Message"
          },
          "type": {
            "type": "string",
            "title": "Error Type"
          }
        },
        "type": "object",
        "required": [
          "loc",
          "msg",
          "type"
        ],
        "title": "ValidationError"
      },
      "type": "array",
      "title": "Detail"
    }
  },
  "type": "object",
  "title": "HTTPValidationError"
}

POST /api/v1/public/website/contact ​

Contact

Storefront "Get in Touch" enquiry — emails the site's own support inbox (branding.support_email) via the per-site SMTP overlay. Best-effort like every other send here: a disabled/misconfigured SMTP returns a clean 422 rather than 500, so the website can show a real message.

Parameters

NameInRequiredTypeDescription
instancequeryyesstring
x-website-secretheaderno

Request body

json
{
  "properties": {
    "name": {
      "type": "string",
      "maxLength": 200,
      "minLength": 1,
      "title": "Name"
    },
    "email": {
      "type": "string",
      "maxLength": 255,
      "minLength": 3,
      "title": "Email"
    },
    "subject": {
      "anyOf": [
        {
          "type": "string",
          "maxLength": 200
        },
        {
          "type": "null"
        }
      ],
      "title": "Subject"
    },
    "message": {
      "type": "string",
      "maxLength": 5000,
      "minLength": 1,
      "title": "Message"
    }
  },
  "type": "object",
  "required": [
    "name",
    "email",
    "message"
  ],
  "title": "ContactIn"
}

200 — Successful Response

json
{}

422 — Validation Error

json
{
  "properties": {
    "detail": {
      "items": {
        "properties": {
          "loc": {
            "items": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "integer"
                }
              ]
            },
            "type": "array",
            "title": "Location"
          },
          "msg": {
            "type": "string",
            "title": "Message"
          },
          "type": {
            "type": "string",
            "title": "Error Type"
          }
        },
        "type": "object",
        "required": [
          "loc",
          "msg",
          "type"
        ],
        "title": "ValidationError"
      },
      "type": "array",
      "title": "Detail"
    }
  },
  "type": "object",
  "title": "HTTPValidationError"
}

POST /api/v1/public/website/boxseats-trial ​

Boxseats Trial

Request the free 10-minute Box Seats instant web trial.

Records the lead and EMAILS a one-time sign-in link; nothing is provisioned here (owner, 2026-09-07: "send trial auto login link to email, to ensure the email is valid"). The line is created when that link is redeemed at /boxseats-trial/activate below.

Answers {ok, reason, message}; message is written to be shown as-is. reason is sent on success and already_claimed for an address that has already ACTIVATED a trial — a row that was mailed and never clicked is re-sent to, not refused.

Parameters

NameInRequiredTypeDescription
instancequeryyesstring
x-website-secretheaderno
authorizationheaderno

Request body

json
{
  "properties": {
    "name": {
      "type": "string",
      "maxLength": 200,
      "minLength": 1,
      "title": "Name"
    },
    "email": {
      "type": "string",
      "maxLength": 255,
      "minLength": 3,
      "title": "Email"
    },
    "phone": {
      "anyOf": [
        {
          "type": "string",
          "maxLength": 64
        },
        {
          "type": "null"
        }
      ],
      "title": "Phone"
    },
    "province": {
      "anyOf": [
        {
          "type": "string",
          "maxLength": 80
        },
        {
          "type": "null"
        }
      ],
      "title": "Province"
    },
    "age_group": {
      "anyOf": [
        {
          "type": "string",
          "maxLength": 80
        },
        {
          "type": "null"
        }
      ],
      "title": "Age Group"
    },
    "watch_on": {
      "anyOf": [
        {
          "type": "string",
          "maxLength": 80
        },
        {
          "type": "null"
        }
      ],
      "title": "Watch On"
    },
    "watches": {
      "anyOf": [
        {
          "type": "string",
          "maxLength": 80
        },
        {
          "type": "null"
        }
      ],
      "title": "Watches"
    },
    "latitude": {
      "anyOf": [
        {
          "type": "number",
          "maximum": 90,
          "minimum": -90
        },
        {
          "type": "null"
        }
      ],
      "title": "Latitude"
    },
    "longitude": {
      "anyOf": [
        {
          "type": "number",
          "maximum": 180,
          "minimum": -180
        },
        {
          "type": "null"
        }
      ],
      "title": "Longitude"
    },
    "accuracy": {
      "anyOf": [
        {
          "type": "number",
          "maximum": 20000000,
          "minimum": 0
        },
        {
          "type": "null"
        }
      ],
      "title": "Accuracy"
    }
  },
  "type": "object",
  "required": [
    "name",
    "email"
  ],
  "title": "BoxseatsTrialIn"
}

200 — Successful Response

json
{}

422 — Validation Error

json
{
  "properties": {
    "detail": {
      "items": {
        "properties": {
          "loc": {
            "items": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "integer"
                }
              ]
            },
            "type": "array",
            "title": "Location"
          },
          "msg": {
            "type": "string",
            "title": "Message"
          },
          "type": {
            "type": "string",
            "title": "Error Type"
          }
        },
        "type": "object",
        "required": [
          "loc",
          "msg",
          "type"
        ],
        "title": "ValidationError"
      },
      "type": "array",
      "title": "Detail"
    }
  },
  "type": "object",
  "title": "HTTPValidationError"
}

POST /api/v1/public/website/boxseats-trial/activate ​

Boxseats Trial Activate

Redeem an emailed trial link: create the line and hand back the sign-in.

Answers {ok, reason, message} plus username, password, player_url, minutes and expires_at on success. The website redirects the browser to player_url.

Rate-limited on the TOKEN rather than only the IP: a token is a bearer credential arriving complete, so the thing worth throttling is repeated attempts against one, and a shared-NAT IP must not be able to lock out everyone behind it. 256 bits is not guessable — this is belt to that braces, and it also caps a mail client that pre-fetches links in a loop.

Parameters

NameInRequiredTypeDescription
instancequeryyesstring
x-website-secretheaderno
authorizationheaderno

Request body

json
{
  "properties": {
    "token": {
      "type": "string",
      "maxLength": 200,
      "minLength": 8,
      "title": "Token"
    }
  },
  "type": "object",
  "required": [
    "token"
  ],
  "title": "BoxseatsTrialActivateIn"
}

200 — Successful Response

json
{}

422 — Validation Error

json
{
  "properties": {
    "detail": {
      "items": {
        "properties": {
          "loc": {
            "items": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "integer"
                }
              ]
            },
            "type": "array",
            "title": "Location"
          },
          "msg": {
            "type": "string",
            "title": "Message"
          },
          "type": {
            "type": "string",
            "title": "Error Type"
          }
        },
        "type": "object",
        "required": [
          "loc",
          "msg",
          "type"
        ],
        "title": "ValidationError"
      },
      "type": "array",
      "title": "Detail"
    }
  },
  "type": "object",
  "title": "HTTPValidationError"
}

POST /api/v1/public/website/trial-signup ​

Trial Signup

Start a self-serve free trial. Answers {ok, reason, message, job_id?}; a refusal (already_claimed) is a 200 with ok: false so the form can show the message as an answer rather than a fault.

Parameters

NameInRequiredTypeDescription
instancequeryyesstring
x-website-secretheaderno

Request body

json
{
  "properties": {
    "name": {
      "type": "string",
      "maxLength": 200,
      "minLength": 1,
      "title": "Name"
    },
    "email": {
      "type": "string",
      "maxLength": 255,
      "minLength": 3,
      "title": "Email"
    },
    "company": {
      "type": "string",
      "maxLength": 200,
      "minLength": 1,
      "title": "Company"
    },
    "product": {
      "anyOf": [
        {
          "type": "string",
          "maxLength": 120
        },
        {
          "type": "null"
        }
      ],
      "title": "Product"
    },
    "product_line": {
      "anyOf": [
        {
          "type": "string",
          "maxLength": 120
        },
        {
          "type": "null"
        }
      ],
      "title": "Product Line"
    },
    "notes": {
      "anyOf": [
        {
          "type": "string",
          "maxLength": 4000
        },
        {
          "type": "null"
        }
      ],
      "title": "Notes"
    },
    "support_email": {
      "anyOf": [
        {
          "type": "string",
          "maxLength": 255
        },
        {
          "type": "null"
        }
      ],
      "title": "Support Email"
    }
  },
  "type": "object",
  "required": [
    "name",
    "email",
    "company"
  ],
  "title": "TrialSignupIn"
}

200 — Successful Response

json
{}

422 — Validation Error

json
{
  "properties": {
    "detail": {
      "items": {
        "properties": {
          "loc": {
            "items": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "integer"
                }
              ]
            },
            "type": "array",
            "title": "Location"
          },
          "msg": {
            "type": "string",
            "title": "Message"
          },
          "type": {
            "type": "string",
            "title": "Error Type"
          }
        },
        "type": "object",
        "required": [
          "loc",
          "msg",
          "type"
        ],
        "title": "ValidationError"
      },
      "type": "array",
      "title": "Detail"
    }
  },
  "type": "object",
  "title": "HTTPValidationError"
}

GET /api/v1/public/website/trial-signup/{job_id} ​

Trial Signup Status

Where a started trial has got to: {ok, status, message, sign_in_url?}. status ends at ready or failed. Never carries the password.

Parameters

NameInRequiredTypeDescription
job_idpathyesstring
instancequeryyesstring
x-website-secretheaderno

200 — Successful Response

json
{}

422 — Validation Error

json
{
  "properties": {
    "detail": {
      "items": {
        "properties": {
          "loc": {
            "items": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "integer"
                }
              ]
            },
            "type": "array",
            "title": "Location"
          },
          "msg": {
            "type": "string",
            "title": "Message"
          },
          "type": {
            "type": "string",
            "title": "Error Type"
          }
        },
        "type": "object",
        "required": [
          "loc",
          "msg",
          "type"
        ],
        "title": "ValidationError"
      },
      "type": "array",
      "title": "Detail"
    }
  },
  "type": "object",
  "title": "HTTPValidationError"
}

POST /api/v1/public/website/quote-request ​

Quote Request

Submit a basket as a quote request — the quote cart type's checkout.

Parameters

NameInRequiredTypeDescription
instancequeryyesstring
x-website-secretheaderno
authorizationheaderno

Request body

json
{
  "properties": {
    "name": {
      "type": "string",
      "maxLength": 200,
      "minLength": 1,
      "title": "Name"
    },
    "email": {
      "type": "string",
      "maxLength": 255,
      "minLength": 3,
      "title": "Email"
    },
    "phone": {
      "anyOf": [
        {
          "type": "string",
          "maxLength": 64
        },
        {
          "type": "null"
        }
      ],
      "title": "Phone"
    },
    "company": {
      "anyOf": [
        {
          "type": "string",
          "maxLength": 200
        },
        {
          "type": "null"
        }
      ],
      "title": "Company"
    },
    "message": {
      "anyOf": [
        {
          "type": "string",
          "maxLength": 5000
        },
        {
          "type": "null"
        }
      ],
      "title": "Message"
    },
    "lines": {
      "items": {
        "properties": {
          "item_code": {
            "type": "string",
            "maxLength": 140,
            "minLength": 1,
            "title": "Item Code"
          },
          "qty": {
            "type": "number",
            "maximum": 100000,
            "exclusiveMinimum": 0,
            "title": "Qty"
          }
        },
        "type": "object",
        "required": [
          "item_code",
          "qty"
        ],
        "title": "QuoteLineIn"
      },
      "type": "array",
      "maxItems": 100,
      "minItems": 1,
      "title": "Lines"
    }
  },
  "type": "object",
  "required": [
    "name",
    "email",
    "lines"
  ],
  "title": "QuoteRequestIn"
}

200 — Successful Response

json
{}

422 — Validation Error

json
{
  "properties": {
    "detail": {
      "items": {
        "properties": {
          "loc": {
            "items": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "integer"
                }
              ]
            },
            "type": "array",
            "title": "Location"
          },
          "msg": {
            "type": "string",
            "title": "Message"
          },
          "type": {
            "type": "string",
            "title": "Error Type"
          }
        },
        "type": "object",
        "required": [
          "loc",
          "msg",
          "type"
        ],
        "title": "ValidationError"
      },
      "type": "array",
      "title": "Detail"
    }
  },
  "type": "object",
  "title": "HTTPValidationError"
}

Domain Search

Live domain-name availability: the four ZARC .za TLDs, plus every international extension the Domain Pricing grid has priced (see _intl_tlds). Read-only (EPP <check>/Cloudflare Registrar Check only) and deliberately thin: check_registration() also returns registrant/nameserver/registrar detail for a TAKEN .za domain, which is real personal information for a domain that may not even be a Lubb customer's — this endpoint keeps only the availability flag from that result before it's ever serialized.

Parameters

NameInRequiredTypeDescription
x-website-secretheaderno

Request body

json
{
  "properties": {
    "query": {
      "type": "string",
      "maxLength": 100,
      "minLength": 1,
      "title": "Query"
    },
    "tlds": {
      "anyOf": [
        {
          "items": {
            "type": "string"
          },
          "type": "array",
          "maxItems": 64
        },
        {
          "type": "null"
        }
      ],
      "title": "Tlds"
    }
  },
  "type": "object",
  "required": [
    "query"
  ],
  "title": "DomainSearchIn",
  "description": "``tlds`` narrows the search to the extensions the visitor actually has\nswitched on in the website's chip row (lubb-sites\ncomponents/domain-search.tsx). Omitted or empty means every extension we\nsell, which is what this endpoint has always answered — so an older\nwebsite build that doesn't send the field is unaffected.\n\nIt is a FILTER over the offered set, never an extension of it: a name in\nhere that we don't sell is dropped rather than checked, so the field can't\nbe used to spend our ZARC/Cloudflare rate limit on arbitrary lookups, and\nthe \"only ever offer an extension the owner can see we sell\" rule above\nstill holds. Capped because each surviving entry is an EPP round trip."
}

200 — Successful Response

json
{
  "properties": {
    "results": {
      "items": {
        "properties": {
          "domain": {
            "type": "string",
            "title": "Domain"
          },
          "available": {
            "anyOf": [
              {
                "type": "boolean"
              },
              {
                "type": "null"
              }
            ],
            "title": "Available"
          },
          "price": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "null"
              }
            ],
            "title": "Price"
          },
          "currency": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Currency"
          }
        },
        "type": "object",
        "required": [
          "domain",
          "available"
        ],
        "title": "DomainSearchResult"
      },
      "type": "array",
      "title": "Results"
    }
  },
  "type": "object",
  "required": [
    "results"
  ],
  "title": "DomainSearchOut"
}

422 — Validation Error

json
{
  "properties": {
    "detail": {
      "items": {
        "properties": {
          "loc": {
            "items": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "integer"
                }
              ]
            },
            "type": "array",
            "title": "Location"
          },
          "msg": {
            "type": "string",
            "title": "Message"
          },
          "type": {
            "type": "string",
            "title": "Error Type"
          }
        },
        "type": "object",
        "required": [
          "loc",
          "msg",
          "type"
        ],
        "title": "ValidationError"
      },
      "type": "array",
      "title": "Detail"
    }
  },
  "type": "object",
  "title": "HTTPValidationError"
}

GET /api/v1/public/website/booking/pbx-offer/slots ​

Pbx Offer Slots

Parameters

NameInRequiredTypeDescription
instancequeryyesstring
datequeryno
x-website-secretheaderno

200 — Successful Response

json
{}

422 — Validation Error

json
{
  "properties": {
    "detail": {
      "items": {
        "properties": {
          "loc": {
            "items": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "integer"
                }
              ]
            },
            "type": "array",
            "title": "Location"
          },
          "msg": {
            "type": "string",
            "title": "Message"
          },
          "type": {
            "type": "string",
            "title": "Error Type"
          }
        },
        "type": "object",
        "required": [
          "loc",
          "msg",
          "type"
        ],
        "title": "ValidationError"
      },
      "type": "array",
      "title": "Detail"
    }
  },
  "type": "object",
  "title": "HTTPValidationError"
}

POST /api/v1/public/website/booking/pbx-offer ​

Create Pbx Offer Booking

Parameters

NameInRequiredTypeDescription
instancequeryyesstring
x-website-secretheaderno

Request body

json
{
  "properties": {
    "name": {
      "type": "string",
      "maxLength": 255,
      "minLength": 1,
      "title": "Name"
    },
    "email": {
      "type": "string",
      "maxLength": 255,
      "minLength": 3,
      "title": "Email"
    },
    "phone": {
      "type": "string",
      "maxLength": 64,
      "minLength": 1,
      "title": "Phone"
    },
    "company": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "title": "Company"
    },
    "start_at": {
      "type": "string",
      "format": "date-time",
      "title": "Start At"
    }
  },
  "type": "object",
  "required": [
    "name",
    "email",
    "phone",
    "start_at"
  ],
  "title": "PbxOfferBookingIn"
}

201 — Successful Response

json
{}

422 — Validation Error

json
{
  "properties": {
    "detail": {
      "items": {
        "properties": {
          "loc": {
            "items": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "integer"
                }
              ]
            },
            "type": "array",
            "title": "Location"
          },
          "msg": {
            "type": "string",
            "title": "Message"
          },
          "type": {
            "type": "string",
            "title": "Error Type"
          }
        },
        "type": "object",
        "required": [
          "loc",
          "msg",
          "type"
        ],
        "title": "ValidationError"
      },
      "type": "array",
      "title": "Detail"
    }
  },
  "type": "object",
  "title": "HTTPValidationError"
}

GET /api/v1/public/website/orders/{token} ​

Order Status

The order page's poll — and the shopper's own route into settlement.

Paystack redirects a paying shopper straight here (callback_url is {origin}/order/{pay_token}, set in :func:checkout), so this is the first thing that knows a charge may have landed. It no longer just reports the order's state: while the order is still unpaid it ASKS Paystack whether the charge succeeded and, if it did, settles it into the ERP — Sales Invoice raised and submitted, Activations raised for staff to process — via :func:app.apps.erp.services.accounting_payment_paystack.settle_website_order_by_reference.

Before this, charge.success reaching the webhook (:mod:app.apps.erp.api.paystack) was the single point of failure between a real charge and an invoice: a delivery Paystack never made left the shopper paid, this page spinning "Waiting for payment confirmation…" forever, and nothing raised for anyone to act on. The webhook still runs and is still normally first; both funnel into the same claim-guarded booking, so whichever arrives first wins and the other is a no-op.

origin — the shopper-facing public origin (the renderer passes it, same as checkout does), used for the links in the invoice email settlement sends. Omitted, that falls back to settings.APP_BASE_URL.

Parameters

NameInRequiredTypeDescription
tokenpathyesstring
instancequeryyesstring
originqueryno
x-website-secretheaderno

200 — Successful Response

json
{}

422 — Validation Error

json
{
  "properties": {
    "detail": {
      "items": {
        "properties": {
          "loc": {
            "items": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "integer"
                }
              ]
            },
            "type": "array",
            "title": "Location"
          },
          "msg": {
            "type": "string",
            "title": "Message"
          },
          "type": {
            "type": "string",
            "title": "Error Type"
          }
        },
        "type": "object",
        "required": [
          "loc",
          "msg",
          "type"
        ],
        "title": "ValidationError"
      },
      "type": "array",
      "title": "Detail"
    }
  },
  "type": "object",
  "title": "HTTPValidationError"
}

GET /api/v1/public/website/brief ​

Get Brief

The questions, plus whatever this client has already answered.

Parameters

NameInRequiredTypeDescription
instancequeryyesstring
tokenqueryyesstring
x-website-secretheaderno

200 — Successful Response

json
{}

422 — Validation Error

json
{
  "properties": {
    "detail": {
      "items": {
        "properties": {
          "loc": {
            "items": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "integer"
                }
              ]
            },
            "type": "array",
            "title": "Location"
          },
          "msg": {
            "type": "string",
            "title": "Message"
          },
          "type": {
            "type": "string",
            "title": "Error Type"
          }
        },
        "type": "object",
        "required": [
          "loc",
          "msg",
          "type"
        ],
        "title": "ValidationError"
      },
      "type": "array",
      "title": "Detail"
    }
  },
  "type": "object",
  "title": "HTTPValidationError"
}

POST /api/v1/public/website/brief ​

Save Brief

Save the client's answers. Called on autosave and on submit.

Parameters

NameInRequiredTypeDescription
instancequeryyesstring
x-website-secretheaderno

Request body

json
{
  "properties": {
    "token": {
      "type": "string",
      "maxLength": 64,
      "minLength": 16,
      "title": "Token"
    },
    "answers": {
      "additionalProperties": {
        "type": "string"
      },
      "type": "object",
      "title": "Answers"
    },
    "submit": {
      "type": "boolean",
      "title": "Submit",
      "default": false
    }
  },
  "type": "object",
  "required": [
    "token"
  ],
  "title": "BriefAnswersIn"
}

200 — Successful Response

json
{}

422 — Validation Error

json
{
  "properties": {
    "detail": {
      "items": {
        "properties": {
          "loc": {
            "items": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "integer"
                }
              ]
            },
            "type": "array",
            "title": "Location"
          },
          "msg": {
            "type": "string",
            "title": "Message"
          },
          "type": {
            "type": "string",
            "title": "Error Type"
          }
        },
        "type": "object",
        "required": [
          "loc",
          "msg",
          "type"
        ],
        "title": "ValidationError"
      },
      "type": "array",
      "title": "Detail"
    }
  },
  "type": "object",
  "title": "HTTPValidationError"
}

GET /api/v1/public/website/reseller-rates ​

Get Reseller Rates

Parameters

NameInRequiredTypeDescription
instancequeryyesstring
x-website-secretheaderno

200 — Successful Response

json
{
  "type": "object",
  "additionalProperties": true,
  "title": "Response Get Reseller Rates Api V1 Public Website Reseller Rates Get"
}

422 — Validation Error

json
{
  "properties": {
    "detail": {
      "items": {
        "properties": {
          "loc": {
            "items": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "integer"
                }
              ]
            },
            "type": "array",
            "title": "Location"
          },
          "msg": {
            "type": "string",
            "title": "Message"
          },
          "type": {
            "type": "string",
            "title": "Error Type"
          }
        },
        "type": "object",
        "required": [
          "loc",
          "msg",
          "type"
        ],
        "title": "ValidationError"
      },
      "type": "array",
      "title": "Detail"
    }
  },
  "type": "object",
  "title": "HTTPValidationError"
}

POST /api/v1/public/website/book-order ​

Book Order

Price and book one website order on the books. Returns {order_id, pay_token, grand_total}.

The Idempotency-Key is not optional and not decoration. Putting a network hop in front of order creation adds a failure mode a hub-served website never had: a request that times out after the order was written. Without the key the instance's retry books a second order and the buyer is invoiced twice for one basket.

Parameters

NameInRequiredTypeDescription
instancequeryyesstring
x-website-secretheaderno
authorizationheaderno
Idempotency-Keyheaderno

Request body

json
{
  "properties": {
    "checkout": {
      "properties": {
        "items": {
          "items": {
            "properties": {
              "item_code": {
                "type": "string",
                "title": "Item Code"
              },
              "qty": {
                "type": "number",
                "exclusiveMinimum": 0,
                "title": "Qty"
              },
              "billing_period": {
                "type": "string",
                "enum": [
                  "monthly",
                  "annual"
                ],
                "title": "Billing Period",
                "default": "monthly"
              },
              "line_note": {
                "anyOf": [
                  {
                    "type": "string",
                    "maxLength": 140
                  },
                  {
                    "type": "null"
                  }
                ],
                "title": "Line Note"
              }
            },
            "type": "object",
            "required": [
              "item_code",
              "qty"
            ],
            "title": "CheckoutItemIn"
          },
          "type": "array",
          "maxItems": 100,
          "minItems": 1,
          "title": "Items"
        },
        "email": {
          "type": "string",
          "maxLength": 255,
          "title": "Email"
        },
        "name": {
          "anyOf": [
            {
              "type": "string",
              "maxLength": 255
            },
            {
              "type": "null"
            }
          ],
          "title": "Name"
        },
        "phone": {
          "anyOf": [
            {
              "type": "string",
              "maxLength": 64
            },
            {
              "type": "null"
            }
          ],
          "title": "Phone"
        },
        "shipping_option": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "title": "Shipping Option"
        },
        "shipping_address": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "title": "Shipping Address"
        },
        "origin": {
          "type": "string",
          "maxLength": 500,
          "title": "Origin"
        },
        "recurring": {
          "type": "boolean",
          "title": "Recurring",
          "default": false
        },
        "reseller_user_id": {
          "anyOf": [
            {
              "type": "string",
              "maxLength": 36
            },
            {
              "type": "null"
            }
          ],
          "title": "Reseller User Id"
        }
      },
      "type": "object",
      "required": [
        "items",
        "email",
        "origin"
      ],
      "title": "CheckoutIn"
    },
    "email": {
      "type": "string",
      "title": "Email"
    },
    "currency": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "title": "Currency"
    },
    "lines": {
      "items": {
        "properties": {
          "item_code": {
            "type": "string",
            "title": "Item Code"
          },
          "billing_period": {
            "type": "string",
            "title": "Billing Period",
            "default": ""
          },
          "line_note": {
            "type": "string",
            "title": "Line Note",
            "default": ""
          },
          "qty": {
            "type": "number",
            "title": "Qty"
          }
        },
        "type": "object",
        "required": [
          "item_code",
          "qty"
        ],
        "title": "BookOrderLineIn",
        "description": "One (item_code, billing_period, line_note) -> qty entry.\n\nA list rather than a dict because the real key is a TRIPLE — a cart can hold\none item at both Monthly and Annual, and two domain registrations are one\nitem_code differing only by the name bought. Collapsing to item_code loses a\nline and charges for one of two; see _price_and_book_website_order."
      },
      "type": "array",
      "maxItems": 100,
      "minItems": 1,
      "title": "Lines"
    },
    "website": {
      "anyOf": [
        {
          "properties": {
            "shipping_flat_base": {
              "anyOf": [
                {
                  "type": "number"
                },
                {
                  "type": "null"
                }
              ],
              "title": "Shipping Flat Base"
            },
            "shipping_flat_qty": {
              "anyOf": [
                {
                  "type": "integer"
                },
                {
                  "type": "null"
                }
              ],
              "title": "Shipping Flat Qty"
            },
            "shipping_flat_extra": {
              "anyOf": [
                {
                  "type": "number"
                },
                {
                  "type": "null"
                }
              ],
              "title": "Shipping Flat Extra"
            },
            "branding": {
              "additionalProperties": true,
              "type": "object",
              "title": "Branding"
            }
          },
          "type": "object",
          "title": "BookOrderWebsiteIn",
          "description": "The website settings this order has to be priced with, as they stand\non the INSTANCE that is rendering the shop.\n\nFlat-rate courier (``website_settings.courier_shipping``) and the\nVAT-inclusive display flags (``_vat_multiplier``) — everything else the\nbooks half needs it already has. Not optional in practice: defaulting the\nshipping trio to unset would ship every physical order free, and bs alone\nquotes R199 for the first three items."
        },
        {
          "type": "null"
        }
      ]
    }
  },
  "type": "object",
  "required": [
    "checkout",
    "email",
    "lines"
  ],
  "title": "BookOrderIn"
}

200 — Successful Response

json
{}

422 — Validation Error

json
{
  "properties": {
    "detail": {
      "items": {
        "properties": {
          "loc": {
            "items": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "integer"
                }
              ]
            },
            "type": "array",
            "title": "Location"
          },
          "msg": {
            "type": "string",
            "title": "Message"
          },
          "type": {
            "type": "string",
            "title": "Error Type"
          }
        },
        "type": "object",
        "required": [
          "loc",
          "msg",
          "type"
        ],
        "title": "ValidationError"
      },
      "type": "array",
      "title": "Detail"
    }
  },
  "type": "object",
  "title": "HTTPValidationError"
}

POST /api/v1/public/website/order-payment-ref ​

Order Payment Ref

Attach the Paystack reference (or the init error) to a booked order.

Small, but it is what lets the payment webhook find the order it paid for. The instance calls this synchronously and falls back to its outbox if the call fails — see checkout — because an order that never receives its reference is a payment that cannot be matched to anything.

Parameters

NameInRequiredTypeDescription
instancequeryyesstring
x-website-secretheaderno

Request body

json
{
  "properties": {
    "order_id": {
      "type": "integer",
      "title": "Order Id"
    },
    "reference": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "title": "Reference"
    },
    "error": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "title": "Error"
    }
  },
  "type": "object",
  "required": [
    "order_id"
  ],
  "title": "OrderPaymentRefIn"
}

200 — Successful Response

json
{}

422 — Validation Error

json
{
  "properties": {
    "detail": {
      "items": {
        "properties": {
          "loc": {
            "items": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "integer"
                }
              ]
            },
            "type": "array",
            "title": "Location"
          },
          "msg": {
            "type": "string",
            "title": "Message"
          },
          "type": {
            "type": "string",
            "title": "Error Type"
          }
        },
        "type": "object",
        "required": [
          "loc",
          "msg",
          "type"
        ],
        "title": "ValidationError"
      },
      "type": "array",
      "title": "Detail"
    }
  },
  "type": "object",
  "title": "HTTPValidationError"
}

POST /api/v1/public/website/ingest ​

Ingest

Accept one queued write from a websites instance, exactly once.

Returns 409 with the ORIGINAL result for a key already seen — which the sender treats as success, because that is what it is: the first attempt landed and only the acknowledgement was lost.

Parameters

NameInRequiredTypeDescription
instancequeryyesstring
x-website-secretheaderno
Idempotency-Keyheaderno

Request body

json
{
  "properties": {
    "kind": {
      "type": "string",
      "maxLength": 32,
      "minLength": 1,
      "title": "Kind"
    },
    "payload": {
      "additionalProperties": true,
      "type": "object",
      "title": "Payload"
    }
  },
  "type": "object",
  "required": [
    "kind",
    "payload"
  ],
  "title": "IngestIn"
}

200 — Successful Response

json
{}

422 — Validation Error

json
{
  "properties": {
    "detail": {
      "items": {
        "properties": {
          "loc": {
            "items": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "integer"
                }
              ]
            },
            "type": "array",
            "title": "Location"
          },
          "msg": {
            "type": "string",
            "title": "Message"
          },
          "type": {
            "type": "string",
            "title": "Error Type"
          }
        },
        "type": "object",
        "required": [
          "loc",
          "msg",
          "type"
        ],
        "title": "ValidationError"
      },
      "type": "array",
      "title": "Detail"
    }
  },
  "type": "object",
  "title": "HTTPValidationError"
}

Lubb One Documentation