Identity & Teams

Look up the authenticated caller and their team memberships.

The identity and teams endpoints expose the caller's own details, verified capabilities, and the teams they belong to. Use these to:

  • Bootstrap an integration with one call — GET /api/v1/auth/me returns the caller, their primary team, and any other teams they belong to or have been invited to.
  • Render team directories and member pickers.
  • Check whether the authenticated user is an admin of a given team before invoking admin-only operations.

Quick Reference

Feature Details
GET /auth/me The caller's identity, capabilities, primary team, and any other teams they belong to or are invited to.
GET /teams/{id} A single team. Includes an inline member list (up to 50) when the caller is an active member.
GET /teams/{id}/members Paginated, role-filterable list of active members. Stable keyset ordering for cursor pagination.
Access levels Active members get the full member list; callers with a pending invite see the team's details only.
Inline cap members[] is capped at 50 entries. hasMoreMembers: true with a memberCount signals truncation.
IDs All identifiers are opaque strings. Treat them as such; do not assume any format.
Not found 404 not-found — never 403 — when the caller has no visibility into the target team.

Endpoints

GET /api/v1/auth/me

Returns the authenticated caller. No query parameters — always a single object.

curl "https://app.propstreet.com/api/v1/auth/me" \
  -H "Authorization: Bearer YOUR_TOKEN"

Response:

{
  "id": "a3f8...",
  "email": "jane@acme.com",
  "firstName": "Jane",
  "lastName": "Smith",
  "capabilities": ["broker"],
  "culture": "en-US",
  "uiCulture": "en-US",
  "region": "US",
  "timeZone": "America/New_York",
  "isBot": false,
  "primaryTeam": {
    "id": "...",
    "name": "Acme Capital",
    "categories": ["private_equity"],
    "plan": "professional",
    "membership": { "role": "admin", "status": "active", "joinedUtc": "2024-02-01T10:00:00Z" },
    "members": [ /* up to 50 active members */ ],
    "memberCount": 7,
    "hasMoreMembers": false
  },
  "secondaryTeams": [ /* other teams — may include pending invitations */ ]
}

Notes:

  • capabilities lists the caller's verified, non-revoked claims. Only broker and investor appear on the wire.
  • firstName and lastName are exposed as discrete fields. Compose display names client-side (e.g. `${firstName} ${lastName}`.trim()); fall back to email when both name fields are missing.
  • Callers with no active team (pre-onboarding, service accounts, etc.) return primaryTeam: null and secondaryTeams: []. Handle this gracefully — it's a valid runtime state.
  • membership.status inside each team tells you whether the caller is an active member or has a pending invite. Prefer this over inferring from position in the secondaryTeams[] array.

GET /api/v1/teams/{id}

Returns a single team by its opaque id. The caller must have visibility — active membership or a pending invite — into the team; 404 otherwise.

curl "https://app.propstreet.com/api/v1/teams/TEAM_ID" \
  -H "Authorization: Bearer YOUR_TOKEN"

The members[], memberCount, and hasMoreMembers fields are only populated when the caller is an active member of the team. Callers with a pending invite see the team's details but not the member list.

hasMoreMembers: true is a truncation signal, not continuation metadata. The inline member slice is capped at 50 entries and cannot be resumed — there is no cursor on the inline slice, and its ordering differs from the paginated /teams/{id}/members response. Clients that need the full roster should call /teams/{id}/members from page 1, not try to "append" pages onto the inline slice.

GET /api/v1/teams/{id}/members

Paginated list of a team's active members. The caller must be an active member; callers with a pending invite receive 404.

Query parameter Type Description
role string Filter: admin or member. Omit for all roles.
page_size int Page size, 1–100. Default 50. Values above the cap are clamped.
cursor string Opaque cursor from a previous response's page.nextCursor.

Snake_case query parameter names match the rest of the Public API.

curl "https://app.propstreet.com/api/v1/teams/TEAM_ID/members?role=admin&page_size=10" \
  -H "Authorization: Bearer YOUR_TOKEN"

Response:

{
  "data": [
    {
      "id": "...",
      "role": "admin",
      "status": "active",
      "user": {
        "id": "a3f8...",
        "firstName": "Jane",
        "lastName": "Smith",
        "email": "jane@acme.com"
      }
    }
  ],
  "page": { "pageSize": 10, "hasMore": false }
}

Access levels

Team endpoints distinguish two levels of access based on the caller's relationship to the team:

  • Pending invite — the caller has been invited but has not accepted. They see the team's details (name, plan, country, contactPerson, etc.) so they can recognise the invitation, but not the member list.
  • Active member — the caller has accepted. In addition to the details above, they see members[], memberCount, hasMoreMembers, and can call GET /teams/{id}/members.

membership.status on each team is the authoritative signal — use it rather than inferring from which fields are present.

Member ordering

The inline members[] slice on a team response is ordered for display:

  1. Admins first.
  2. Then by the composed first/last name, falling back to email when both are missing.
  3. Then by member id as a stable tiebreaker.

The paginated /teams/{id}/members endpoint uses a different, keyset-stable ordering so cursors remain valid across pages:

  1. joinedUtc ascending.
  2. Then by member id as a stable tiebreaker.

These orderings differ by design. The inline slice is for display in a team card; the paginated endpoint is for walking the full roster with cursors. Because the orderings differ, the inline slice is not a prefix of the paginated listing — a client that wants the full roster must restart from page 1 of /teams/{id}/members.

  • Multi-Tenant — how teams isolate data between tenants.
  • MCP Server — the same identity data is available to AI agents via the get_me tool.