Team access and roles
Your business is a workspace, and more than one login can be in it. Invite colleagues from Settings in your dashboard, and give each of them a role that decides what they can reach.
The one thing worth deciding before you invite anyone: only Owner and Admin can see your API key and webhook secret. Everyone else can work in the dashboard without ever being shown a credential.
The four roles
| Role | Can do |
|---|---|
| Owner | Everything, plus transferring ownership. Exactly one per business |
| Admin | Everything the owner can, except transferring ownership |
| Finance | View, refund, and work with invoices and exports. Cannot change bank details, create or cancel splits, or reach your credentials |
| Member | Day-to-day operations: view splits, create them, cancel them. Cannot refund, and cannot reach settings or credentials |
Every change to bank details, business details, payment processors, the team, API keys and webhook settings is recorded with who made it, their role and when, in a history that cannot be edited or deleted. A change to bank details also emails the owner, naming who made it.
Read across, by what someone actually does:
| Action | Owner | Admin | Finance | Member |
|---|---|---|---|---|
| View splits and the webhook delivery log | ✅ | ✅ | ✅ | ✅ |
| Create a split, cancel a pending split | ✅ | ✅ | ✅ | |
| Refund a captured split | ✅ | ✅ | ✅ | |
| Reveal or rotate the API key and webhook secret | ✅ | ✅ | ||
| Set the webhook URL and allowed return domain | ✅ | ✅ | ||
| Send a test webhook, resend a delivery | ✅ | ✅ | ||
| Start or refresh Stripe Connect onboarding, remove a processor | ✅ | ✅ | ||
| Change invoicing and bank details | ✅ | ✅ | ||
| Invite, re-role and remove team members | ✅ | ✅ | ||
| Transfer ownership | ✅ |
A call made without the right role returns
403 {"code":"business_forbidden","error":"You do not have permission to perform this action for this business."}.
Finance can refund but cannot cancel; a Member can cancel but cannot refund. Cancel only ever releases holds, so it cannot move money and is safe to leave with the people running orders. Refund returns money that has already settled, so it sits with the people who own the books. Give someone both by making them an Admin.
Inviting someone
Nobody is added directly. You send an invitation, and they accept it from an email while signed in to their own SharePay account.
POST /api/merchants/team/invites
role must be admin, finance or member. You cannot invite an owner,
and you cannot grant a role you do not hold: an Admin may invite Finance and
Member, but not another Admin. Trying returns
403 You cannot grant that role.
Returns 201 { id, email, role, expires_at } and emails the invitation. If
that email cannot be sent, the invitation is revoked again and you get
502 The invitation email could not be sent. Please try again. rather than a
pending invite nobody will ever receive.
| Response | Cause |
|---|---|
400 Enter a valid email and role. | Malformed email, or a role outside the three above |
403 You cannot grant that role. | Admin trying to create another Admin, or anyone trying to create an Owner |
409 That person is already a team member. | They are already in this workspace |
409 An invitation is already pending for this email. | Revoke or resend the existing one instead |
Invitations last 7 days. An expired one cannot be accepted; resend it to mint a fresh link.
Managing pending invitations
POST /api/merchants/team/invites/:id/resend
DELETE /api/merchants/team/invites/:id
Resending replaces the link. It issues a new token and a new 7-day expiry,
so any link you sent before stops working. It returns { ok: true, expires_at },
or 502 if the email fails — in which case the previous link is restored and
still works.
DELETE revokes the invitation. Both return
404 Pending invitation not found. for an invitation that was already accepted,
revoked or never existed.
Invitations are rate limited to 5 requests per 10 minutes, keyed by your IP
address, and that bucket is shared with credential rotations and webhook test
sends. Inviting a handful of colleagues in one sitting will lock all three out
with 429 Too many requests. Please try again later. See
Limits.
Accepting an invitation
The invited person opens the link in their email, which lands on
/business-invite/<token>. Two rules decide whether it works:
- They must be signed in to SharePay with the address the invitation was
sent to. A different account gets
403 This invitation was sent to <email>. Sign in with that email to accept it.If they have no SharePay account at all, they sign up with that address first — an invitation does not create a login for them. - The link is single-use. A second accept returns
409 This invitation has already been used or cancelled.
Behind that page:
GET /api/merchants/team-invites/:token
POST /api/merchants/team-invites/:token/accept
The GET takes no credentials and shows the invitation before anyone signs in
— { business_name, role, email_masked, expires_at }, with the address masked
to co***@your-shop.example so a leaked link does not leak an email. An
expired or unknown token returns
404 This invitation is invalid or has expired.
The POST needs the invited person's session and returns
{ ok: true, merchant_id }.
Reading and changing the team
GET /api/merchants/team
Any member may read it. Returns current_role and current_user_id — which is
how the dashboard knows what to show you — alongside members (each with id,
user_id, role, email, name, joined_at) and every pending invites
row.
PATCH /api/merchants/team/members/:id
DELETE /api/merchants/team/members/:id
PATCH takes { "role": "member" } and returns { id, role }. You must be
able to manage both the role they hold now and the role you are moving them
to, so an Admin cannot promote a Member to Admin, and cannot re-role another
Admin. Either failure returns
403 You cannot change this team member's role.
DELETE removes their access immediately and returns { ok: true }. You
cannot remove yourself — that returns
400 You cannot remove your own access here. — and you cannot remove someone
whose role you do not manage.
Neither call can touch the Owner. To change who the owner is, transfer ownership.
Transferring ownership
Owner only, and it is not reversible from your side afterwards.
POST /api/merchants/team/transfer-ownership
{ "member_id": "<their team member id>", "confirmation": "Corner Coffee" }
confirmation must be your business name typed exactly, or the call
returns 400 Enter the business name exactly to confirm the transfer.
member_id is the id from GET /api/merchants/team, not their user_id,
and it must be someone already in the workspace.
You are demoted to Admin, they become Owner. You keep every day-to-day permission, so nothing about running the business changes, but you can no longer transfer ownership, and only they can transfer it back.
Verification starts again for the new owner. Business verification is tied to the owner's identity, so the transfer clears the previous result and re-runs the check straight away:
- If the new owner has not verified their identity in the SharePay app, the business cannot send invoices or take payments until they do. Their verification is theirs alone; yours does not carry over.
- If they are verified but Companies House does not list them as a director or officer, the business goes to review and SharePay decides by hand, as it would for a first registration.
- Stripe, orders, invoices and the team are untouched.
GET /api/merchants/team marks each member with identity_verified so you
can see this before you transfer.
| Response | Cause |
|---|---|
403 Only the owner can transfer ownership. | You are not the owner |
400 Choose another current team member. | member_id is you, or is not in this workspace |
409 Ownership changed while you were confirming. Reload and try again. | Someone else transferred ownership first |
Being in more than one business
One login can belong to several businesses — your own, plus any you have been invited into.
GET /api/merchants/workspaces
returns { workspaces: [{ merchant_id, name, role }] } for every business the
signed-in account can reach.
While your login belongs to exactly one business, every endpoint resolves it
for you and nothing changes. The moment it belongs to two, the merchant
endpoints stop guessing and answer
409 {"code":"business_selection_required","error":"Select a business workspace before continuing."}
until you name one.
You name it with a header:
x-sharepay-merchant-id: <merchant_id>
This only works from your server. Browsers are not allowed to send that
header to our API, so the dashboard cannot send it for you. Which means: if you
accept an invitation into a second business using the same login you own your
own business with, expect the dashboard to start showing that 409. Use a
separate login for each business unless you are calling the API from your own
server.
Registering a new business is also once per login. A second
POST /api/merchants/register returns
409 {"code":"merchant_exists","error":"You already have a merchant account"}.
Being invited into other workspaces does not change that: you can join many,
and create one.
If your workspace is paused
A workspace SharePay has paused keeps working for reading only. Every action
beyond viewing returns
403 {"code":"business_forbidden","error":"This business workspace has been paused. Contact SharePay support."},
whatever your role, including the owner's. Existing splits are unaffected by
the pause itself; contact support to have it lifted.