v1 API

In beta

Identity

Login and account management for the people who visit your own site, separate from the staff who edit content in your Sedgemark dashboard.

Identity is in beta

Enabled per workspace on request. If your workspace does not have it yet, contact support@getsedgemark.com. Everything below describes the surface as it works once it is turned on.

Accounts are not users

Sedgemark already has one notion of a person: a user: a member of your team, signed in to the dashboard, editing content. An account is a completely different thing: someone who visits your site and signs in there, a subscriber, a customer, a forum member. Different table, different auth, different session type. A workspace can have any number of accounts and they never appear anywhere in the dashboard’s user list.

What accounts are for: gating a collection so only signed-in visitors (or visitors who satisfy a rule you define) can read it over the API. See Content API → Visibility for the gated tier this unlocks.

The public endpoints

All under /api/v1/accounts, public and unauthenticated except where noted, and sharing the platform’s ordinary 100/min/IP budget: see Errors & rate limits.

EndpointDoes
POST /accounts/signupStart self-registration. Verify-first, see below.
POST /accounts/verifyFinish signup with the emailed token. This is where the account row is actually created.
POST /accounts/loginEmail + password in, a bearer session token out.
POST /accounts/logoutRequires a session token. Revokes it.
GET /accounts/meRequires a session token. The signed-in account’s own record.
POST /accounts/password-resetAlways 202, whether or not the address has an account: no address-enumeration oracle.
POST /accounts/password-reset/confirmSets a new password with the emailed token.
GET /accounts/fieldsPublic field descriptor, see Custom fields below.
A disabled or unconfigured workspace answers a uniform 404

When Identity is off for a workspace, or registration is closed, every one of these endpoints answers a plain 404, the same body a route that does not exist would return. The public must not be able to read a workspace’s identity configuration off a status code.

Signup is verify-first

POST /accounts/signup does not create an account. It stores a pending signup and emails a verification link to your own configured verify page, the same shape as Sedgemark’s own workspace signup. The account row is created only when the visitor clicks through and POST /accounts/verify consumes that token. An address only becomes a real account after its owner has proven they can read mail sent to it.

A repeat signup to an address that already has an account gets a plain 409 email_taken, see Errors & rate limits → Identity codes.

Logging in

http
POST https://acme.sedgemark.app/api/v1/accounts/login
Content-Type: application/json

{ "email": "visitor@example.com", "password": "correct horse battery staple" }
json
{
  "ok": true,
  "token": "8f14e45f-cea5-4c2b-9d31-77b0a1f6c3de...",
  "expires_at": "2026-09-03T15:04:05.000Z",
  "account": {
    "id": "3b1e7c40-88a2-4d19-9f5c-6a0b1d2e3f40",
    "email": "visitor@example.com",
    "created_at": "2026-01-04T14:58:11.000Z",
    "custom_fields": { "plan_tier": "free" }
  }
}

One generic 401 for an unknown address and a wrong password alike: there is no way to tell which from the response. A deactivated account is named only after the password verifies (403 user_disabled), since naming it earlier would let a caller probe for which addresses have accounts at all.

Sessions

token is an opaque bearer credential (not a JWT, nothing to decode), sent the same way an API key is sent, Authorization: Bearer. It is fixed at 30 days with no renewal or sliding expiry: when it expires, the visitor logs in again.

bash
curl https://acme.sedgemark.app/api/v1/accounts/me \
  -H "Authorization: Bearer $ACCOUNT_SESSION_TOKEN"

POST /accounts/logout revokes the token immediately: it stops working on the very next request, everywhere it is used. There is no per-device session list on the public API; from the dashboard, staff holding the accounts grant can sign an account out of every session at once.

Using a session to read gated content

Once you have a token, send it as a bearer credential against a gated collection exactly like a normal read:

bash
curl https://acme.sedgemark.app/api/v1/member_post \
  -H "Authorization: Bearer $ACCOUNT_SESSION_TOKEN"

Full behavior (what happens with no rule attached, what a failing rule returns, and how an API key interacts with a gated collection) is Content API → Visibility.

Custom fields on accounts

Beyond email and password, a workspace can define its own fields on accounts (a plan tier, a signup source, a marketing opt-in) up to 50 definitions. The type list is deliberately the same bounded set a form field uses, because signup is a public endpoint:

Field type
text
integer
decimal
boolean
date
datetime
enum

Each definition has a set_on_signup flag deciding whether the public signup endpoint can accept a value for it. Only flagged fields are collectible there; anything else sent is dropped and echoed back in an ignored array, the same convention form submission uses. Staff can set or override any field’s value from the dashboard regardless of the flag.

GET /accounts/fields is the public descriptor: only the set_on_signup definitions, each with a constraints block (control element, input type, bounds, enum values) shaped exactly like a form field’s, so your own signup page can render and client-side-validate them without hardcoding Sedgemark’s field semantics.

Rules: what "gated" can mean beyond "signed in"

Attach nothing to a gated collection and it means exactly one thing: any signed-in account may read it. Attach a rule and it narrows that to accounts matching conditions you define: an account created before a date, a custom field set to a particular value, and combinations of both. Rules are managed from the dashboard’s own /rules page, not over the public API.

A rule’s conditions test one field type at a time, each with its own operators:

Field typeOperators
booleaneq
datetimebefore, after, older_than_days
numbereq, gt, gte, lt, lte
texteq, in
enumeq, in

Conditions combine into an AND/OR tree, up to 10 conditions total and 3 levels of nesting; an in condition takes up to 20 values. A visitor who does not satisfy the attached rule gets 403 access_rule_not_satisfied with no detail about what the rule actually tests for: the refusal must not teach a stranger the shape of your gate.

A key with entries:read bypasses every rule

An API key holding entries:read already reads private content, and a gated type is no exception: the rule only ever applies to an account session. This is deliberate: carving out a gated-only exception for keys would make key behavior depend on a visibility tier nothing else does.

Managing accounts from the dashboard

List, search, create with an invite email, deactivate, sign out everywhere, and delete, all from Accounts in the dashboard, gated behind the accounts permission grant. Registration, the origin allowlist for browser calls, and the emailed-link page URLs live under Settings → Identity, owner-only: those URLs and the allowlist are credential-adjacent, the same line webhooks sit behind.

There is no MCP surface for account rows at all: an agent holding users:read can list your custom field definitions ( list_user_fields) and nothing else. See MCP server → Accounts.