Swagger

Projects

14 endpoints.

14 endpoints

Projects

POST/project/createCreate project

Create a project. The authenticated user becomes its owner (only owner/admin members can mint API keys). Optionally seeds additional members by email and always auto-issues a full-access API key named after the project (raw key in apiKey.value — also persisted, so it stays retrievable later by owners/admins via GET /project/:projectId/api-key). Unknown/duplicate member emails never fail the request — they are reported in warnings instead.

Authentication: Bearer JWT (client session)

Input — request body

name string required
Project name
members array
Optional seed members (max 20). Each item: { email: string, role?: "admin"|"member"|"viewer" (default "member") }

Input — example request

{
  "name": "My App",
  "members": [
    {
      "email": "teammate@example.com",
      "role": "admin"
    }
  ]
}

Output — example response (201)

{
  "id": "ugtp_project_a1b2c3d4e5f6",
  "name": "My App",
  "apiKey": {
    "id": "ugtp_project_api_key_a1b2c3d4e5f6",
    "name": "My App",
    "keyPrefix": "ugtp2_0123",
    "value": "ugtp2_0123456789abcdef0123456789abcdef"
  },
  "warnings": []
}

Output — every status this endpoint answers with

StatusWhen
201Project, auto-issued API key, invited emails, and any skip warnings
400Invalid request body or query parameters
401Missing or invalid credentials
500Internal server error
Open in Swagger
DELETE/project/:projectIdDelete project (soft delete)

Owner-only soft delete: archives the project (marks it inactive) and revokes all its still-active API keys. Nothing is removed from the database — operations, fees, status events, security reports, and memberships are retained for history/analytics. Idempotent — repeat calls still return 204. Returns 404 both if the project doesn't exist and if the caller is not its owner (non-owners get the same not-found response, never a 403).

Authentication: Bearer JWT (project owner)

Input — query and path parameters

projectId string required
Project id

Output — every status this endpoint answers with

StatusWhen
204Archived
401Missing or invalid credentials
404Resource not found
Open in Swagger
GET/project/listList my projects

The projects the caller belongs to, newest first.

  • Each item includes membersCount, counting all members, and apiKeysCount, counting active keys only.
  • transactionCount is the project's lifetime operation count.
  • The caller's own role on each project is included, so a client can gate management actions without a second call.

Authentication: Bearer JWT (client session)

Output — example response (200)

{
  "projects": [
    {
      "id": "ugtp_project_a1b2c3d4e5f6",
      "name": "My App",
      "isActive": true,
      "role": "owner",
      "createdAt": "2026-06-18T12:00:00.000Z",
      "membersCount": 3,
      "apiKeysCount": 2,
      "lifiFeePercent": 0.2,
      "lifiIntegrator": "extrasafe",
      "eercFees": {
        "depositFeePercent": 0.25,
        "withdrawFeePercent": 0.25,
        "transferFlatUsd": 0.1,
        "depositIsDefault": true,
        "withdrawIsDefault": true,
        "transferIsDefault": true
      }
    }
  ]
}

Output — every status this endpoint answers with

