Invite a teammate by email
const url = 'http://localhost:8080/v1/members/invite';const options = { method: 'POST', headers: { cookie: 'hardcount_session=<hardcount_session>', 'Content-Type': 'application/json' }, body: '{"email":"hello@example.com"}'};
try { const response = await fetch(url, options); const data = await response.json(); console.log(data);} catch (error) { console.error(error);}curl --request POST \ --url http://localhost:8080/v1/members/invite \ --header 'Content-Type: application/json' \ --cookie hardcount_session=<hardcount_session> \ --data '{ "email": "hello@example.com" }'Creates a pending invitation (7-day expiry, one pending per
email) and sends the accept link via the transactional email
outbox. Requires DASHBOARD_BASE_URL — accept links are never
derived from request headers. 409 when the email is already a
member or already has a pending invite. ADR-081.
Authorizations
Section titled “Authorizations”Parameters
Section titled “Parameters”Header Parameters
Section titled “Header Parameters”Makes the call safe to retry. Send a unique value (a UUID works) and
the same value on every retry. For 24 hours, a repeat with the same
key, method, path and body gets the first response again, with an
Idempotent-Replayed: true header, instead of running again. The
error cases are in docs/api-surface.md. The usage routes do not take
it: each usage event carries its own idempotency_key.
Request Bodyrequired
Section titled “Request Bodyrequired”object
Example generated
{ "email": "hello@example.com"}Responses
Section titled “Responses”Invitation created and email enqueued
object
Recorded for the future role split; not enforced (every member holds the full permission set).
Server-derived; treat as authoritative.
Example
{ "id": "hc_minv_1a2b3c4d5e6f7890", "status": "pending"}No invitation was created when the code is
email_not_configured (this server has no SMTP set up, so the
accept link could not be delivered) or invalid_state
(DASHBOARD_BASE_URL is not set). Also returned when the email
is already a member or already has a pending invitation.
object
object
One of invalid_request_error, authentication_error,
rate_limit_error or api_error. Every error the API
answers has this shape, including an unknown path (404,
code route_not_found) and a method the path does not take
(405, code method_not_allowed, with an Allow header).
The few exceptions are listed in docs/api-surface.md.
The request field the error is about. Sent only when the error
names one. A request body may hold only the fields its
operation lists, spelled and cased as listed: any other
field, or one the server sets (such as id), is refused
with 422 and param naming it. An operation with no request body lists no fields: send
no body, {} or null. The Stripe and Postmark webhooks and
the LiteLLM callback take their sender’s body as it comes.
Example
{ "error": { "type": "invalid_request_error" }}Invalid email
object
object
One of invalid_request_error, authentication_error,
rate_limit_error or api_error. Every error the API
answers has this shape, including an unknown path (404,
code route_not_found) and a method the path does not take
(405, code method_not_allowed, with an Allow header).
The few exceptions are listed in docs/api-surface.md.
The request field the error is about. Sent only when the error
names one. A request body may hold only the fields its
operation lists, spelled and cased as listed: any other
field, or one the server sets (such as id), is refused
with 422 and param naming it. An operation with no request body lists no fields: send
no body, {} or null. The Stripe and Postmark webhooks and
the LiteLLM callback take their sender’s body as it comes.
Example
{ "error": { "type": "invalid_request_error" }}