Internal API
Server to server calls for product backends, guarded by the shared internal key.
Product backends call these with x-internal-key: <INTERNAL_API_KEY>. Never send the key from a browser.
GET /internal/ventures/:id/members/:userId
Is this user an active member, and with what role. Cached in Redis under the id: prefix and cleared whenever a role or membership changes, so it is safe to call on every request.
{
"SUCCESS": true,
"data": {
"ventureId": "6aca9bfeb50f1fec97df56d6",
"userId": "6aca9bf9b50f1fec97df56c0",
"role": "owner",
"productRoles": [],
"status": "active",
"venture": { "_id": "6aca9bfeb50f1fec97df56d6", "name": "Acme Studios", "slug": "acme-studios", "logo": null, "ownerId": "6aca9bf9b50f1fec97df56c0" }
}
}Not a member answers 404 with the message "You are not a member of this venture." and the code NOT_A_MEMBER. Treat any 4xx as no access and a 5xx or timeout as unavailable.
GET /internal/ventures/:id/members
Every active member with name, email and role. Use it to show a team picker or to fan out notifications.
GET /internal/users/:userId/ventures
The ventures a user belongs to, the same shape as GET /ventures for that user.
Example middleware
const membership = async (ventureId, userId) => {
const res = await fetch(`${process.env.CORPLAND_ID_API_URL}/internal/ventures/${ventureId}/members/${userId}`, {
headers: { "x-internal-key": process.env.CORPLAND_ID_INTERNAL_KEY },
});
if (!res.ok) return null;
return (await res.json()).data;
};
const requireVenture = async (req, res, next) => {
const ventureId = req.headers["x-venture-id"] || req.user.vid;
const member = ventureId && (await membership(ventureId, req.user._id));
if (!member) return res.status(403).json({ message: "Pick a venture you belong to." });
req.venture = member;
next();
};