Invite a partner into the household (admin only)
const url = 'https://api.tovarifinancial.com/invites';const options = { method: 'POST', headers: {Authorization: 'Bearer <token>', '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 https://api.tovarifinancial.com/invites \ --header 'Authorization: Bearer <token>' \ --header 'Content-Type: application/json' \ --data '{ "email": "hello@example.com" }'A household ADMIN invites one person, by email, into their household. The server stores only a hash of the invite token and emails the accept link to the invitee — that email is the ONLY place the link ever goes (KAN-3109): acceptUrl is ALWAYS null in this response, on a create and on a replay. It is kept, nullable, so the response shape is unchanged for existing clients; a client must not build, expect or offer a copyable link. emailSent reports whether the email provider accepted the invite email; when it is false, delivery could not be confirmed and the admin should revoke the invite and send it again.
Idempotency: X-Idempotency-Key is optional. When it matches an invite this household already created with that key, the answer is 200 with THAT invite as it stands now (its status may since have moved on), emailSent: false, and nothing is written or re-sent — a drifted body is never applied. A first-time create answers 201. IDEMPOTENCY_CONFLICT is not reachable on this operation.
Refusals, in the order they are checked: 403 FORBIDDEN for a caller who is not a household admin; 409 when the address already belongs to an active member, an invite is already pending for it, or the household is full; 429 INVITE_SEND_THROTTLED when the household has used its daily invite sends. A 403 DATABASE_ERROR is also possible if the row-level policy itself refuses the write — a code the 403 schema below does not enumerate.
Authorizations
Section titled “Authorizations”Parameters
Section titled “Parameters”Header Parameters
Section titled “Header Parameters”Optional idempotency key for safe write retries. Every operation that declares this header reads it. Three cases, told apart per operation: (1) where the operation also documents a 409 whose codes include IDEMPOTENCY_CONFLICT, replaying this key with a different payload returns 409 IDEMPOTENCY_CONFLICT and replaying it with the same payload returns the original response; (2) where it documents no 409, a replay is reconciled optimistically against a unique key column and answers the original 200; (3) where it documents a 409 whose codes do NOT include IDEMPOTENCY_CONFLICT, a conflict on this key is impossible — a replay answers a 200, and that 409 is a resource-state conflict described by the operation’s own 409 schema.
Request Bodyrequired
Section titled “Request Bodyrequired”object
The invitee’s address. Trimmed and lower-cased by the server before it is validated and stored.
Examplegenerated
{ "email": "hello@example.com"}Responses
Section titled “Responses”Replay of an X-Idempotency-Key this household already used — the existing invite, nothing re-sent
object
object
The invitee address, lower-cased.
Always pending on a 201. A 200 replay returns the invite as it stands now, which may have been accepted or revoked since.
ALWAYS null (KAN-3109). The raw invite token leaves the server only inside the invite email, so this response never carries an accept link. Kept, nullable, so the response shape is unchanged for existing clients.
Whether the email provider accepted the invite email. Always false on a 200 replay (nothing is re-sent) and whenever sending is disabled or failed.
Example
{ "data": { "status": "pending", "acceptUrl": null }}Invite created (the invite email is attempted after the create commits)
object
object
The invitee address, lower-cased.
Always pending on a 201. A 200 replay returns the invite as it stands now, which may have been accepted or revoked since.
ALWAYS null (KAN-3109). The raw invite token leaves the server only inside the invite email, so this response never carries an accept link. Kept, nullable, so the response shape is unchanged for existing clients.
Whether the email provider accepted the invite email. Always false on a 200 replay (nothing is re-sent) and whenever sending is disabled or failed.
Example
{ "data": { "status": "pending", "acceptUrl": null }}Validation failed — see errorCode / fieldErrors
object
Machine-readable error code (AUTH_ERROR_CODES).
Optional field-level validation errors, keyed by field name.
object
Example
{ "errorCode": "INVALID_EMAIL_FORMAT"}Missing, invalid, or expired bearer token
object
Machine-readable error code (AUTH_ERROR_CODES).
Optional field-level validation errors, keyed by field name.
object
Example
{ "errorCode": "INVALID_EMAIL_FORMAT"}Authenticated, but the tenant gate refuses the request until the caller resolves a precondition. errorCode is one of EMAIL_NOT_VERIFIED, POLICY_ACCEPTANCE_REQUIRED, or SUBSCRIPTION_REQUIRED, evaluated in exactly that order (contract term CCR-1: email verification first, then policy acceptance, then subscription — so a subscriber who has merely not re-accepted the current policies always sees POLICY_ACCEPTANCE_REQUIRED). A POLICY_ACCEPTANCE_REQUIRED body additionally carries details.outstanding (the policy versions still to accept) and details.firstAcceptance. The SUBSCRIPTION_REQUIRED arm is INERT unless the server-side BILLING_ENABLED flag is exactly the string true; while it is off, only the first two codes are reachable. Not retryable as sent — resolve the named condition, then resend. This operation can ALSO answer 403 with errorCode FORBIDDEN, which is a refusal by the caller’s HOUSEHOLD ROLE rather than by the tenant gate: only a household admin may perform it, and a member is refused before anything is read or written. That arm carries no details and is not cleared by resolving anything on the account — it needs an admin. It is INDEPENDENT of BILLING_ENABLED, so the reachable set here is three codes today (the two gate codes above plus FORBIDDEN) and four once billing is on. The errorCode enum on this operation’s response schema is the complete, machine-readable list.
object
Which condition refused the request. The first three are the tenant gate, evaluated in that order (CCR-1), and SUBSCRIPTION_REQUIRED is unreachable while BILLING_ENABLED is not exactly true. FORBIDDEN is a refusal by the target resource itself (or, on POST /invites, by the caller’s household role) and carries no details.
object
Active policy versions the user has not yet accepted.
object
True when the user has accepted no policy before — the client renders the new-signup screen rather than the re-acceptance one.
Example
{ "errorCode": "EMAIL_NOT_VERIFIED"}The household state refuses the invite: USER_ALREADY_EXISTS (already an active member), VALIDATION_ERROR (an invite is already pending for this address), or HOUSEHOLD_FULL (the household has no free seat). Nothing was written and no email was sent. A replay of a used key answers 200, never this status.
object
Which household-state conflict refused the invite. Nothing was written and no email was sent.
Example
{ "errorCode": "USER_ALREADY_EXISTS"}The household has sent the most invites allowed for the rolling day. Retry after details.retryAfterSeconds.
object
The household has sent the most invites allowed in the rolling 24-hour window.
object
Seconds until the oldest send in the window ages out and another invite is admitted.
Example
{ "errorCode": "INVITE_SEND_THROTTLED"}Internal error (no internal detail leaked)
object
Machine-readable error code (AUTH_ERROR_CODES).
Optional field-level validation errors, keyed by field name.
object
Example
{ "errorCode": "INVALID_EMAIL_FORMAT"}Service temporarily unavailable — errorCode is SERVICE_UNAVAILABLE. A TRANSIENT, RETRYABLE condition rather than a defect in the request: a query that exceeded its time budget, a lost or refused database connection, a saturated connection pool, or an outage at an upstream provider the request depends on (the authentication provider, or the bank data provider on a bank-connection operation). The identical request may succeed on retry — back off briefly and, on a write, resend the same X-Idempotency-Key where the operation accepts one.
object
Machine-readable error code (AUTH_ERROR_CODES).
Optional field-level validation errors, keyed by field name.
object
Example
{ "errorCode": "INVALID_EMAIL_FORMAT"}