# FYInbox — full documentation Canonical index: https://fyinbox.com/llms.txt OpenAPI: https://fyinbox.com/openapi.json Documentation routes: https://fyinbox.com/docs, https://fyinbox.com/docs/quickstart, https://fyinbox.com/docs/concepts, https://fyinbox.com/docs/authentication, https://fyinbox.com/docs/notification-payload, https://fyinbox.com/docs/deduplication, https://fyinbox.com/docs/typescript-sdk, https://fyinbox.com/docs/errors-and-retries, https://fyinbox.com/docs/limits, https://fyinbox.com/docs/api-reference, https://fyinbox.com/docs/security-and-privacy, https://fyinbox.com/docs/compatibility, https://fyinbox.com/docs/changelog # Quickstart Send one structured notification to your personal inbox with a source API key and a single HTTP request. ## Before you start - Create a source in the dashboard and copy its API key when it is shown. The secret is displayed once. - Set NOTIFICATIONS_URL to the dashboard origin and NOTIFICATIONS_API_KEY to that source key. - Keep the key on the server or in a secret store. Never embed it in browser code, a public repository, or a notification payload. ## Send the notification ```bash #!/usr/bin/env bash set -euo pipefail : "${NOTIFICATIONS_URL:?Set NOTIFICATIONS_URL to your FYInbox origin}" : "${NOTIFICATIONS_API_KEY:?Set NOTIFICATIONS_API_KEY to a source API key}" curl --fail-with-body --silent --show-error \ --request POST \ --url "${NOTIFICATIONS_URL%/}/api/v1/notifications" \ --header "Authorization: Bearer $NOTIFICATIONS_API_KEY" \ --header "Content-Type: application/json" \ --data-binary @- <<'JSON' { "title": "Deployment finished", "body": "billing-api is healthy in production.", "severity": "success", "tags": ["production", "deploy"], "source": "billing-api", "externalId": "deploy-abc123", "deduplicationKey": "deploy:billing-api:abc123", "metadata": { "environment": "production", "commit": "abc123" }, "actions": [ { "id": "open-deploy", "type": "link", "label": "Open deployment", "href": "https://example.com/deployments/abc123", "style": "primary" } ] } JSON ``` > **Replace the event identity.** Use your own externalId and deduplicationKey. A retry with the same deduplicationKey returns the original notification instead of creating another one. ## Read the response A new notification returns HTTP 201 with { id, created: true }. A deduplicated retry returns HTTP 200 with the same id and created: false. The notification is then available in the authenticated inbox. - [Authentication](/docs/authentication) - [Deduplication](/docs/deduplication) - [OpenAPI 3.1](/openapi.json) --- # Concepts Understand the small set of account, source, notification, tag, metadata, action, and push concepts used by the product. ## One personal notification inbox FYInbox is a managed SaaS inbox for events produced by scripts, services, no-code automations, and agents. It keeps machine notifications out of chat and email while preserving a filterable history. The current product model is one person per account; teams are not part of this version. ## Sources and API keys A source groups notifications from one producer or automation. Its bearer API key selects the source and account on the server. The optional source field inside a payload is descriptive data; it cannot change the source selected by the key. > **Scope is server-derived.** Producer requests do not choose account or project identifiers. A key can only create and update notifications inside its own source scope. ## Structure and delivery - Tags provide normalized labels for filtering and triage. - Metadata stores bounded string key/value pairs for exact filters and later context. - Actions are explicit HTTPS or mailto links shown with a notification. - Web Push is an optional delivery view of the same inbox item and can be configured per device and source. --- # API authentication Authenticate producer and agent requests with a source-scoped bearer key while keeping the secret out of clients, payloads, logs, and source control. ## Use the source API key Send the key in the Authorization header as Bearer . The key can create notifications and read or mutate metadata only for its own source. The public API does not use dashboard cookies, account IDs, or project IDs. Missing, invalid, or revoked keys return HTTP 401 with { error: "unauthorized" }. ## Handle the key as a secret - Read the key from an environment variable or secret store on a trusted server or automation runtime. - Do not use the key from browser JavaScript or ship it in a mobile or desktop bundle. - Do not place it in title, body, tags, metadata, actions, URLs, error messages, or logs. - Revoke a key in the dashboard if it may have been exposed and create a replacement. - [Security and privacy](/docs/security-and-privacy) --- # Notification payload Start with a title, then add severity, body, tags, metadata, event identity, timestamp, and safe link actions only when useful. ## Fields | Field | Required | Meaning | | --- | --- | --- | | title | Yes | Short human-readable event summary. | | body | No | Plain-text detail; it is never rendered as HTML. | | severity | No | info, success, warning, error, or critical; defaults to info. | | tags | No | Normalized labels; defaults to an empty list. | | source | No | Descriptive producer label; it does not select key scope. | | externalId | No | Identifier from the producing system. | | deduplicationKey | No | Stable event identity used for safe create retries. | | metadata | No | Bounded string key/value context for filtering. | | actions | No | Up to five HTTPS or mailto link actions. | | createdAt | No | Producer event time as an ISO 8601 date-time with offset. | ## Validation and normalization - The request is a strict JSON object; unknown fields are rejected. - Title, tags, identifiers, and metadata keys are trimmed within their documented bounds. - Tags are lowercased, converted to slugs, and deduplicated by normalized slug. - Metadata keys are normalized to lowercase and collisions after normalization are rejected. - Action IDs must be unique and at most one action may use the primary style. - [Create request JSON Schema](/json-schema/create-notification.request.schema.json) - [Protocol limits](/docs/limits) --- # Deduplication and retries Give each logical event a stable source-scoped key so a network retry cannot create a second inbox item while the original is retained. ## Build a stable key Derive deduplicationKey from immutable event identity, not the retry attempt or current time. Useful shapes include deploy::, run::, and invoice::paid. The same string may be reused by another source because uniqueness is scoped to the source selected by the API key. ## What a duplicate means - The first accepted request returns 201 and created: true. - A repeat returns 200, created: false, and the original notification id. - The service does not compare the repeated payload with the original payload. - The guarantee lasts while the original notification exists; retention can eventually permit the key to create a new item. > **Retry only with event identity.** After a timeout or lost response, automatically retry create only when a deduplicationKey was sent. Without one, the server may already have committed the first request. --- # TypeScript SDK Use the server-only TypeScript client for runtime validation, bounded responses, safe errors, deadlines, and conservative retry behavior. ## Current availability > **Private preview.** The TypeScript package currently ships inside the FYInbox workspace and is not published to a public package registry. Use the cURL or HTTP contract until package distribution is announced. ## Create a client ```typescript import { NotificationsClient, NotificationsError } from "@notifications/sdk"; function requiredEnv( name: "NOTIFICATIONS_API_KEY" | "NOTIFICATIONS_URL", ): string { const value = process.env[name]; if (value === undefined || value.length === 0) { throw new Error(`Set ${name} before running this example.`); } return value; } const client = new NotificationsClient({ apiKey: requiredEnv("NOTIFICATIONS_API_KEY"), baseUrl: requiredEnv("NOTIFICATIONS_URL"), }); try { const result = await client.createNotification({ title: "Deployment finished", body: "billing-api is healthy in production.", severity: "success", tags: ["production", "deploy"], source: "billing-api", externalId: "deploy-abc124", deduplicationKey: "deploy:billing-api:abc124", metadata: { environment: "production", commit: "abc124", }, actions: [ { id: "open-deploy", type: "link", label: "Open deployment", href: "https://example.com/deployments/abc124", style: "primary", }, ], }); process.stdout.write(`${JSON.stringify(result)}\n`); } catch (error) { const safeError = error instanceof NotificationsError ? error.toJSON() : { kind: "unexpected_error" }; process.stderr.write(`${JSON.stringify(safeError)}\n`); process.exitCode = 1; } ``` ## Safety behavior - Call getNotification(id) to retrieve the complete stored context for a notification in the API key's source. - The default total deadline is 10 seconds and can be overridden per client or request. - Read requests are safe to retry after network and transient server failures. - At most two retries are attempted, and ambiguous create failures are retried only when the request has a deduplicationKey. - Metadata mutations are not repeated after ambiguous network or server failures. - Typed errors omit the API key, Authorization header, request payload, and untrusted server message. - The client fails closed in browser runtimes because source API keys are server-side credentials. - [Errors and retries](/docs/errors-and-retries) - [Compatibility](/docs/compatibility) --- # Errors and retries Branch on HTTP status and stable error code, preserve unknown codes, honor Retry-After, and avoid replaying ambiguous non-idempotent mutations. ## Public error contract | HTTP | Code | Meaning | | --- | --- | --- | | 400 | invalid_request | JSON, fields, or values are invalid. | | 401 | unauthorized | The bearer key is missing, invalid, or revoked. | | 404 | not_found | The scoped notification or source no longer exists. | | 409 | conflict | The requested metadata mutation conflicts with current state. | | 409 | quota_exceeded | The source cannot store another notification. | | 413 | payload_too_large | The raw HTTP body exceeds the request limit. | | 429 | rate_limited | The shared per-key request window is exhausted. | | 500 | internal_error | The server could not complete the request. | Errors use { error, message? }. Clients should retain unknown non-empty error codes so the service can add a new code without turning a parseable failure into an opaque one. ## Retry policy 1. **Validate locally.** Fix 400, 401, 404, 409, and 413 responses before sending the same operation again. 2. **Honor rate limiting.** For 429, wait for Retry-After when the delay fits your total deadline, then retry conservatively. 3. **Treat ambiguity explicitly.** A notification read is safe to retry. A network failure or 5xx may happen after a write commits: retry create only with a stable deduplicationKey, and do not automatically replay metadata mutations. 4. **Keep diagnostics safe.** Record status, stable error code, and X-Request-ID when present, but never log the bearer key or full sensitive payload. --- # Limits Design producers below the public protocol ceilings and expect the running deployment or account policy to enforce lower operational values. ## Public protocol ceilings | Resource | Maximum | | --- | --- | | Raw request body | 131072 bytes | | Title | 200 characters | | Notification body | 65536 UTF-8 bytes | | Tags | 100 protocol / 20 default validation | | Metadata | 50 pairs / 32768 serialized bytes | | Actions | 5 | | Deduplication key | 255 characters | > **Runtime validation is authoritative.** JSON Schema cannot express every normalization and UTF-8 rule. Validate against the runtime contract or SDK, and do not assume every protocol ceiling is enabled for an account. ## Operational policy The deployment can apply lower payload, tag, metadata, request-rate, storage, and retention limits. A deduplicated create does not consume a new stored-notification slot. Inspect current account usage in Settings and handle quota_exceeded and rate_limited as documented errors; no commercial plan or price is implied by these safety guards. - [OpenAPI limits](/openapi.json) - [Errors](/docs/errors-and-retries) --- # API reference Use the versioned API for notification creation, agent-readable notification context, and scoped metadata mutation; dashboard and account endpoints are not public APIs. ## Public v1 operations | Method | Path | Result | | --- | --- | --- | | GET | /api/v1/notifications/{id} | Return the complete stored context for a notification owned by the key's source. | | POST | /api/v1/notifications | Create or deduplicate a notification. | | POST | /api/v1/notifications/{id}/metadata | Add metadata keys that do not yet exist. | | PATCH | /api/v1/notifications/{id}/metadata | Modify metadata keys that already exist. | > **Same-source authorization.** Read and metadata operations can address only a notification owned by the source and account selected by the bearer key. A notification id identifies a resource but never authorizes access; missing and out-of-scope ids both return not_found. ## Machine-readable contracts - [OpenAPI 3.1](/openapi.json) - [Create request schema](/json-schema/create-notification.request.schema.json) - [Public error schema](/json-schema/public-error.response.schema.json) - [Agent index](/llms.txt) --- # Security and privacy Minimize notification data, keep producer credentials server-side, and understand what the managed service must decrypt to provide inbox and filtering features. ## Data handling Sensitive business fields are encrypted by the application before PostgreSQL persistence and tenant-owned access is account-scoped. The managed application holds the keys needed to render and filter the inbox; this is not a client-held-key design. Use TLS for requests and send only the context needed to recognize and act on an event. - Good metadata is a bounded operational identifier, status, environment, version, or non-secret reference. - Do not send passwords, session tokens, API keys, authorization headers, cookies, private keys, recovery codes, or raw payment credentials. - Avoid personal data and full customer content when a pseudonymous identifier or dashboard link is enough. - Treat notification titles, bodies, metadata, and action URLs as stored account data. ## Credentials and actions Source API keys are high-entropy secrets shown once; the service stores a cryptographic digest for authentication. Actions accept only HTTPS and mailto URLs without embedded credentials. They are links, not arbitrary code execution, and the user chooses whether to open them. - [Authentication](/docs/authentication) - [Payload fields](/docs/notification-payload) --- # Compatibility policy Integrate against the versioned contract, accept additive response evolution, and use stable error codes and schemas instead of dashboard internals. ## Versioning boundary The notification API is versioned in its /api/v1 path and its OpenAPI document reports version 1.0.0. Public compatibility covers the four operations in the API reference. Dashboard routes, cookies, account lifecycle endpoints, inbox loaders, source management, API-key management, and push endpoints are internal and may change without a public API version. ## Forward-compatible clients - Ignore additive response fields unless strict validation is intentionally pinned to an artifact version. - Preserve unknown non-empty error codes and branch on known codes when behavior differs. - Use the checked OpenAPI and JSON Schemas as the machine contract, while treating documented runtime normalization as authoritative. - Do not automate undocumented dashboard routes or depend on internal database identifiers. - [API reference](/docs/api-reference) - [Changelog](/docs/changelog) --- # Changelog Track public producer API, machine-contract, SDK, and documentation changes without mixing in private dashboard implementation details. ## 2026-08-19 — source-scoped notification reads - Added GET /api/v1/notifications/{id} for complete agent-readable notification context. - Scoped every read to the account and source selected by the bearer API key, with opaque not_found responses outside that scope. - Added NotificationsClient.getNotification(id) with response validation and safe retries for transient failures. - [API reference](/docs/api-reference) - [Authentication](/docs/authentication) ## 2026-08-01 — v1 initial contract - Documented notification create and source-scoped metadata add/modify operations. - Published deterministic OpenAPI 3.1 and JSON Schema artifacts. - Documented create deduplication, error codes, protocol ceilings, security guidance, and compatibility policy. - Added canonical cURL and private-preview TypeScript examples plus agent discovery documents. - [Quickstart](/docs/quickstart) - [Compatibility](/docs/compatibility) ---