team

Accept Invite

Join the workspace this invitation names, as the person signed in now.

Two things have to agree and neither is enough alone. The token proves the caller was sent this particular link. The address proves they are the person it was sent to — checked against the address the identity provider verified, never against anything in the request — so forwarding the mail to somebody else does not hand them the workspace.

Until this existed the token was decoration: it travelled in the URL and the sign-up screen read it only to prefill an email field. Joining depended entirely on an address matching at first sign-in, which is why an invitation sent to somebody who already had an account could never be applied (measured in production, 2026-09-14).

post/api/team/invites/{token}/accept

Request

  • Base URL: https://api.heymetra.com (the document declares no server; this is the origin it was published from)
  • URL: https://api.heymetra.com/api/team/invites/{token}/accept
  • Auth: none declared

Path parameters

tokenstring required

Headers

authorizationstring nullable
x-workspace-idstring nullable

Response

Successful Response

tenant_idstring uuid required
tenant_namestring required
role'owner' | 'member' required

Who somebody is in one workspace.

Two, by the owner's decision of 2026-09-18, and the argument is the one he made: four roles that collapse to two predicates are not four roles. OWNER and ADMIN differed in two lines in the whole codebase; EDITOR and VIEWER in one.

Worse, four roles plus per-connection grants meant the same fact could be stated twice and disagree — a role saying VIEWER while a grant said writer, with the winner decided across sixteen separate gates. One axis cannot express the contradiction.

OWNER does everything: billing, team, connecting accounts, approving changes, and reading and writing on every connection.

MEMBER does exactly what it was given, per connection, in connection_grants.level. A member with no row anywhere can sign in and see that nothing has been shared with them yet, and that is all.

Persisted as a plain string: VARCHAR(32), no CHECK constraint, no native Postgres type, so this is a code change and not a migration.

Changes

No recorded changes to this operation across all 3 revisions of this API.