Swagger

Projects

17 endpoints.

17 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 an owner or admin can read it back later from GET /api-key/all). 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)

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. The project's owner may do this, and so may platform staff; every other caller gets the same 404 as a project that does not exist, 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
PATCH/project/:projectIdRename project

Renames the project. The name is trimmed and must be 1–120 characters.

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

Input — query and path parameters

projectId string required
Project id

Input — request body

name string required
New project name, 1–120 characters after trimming

Input — example request

{
  "name": "My App"
}

Output — example response (200)

{
  "projectId": "ugtp_project_a1b2c3d4e5f6",
  "name": "My App"
}

Output — every status this endpoint answers with

StatusWhen
200{ projectId, name }
400Invalid request body or query parameters
401Missing or invalid credentials
403Authenticated but not permitted
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.
  • accountMode is the account model the project's operations use, and platformPaymasterStaticFeeUsd is our flat transfer fee in dollars, beside platformPaymasterFeePercent.

Authentication: Bearer JWT (client session)

Output — example response (200)

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

Output — every status this endpoint answers with

StatusWhen
200Projects with member, API-key and transaction counts, revenue and fee settings.
401Missing or invalid credentials
500Internal server error
Open in Swagger
PATCH/project/:projectId/feesSet LI.FI integrator fee and eERC fee overrides

Set the platform's fee rates for a project and the project's own markup. All fields optional, at least one required. Percent fields are 0–2 with at most 2 decimals; eercTransferFlatUsd is 0–5 dollars in cent steps. Platform rates are PLATFORM STAFF ONLY and are set per project, so one project can be priced differently from another. A project sets its own markup per leg: projectPaymasterFeePercent on transfers, projectEercFeePercent on private-token deposits and withdrawals. A markup is charged on top of ours and paid to the project's payout address, so it is refused while that address is unset. Ceilings apply to the total of both parts: 2% on swaps, 0.4% on transfers, 0.5% per private-token leg.

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

Input — query and path parameters

projectId string required
Project id

Input — request body

lifiFeePercent number
Platform staff only. Fee percent, 0–2, at most 2 decimal places
platformLifiFeePercent number
Platform staff only. The platform's own swap cut, 0–2
eercDepositFeePercent number
Platform staff only. EERC deposit fee percent, 0–2; null resets to the platform default
eercWithdrawFeePercent number
Platform staff only. EERC withdraw fee percent, 0–2; null resets to the platform default
eercTransferFlatUsd number
Platform staff only. EERC flat transfer fee in dollars, 0–5, cent steps; null resets to the platform default
platformPaymasterFeePercent number
Platform staff only. Our own transfer rate for this project; together with projectPaymasterFeePercent at most 0.4. Charged on every network, and fee estimates and quotes are priced at it
projectPaymasterFeePercent number
The project's own markup on transfers, 0–2; null resets it to zero
projectEercFeePercent number
The project's own markup on private-token deposits and withdrawals, 0–2; null resets it to zero

Input — example request

{
  "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,
    "projectFeePercent": 0,
    "totalDepositFeePercent": 0.4,
    "totalWithdrawFeePercent": 0.25,
    "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
PATCH/project/:projectId/payout-addressSet the project's payout address

Sets the address the project's own markup is paid to, inside the same transaction as the operation. null clears it.

  • The address must be a 0x-prefixed 20-byte address. A mixed-case address must carry a valid EIP-55 checksum; an all-lowercase one is accepted as written.
  • The zero address and the platform's own treasury are refused with a 400.
  • Clearing the address is refused with a 400 while the project still charges a markup — set its markup rates to zero first.
  • The address is stored and returned in lowercase, and also appears as payoutAddress in /project/list and /project/all.

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

Input — query and path parameters

projectId string required
Project id

Input — request body

payoutAddress string required
Payout address, or null to clear it

Input — example request

{
  "payoutAddress": "0x1111111111111111111111111111111111111111"
}

Output — example response (200)

{
  "projectId": "ugtp_project_a1b2c3d4e5f6",
  "payoutAddress": "0x1111111111111111111111111111111111111111"
}

Output — every status this endpoint answers with

StatusWhen
200{ projectId, payoutAddress }
400Invalid request body or query parameters
401Missing or invalid credentials
403Authenticated but not permitted
404Resource not found
Open in Swagger
PATCH/project/:projectId/account-modeSet the project's account model

Switches the account model the project's operations use. Platform staff only — it changes which address the operations come from and which signature scheme the project's clients must use.

  • eoa_7702 is the current model: the account is the owner's own address. smart_account is the retired smart-account model.
  • The switch takes effect within seconds of the write.

Authentication: Bearer JWT (platform staff)

Input — query and path parameters

projectId string required
Project id

Input — request body

accountMode string required
eoa_7702 | smart_account

Input — example request

{
  "accountMode": "eoa_7702"
}

Output — example response (200)

{
  "projectId": "ugtp_project_a1b2c3d4e5f6",
  "accountMode": "eoa_7702"
}

Output — every status this endpoint answers with

StatusWhen
200{ projectId, accountMode }
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 search by case-insensitive name fragment or exact project id. 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 name fragment, or a project's exact id
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