Endpoints

Endpoints are universal ingestion URLs. Point your existing forms at one and yorkyy captures every submission — no schema, no setup beyond a name. Works with regular HTML, React/Next, mobile apps, and server-to-server.

When to use endpoints

Use endpoints when:

  • You already have a form on a site or app and just want yorkyy to receive its submissions
  • You need server-to-server ingestion (logs, alerts, contact-form proxies)
  • You're building a custom UI in React/Vue/Svelte and want to keep your existing component code

Use iframe embeds instead when you want a fully hosted form with no code on your side. Use the REST API when you've built a form in yorkyy and need to read its submissions.

Create your first endpoint

  1. Visit dashboard / Endpoints
  2. Click New endpoint, give it a name (e.g. "Marketing site contact form")
  3. Copy the endpoint URL — looks like https://yorkyy.com/api/ingest/aB3xK9mQ…

That's the URL you'll point your form at. The endpoint is live immediately.

Integration: HTML form action

The simplest path. No JavaScript needed. Edit your <form> tag's action attribute:

<form action="https://yorkyy.com/api/ingest/aB3xK9mQ..." method="POST">
  <label>
    Your name
    <input name="name" required>
  </label>

  <label>
    Email
    <input type="email" name="email" required>
  </label>

  <label>
    Message
    <textarea name="message" required></textarea>
  </label>

  <!-- Honeypot: bots fill this; humans don't see it. -->
  <input type="text" name="_gotcha" style="display:none" tabindex="-1" autocomplete="off">

  <button type="submit">Send</button>
</form>

That's it. Every field's name attribute becomes the field label in your notifications. After submitting, the user is redirected to a success page (configurable on the endpoint).

Heads up: pure HTML forms cannot be signed.

HMAC signing requires computing a hash of the request body before sending — that's not something a <form action="..."> can do without JavaScript. If you enable signing on an endpoint, plain HTML form POSTs to it will get rejected with 401.

Your options for a public-website form:

  • Don't enable signing. For public web forms, signing doesn't add real security (the secret would have to live in the page anyway). Use origin allowlist + Turnstile + honeypot instead — that's the right defense stack for browser-context endpoints.
  • Intercept the submit with JavaScript and POST as JSON with a signature. See the "React / SPA" section below for the pattern. The signing secret still needs to be accessible from the client, so this only helps if you proxy through your own backend.
  • Server-to-server only: use signing when both ends are under your control (a backend service POSTing to yorkyy). HTML form-action is the wrong pattern for that anyway.

Integration: React / Next / SPA (fetch)

For interactive UIs where you want to stay on the page after submission:

async function handleSubmit(values: { name: string; email: string; message: string }) {
  const res = await fetch("https://yorkyy.com/api/ingest/aB3xK9mQ...", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify(values),
  });

  if (!res.ok) {
    const err = await res.json();
    throw new Error(err.error.message);
  }

  const { submission_id } = await res.json();
  // Show your own success UI
}

Or with the official SDK:

import { ingest } from "@yorkyy/sdk";

const result = await ingest("aB3xK9mQ...", {
  name: values.name,
  email: values.email,
  message: values.message,
});

console.log(result.submission_id);

Integration: server-to-server (HMAC signed)

For backend services pushing data to yorkyy (log events, alerts, etc.), enable HMAC signing on the endpoint so only your trusted backend can submit. Steps:

  1. Open the endpoint in the dashboard, scroll to Security
  2. Click Enable signing, copy the secret (shown once)
  3. Store the secret as YORKYY_ENDPOINT_SECRET in your env
  4. Sign every request before sending
import { createHmac } from "node:crypto";

const ENDPOINT_URL = "https://yorkyy.com/api/ingest/aB3xK9mQ...";
const SECRET = process.env.YORKYY_ENDPOINT_SECRET!;

async function ingest(data: Record<string, unknown>) {
  const t = Math.floor(Date.now() / 1000);
  const body = JSON.stringify(data);
  const sig = createHmac("sha256", SECRET).update(`${t}.${body}`).digest("hex");

  const res = await fetch(ENDPOINT_URL, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "X-Yorkyy-Signature": `t=${t},v1=${sig}`,
    },
    body,
  });

  if (!res.ok) throw new Error(`Ingest failed: ${res.status}`);
  return res.json();
}

The signature format is identical to outgoing webhook signatures — if you've written verification code for those, the signing code is symmetric.

Field handling

  • Field names become labels in your notifications, exactly as sent
  • Values are stored as strings (numbers and booleans are stringified)
  • Arrays of values (HTML checkboxes with the same name) are joined with ,
  • Objects (nested JSON) are JSON-stringified
  • Maximum 50 fields per submission
  • Maximum 10KB per field value (longer values are truncated with … (truncated))
  • Maximum 32KB total request body

Reserved fields

Some field names are special and never appear in your notifications:

FieldPurpose
_gotcha (default)Honeypot. Submissions with this filled are silently dropped. Field name is configurable per-endpoint.
cf-turnstile-responseCloudflare Turnstile token, when Turnstile is enabled on the endpoint.
Anything starting with _yorkyyReserved for future use. Always stripped from stored data.

Responses

JSON submissions

JSON POSTs receive a JSON response:

{
  "ok": true,
  "submission_id": "01h..."
}

HTML form submissions

Form-encoded POSTs (the kind a plain HTML <form> sends) get a 303 redirect to the endpoint's configured success URL. If no success URL is set, they receive the same JSON response above.

Error responses

StatusCodeWhen
400invalid_bodyBody couldn't be parsed.
400empty_payloadNo usable fields.
400captcha_requiredTurnstile token missing.
400captcha_failedTurnstile token invalid or expired.
401invalid_signatureHMAC signing required but signature missing or wrong.
403origin_not_allowedOrigin not in the endpoint's allowlist.
404endpoint_not_foundPublic ID doesn't match any endpoint.
413payload_too_largeRequest body exceeds 32KB.
415unsupported_content_typeBody isn't JSON or form-encoded.
429rate_limitedPer-IP or total rate limit exceeded. Honor Retry-After.
503endpoint_disabledEndpoint is paused. Re-enable in the dashboard.

Routing submissions

Each endpoint can route to specific channels (email, Telegram, webhook) or fall back to your account's default channels. Configure this under "Where submissions go" on the endpoint's settings page.

Security checklist

Before pointing a public form at an endpoint, set the appropriate protections:

  • Public HTML form (marketing site) — origin allowlist + Turnstile + honeypot
  • SPA with custom UI — origin allowlist + Turnstile
  • Server-to-server — HMAC signing (no origin restriction needed)
  • Mobile app — HMAC signing with the secret in your backend (never in the app bundle)

Full details: security.

Quotas and accounting

Every endpoint submission is recorded with its source, endpoint ID, and timestamp. We don't enforce per-account quotas yet, but submission counts will count toward your plan's monthly limit once plans launch. Endpoint submissions count the same as form submissions.