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
- Visit dashboard / Endpoints
- Click New endpoint, give it a name (e.g. "Marketing site contact form")
- 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).
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:
- Open the endpoint in the dashboard, scroll to Security
- Click Enable signing, copy the secret (shown once)
- Store the secret as
YORKYY_ENDPOINT_SECRETin your env - 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:
| Field | Purpose |
|---|---|
_gotcha (default) | Honeypot. Submissions with this filled are silently dropped. Field name is configurable per-endpoint. |
cf-turnstile-response | Cloudflare Turnstile token, when Turnstile is enabled on the endpoint. |
Anything starting with _yorkyy | Reserved 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
| Status | Code | When |
|---|---|---|
| 400 | invalid_body | Body couldn't be parsed. |
| 400 | empty_payload | No usable fields. |
| 400 | captcha_required | Turnstile token missing. |
| 400 | captcha_failed | Turnstile token invalid or expired. |
| 401 | invalid_signature | HMAC signing required but signature missing or wrong. |
| 403 | origin_not_allowed | Origin not in the endpoint's allowlist. |
| 404 | endpoint_not_found | Public ID doesn't match any endpoint. |
| 413 | payload_too_large | Request body exceeds 32KB. |
| 415 | unsupported_content_type | Body isn't JSON or form-encoded. |
| 429 | rate_limited | Per-IP or total rate limit exceeded. Honor Retry-After. |
| 503 | endpoint_disabled | Endpoint 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.