StatusWhen
200Projects, with membersCount, apiKeysCount, transactionCount, revenueUsdMicros, the swap-fee split (lifiFeePercent / platformLifiFeePercent /…
401Missing or invalid credentials
500Internal server error
Open in Swagger
PATCH/project/:projectId/feesSet LI.FI integrator fee and eERC fee overrides

Set the project's LI.FI integrator fee and/or eERC (private-token) fee overrides. All fields optional (partial update), but at least one is required. lifiFeePercent, eercDepositFeePercent, and eercWithdrawFeePercent are human-facing percents, 0–2, at most 2 decimal places (they must map exactly onto integer basis points). eercTransferFlatUsd is 0–5 dollars, in cent steps. On the eERC fields (not lifiFeePercent), null resets that field to the platform default (clears the per-project override); omitting a field leaves it untouched. Requires an owner/admin membership on the project, or platform staff. The response echoes the effective values after the write, plus isDefault flags for the eERC fields (true when the value shown is the platform default, i.e. no per-project override is set).

Authentication: Bearer JWT (project owner/admin or platform staff)

Input — query and path parameters

projectId string required
Project id

Input — request body

lifiFeePercent number
Fee percent, 0–2, at most 2 decimal places
eercDepositFeePercent number
eERC deposit fee percent, 0–2, at most 2 decimal places; null resets to the platform default
eercWithdrawFeePercent number
eERC withdraw fee percent, 0–2, at most 2 decimal places; null resets to the platform default
eercTransferFlatUsd number
eERC flat transfer fee in dollars, 0–5, cent steps; null resets to the platform default

Input — example request

{
  "lifiFeePercent": 0.25,
  "eercDepositFeePercent": 0.4,
  "eercWithdrawFeePercent": null
}

Output — example response (200)

{
  "projectId": "ugtp_project_a1b2c3d4e5f6",
  "lifiFeePercent": 0.25,
  "eercFees": {
    "depositFeePercent": 0.4,
    "withdrawFeePercent": 0.25,
    "transferFlatUsd": 0.1,
    "depositIsDefault": false,
    "withdrawIsDefault": true,
    "transferIsDefault": true
  }
}

Output — every status this endpoint answers with

StatusWhen
200Updated fees, e.g.
400Invalid request body or query parameters
401Missing or invalid credentials
403Authenticated but not permitted
404Resource not found
Open in Swagger
GET/project/allList all projects (staff)

Platform-staff only (owner / system_admin): every project, sorted by transaction count (desc), capped at 50, with an optional case-insensitive name search. Backs the owner/admin project picker; clients use GET /project/list (scoped to their memberships).

Authentication: Bearer JWT (platform owner/system admin)

Input — query and path parameters

search string
Case-insensitive project-name filter
limit number
Max results (default 50, max 50)

Output — example response (200)

{
  "projects": [
    {
      "id": "ugtp_project_a1b2c3d4e5f6",
      "name": "Acme",
      "isActive": true,
      "transactionCount": 4210,
      "createdAt": "2026-06-18T12:00:00.000Z"
    }
  ]
}

Output — every status this endpoint answers with

StatusWhen
200Projects with activity metrics.
401Missing or invalid credentials
403Authenticated but not permitted
Open in Swagger
GET/project/:projectId/members/listList members

List project members. Readable by any project member or platform staff.

Authentication: Bearer JWT (project member or platform staff)

Input — query and path parameters

projectId string required
Project id

Output — example response (200)

{
  "members": [
    {
      "id": "ugtp_project_member_a1b2c3d4e5f6",
      "userId": "ugtp_user_a1b2c3d4e5f6",
      "email": "user@example.com",
      "name": "Jane Dev",
      "role": "owner",
      "createdAt": "2026-06-18T12:00:00.000Z"
    }
  ]
}

Output — every status this endpoint answers with

StatusWhen
200Members
401Missing or invalid credentials
403Authenticated but not permitted
Open in Swagger
POST/project/:projectId/members/addAdd or invite member

Add a project member by email. If the email belongs to an existing UGTP user they're added directly (201, { status: "added", member }). If the email is new, a pending invitation is created and emailed to the recipient (202, { status: "invited", email }) — see /project/invitations/accept. Requires an owner/admin membership on the project. Returns 404 if the project doesn't exist, 400 if the email is already a member.

Authentication: Bearer JWT (project owner/admin)

Input — query and path parameters

projectId string required
Project id

Input — request body

email string required
User or invitee email
role string
admin | member | viewer (default member)

Input — example request

{
  "email": "teammate@example.com",
  "role": "viewer"
}

Output — example response (202)

{
  "added": {
    "status": "added",
    "member": {
      "userId": "ugtp_user_a1b2c3d4e5f6",
      "email": "teammate@example.com",
      "name": "Teammate",
      "role": "viewer"
    }
  },
  "invited": {
    "status": "invited",
    "email": "newperson@example.com"
  }
}

Output — every status this endpoint answers with

StatusWhen
202Invitation created (or refreshed) and emailed
400Invalid request body or query parameters
401Missing or invalid credentials
403Authenticated but not permitted
404Resource not found
Open in Swagger
GET/project/:projectId/invitationsList pending invitations

List a project's pending invitations. Requires an owner/admin membership on the project.

Authentication: Bearer JWT (project owner/admin)

Input — query and path parameters

projectId string required
Project id

Output — example response (200)

{
  "invitations": [
    {
      "id": "ugtp_project_invitation_a1b2c3d4e5f6",
      "email": "teammate@example.com",
      "role": "viewer",
      "status": "pending",
      "expiresAt": "2026-07-20T12:00:00.000Z",
      "createdAt": "2026-07-13T12:00:00.000Z"
    }
  ]
}

Output — every status this endpoint answers with

StatusWhen
200Pending invitations
401Missing or invalid credentials
403Authenticated but not permitted
Open in Swagger
POST/project/:projectId/invitations/:invitationId/resendResend invitation

Resend the invitation email. The invitation must still be pending and belong to this project (400 otherwise). Requires an owner/admin membership on the project.

Authentication: Bearer JWT (project owner/admin)

Input — query and path parameters

projectId string required
Project id
invitationId string required
Invitation id

Output — every status this endpoint answers with

StatusWhen
204Invitation resent
400Invalid request body or query parameters
401Missing or invalid credentials
403Authenticated but not permitted
Open in Swagger
POST/project/:projectId/invitations/:invitationId/cancelCancel invitation

Cancel a pending invitation. The invitation must still be pending and belong to this project (400 otherwise). Requires an owner/admin membership on the project.

Authentication: Bearer JWT (project owner/admin)

Input — query and path parameters

projectId string required
Project id
invitationId string required
Invitation id

Output — every status this endpoint answers with

StatusWhen
204Invitation canceled
400Invalid request body or query parameters
401Missing or invalid credentials
403Authenticated but not permitted
Open in Swagger
PATCH/project/:projectId/members/:memberIdChange member role

Set a project member's role. Requires an owner/admin membership on the project. Assigning owner requires the caller to be an owner. An owner row can only be changed by that owner themself, and only while another owner remains, so a project never ends up ownerless; a manager may change their own role.

Authentication: Bearer JWT (project owner/admin)

Input — query and path parameters

projectId string required
Project id
memberId string required
Project member id

Input — request body

role "owner" | "admin" | "member" | "viewer" required
The role to assign

Input — example request

{
  "role": "admin"
}

Output — every status this endpoint answers with

StatusWhen
204Role updated
400Invalid request body or query parameters
401Missing or invalid credentials
403Authenticated but not permitted
Open in Swagger
DELETE/project/:projectId/members/:memberIdRemove member

Remove a non-owner project member. Requires an owner/admin membership on the project.

Authentication: Bearer JWT (project owner/admin)

Input — query and path parameters

projectId string required
Project id
memberId string required
Project member id

Output — every status this endpoint answers with

StatusWhen
204Removed
400Invalid request body or query parameters
401Missing or invalid credentials
403Authenticated but not permitted
Open in Swagger
GET/project/invitations/acceptPreview invitation

Public, session-less preview of a project invitation for an accept-invitation landing page — shows who invited the recipient and to which project before they set a password. Read-only. Returns 404 if the token is unknown, 410 if it expired, 400 if token is missing.

Authentication: None

Input — query and path parameters

token string required
Invitation token from the emailed link

Output — example response (200)

{
  "projectName": "Acme",
  "email": "teammate@example.com",
  "role": "viewer",
  "expiresAt": "2026-07-20T12:00:00.000Z"
}

Output — every status this endpoint answers with

StatusWhen
200Invitation preview
400Invalid request body or query parameters
404Resource not found
410Resource existed but is no longer valid (e.g.
Open in Swagger
POST/project/invitations/acceptAccept invitation

Public, session-less. Consumes the invite token. For a new email it creates the account and the project membership and auto-logs in, returning a real sessionToken. For an already-existing user it attaches them to the project and answers requiresLogin: true with sessionToken: null, so the client must log in normally — auto-login off just the invite token would let anyone holding the link take over an existing account. Returns 404 if the token is unknown, 410 if it expired. Rate-limited to 5 requests/hour per IP+email — check the Retry-After response header on 429.

Authentication: None

Input — request body

token string required
Invitation token from the emailed link
password string required
Password, minimum 8 characters
name string required
Display name for a new account

Input — example request

{
  "token": "a1b2c3d4e5f6...",
  "password": "********",
  "name": "Jane Dev"
}

Output — example response (201)

{
  "newUser": {
    "requiresLogin": false,
    "sessionToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
    "projectId": "ugtp_project_a1b2c3d4e5f6",
    "email": "teammate@example.com"
  },
  "existingUser": {
    "requiresLogin": true,
    "sessionToken": null,
    "projectId": "ugtp_project_a1b2c3d4e5f6",
    "email": "teammate@example.com"
  }
}

Output — every status this endpoint answers with

StatusWhen
200Invitation accepted by the signed-in account
201Account created, invitation accepted, auto-logged in
400Invalid request body or query parameters
401Missing or invalid credentials
403Authenticated but not permitted
404Resource not found
409Request conflicts with the current state of the resource
410Resource existed but is no longer valid (e.g.
429Rate limit exceeded — retry after the number of seconds in the `Retry-After` response header
Open in Swagger