Skip to main content

Projects API

The Projects API covers the JWT-authenticated operations the dashboard uses to manage a single project — renaming it, configuring its retry policy, rotating or revoking its API key, listing/inviting team members, and changing or removing those members.

Base URL: https://api.zeridion.com/platform/v1

All endpoints require a valid JWT obtained from POST /platform/v1/auth/login.

Roles and the role hierarchy

Every team member's authority on a project is encoded as a role string. The server compares roles via RoleHelper.RoleRank, which yields a strict total order:

RoleRankTypical permissions
owner3Everything below, plus delete the project, change the retry policy, transfer/remove the owner. The user who created the project. There is exactly one owner per project.
admin2Rename, rotate/revoke API keys, invite members, change member roles up to admin.
member1Read-only on configuration; can use the project's API key for /flare/v1/* operations.
viewer0Dashboard-only read access.

Endpoints below indicate the minimum rank required. The general rule for any role-changing call is that the caller can never assign or invite a role equal to or higher than their own (role_exceeds_caller), and the owner is immutable from within the API (cannot_modify_owner / cannot_remove_owner).


PATCH /platform/v1/projects/{projectId}

Rename a project.

Authentication: JWT required. Role: owner only.

Request

PATCH /platform/v1/projects/{projectId}
Authorization: Bearer <jwt_token>
Content-Type: application/json

Body

FieldTypeRequiredDescription
namestringYesNew project name. Trimmed; max 200 characters; must be non-empty.
{ "name": "Acme Production" }

Response

200 OK

{
"id": "prj_01JAXBKM3N4P5Q6R7S8T9UVWXY",
"name": "Acme Production",
"plan": "starter"
}

Errors

StatusCodeWhen
400invalid_bodyBody is not valid JSON.
401unauthorizedMissing or invalid JWT.
403forbiddenCaller is not the project owner.
404project_not_foundProject ID does not exist.
422validation_errorname is missing, blank, or longer than 200 characters.

DELETE /platform/v1/projects/{projectId}

Cascade-delete a project and everything attached to it (jobs, recurring jobs, alert settings, audit logs, daily usage, previous API keys, etc.).

Authentication: JWT required. Role: owner only.

Last project guard. A user must always have at least one project. If this is the user's only project the request fails with 409 last_project — create a replacement project first if you really want to delete this one.

Stripe customer. The Stripe customer record is not deleted. Cancel the subscription manually from the Customer Portal before deleting your last project.

Request

DELETE /platform/v1/projects/{projectId}
Authorization: Bearer <jwt_token>

No request body.

Response

200 OK

{
"deleted": true,
"id": "prj_01JAXBKM3N4P5Q6R7S8T9UVWXY"
}

Errors

StatusCodeWhen
401unauthorizedMissing or invalid JWT.
403forbiddenCaller is not the project owner.
404project_not_foundProject ID does not exist.
409last_projectThis is the caller's only project; create another project first.

PATCH /platform/v1/projects/{projectId}/retry-policy

Update the per-project default retry policy applied to new jobs that don't override these values explicitly.

Authentication: JWT required. Role: owner only.

All three body fields are optional; missing fields are unchanged. At least one field must be present.

Request

PATCH /platform/v1/projects/{projectId}/retry-policy
Authorization: Bearer <jwt_token>
Content-Type: application/json

Body

FieldTypeRangeDescription
default_max_attemptsinteger1–50Default max_attempts applied to a new job when the create call doesn't specify one.
retry_backoff_policystringexponential | linear | fixedBackoff curve between attempts.
retry_backoff_secondsinteger1–3600Base backoff (in seconds) used by the curve.
{
"default_max_attempts": 5,
"retry_backoff_policy": "exponential",
"retry_backoff_seconds": 30
}

Response

200 OK

{
"id": "prj_01JAXBKM3N4P5Q6R7S8T9UVWXY",
"default_max_attempts": 5,
"retry_backoff_policy": "exponential",
"retry_backoff_seconds": 30
}

Errors

StatusCodeWhen
400invalid_bodyBody is not valid JSON.
401unauthorizedMissing or invalid JWT.
403forbiddenCaller is not the project owner.
404project_not_foundProject ID does not exist.
422validation_errorA field is the wrong type, out of range, or no fields were supplied.

POST /platform/v1/projects/{projectId}/keys/rotate

Generate a new API key. The previous key remains accepted for a 24-hour grace window so deployed workers can be updated without a hard cutover. Each rotation resets the 24-hour grace window from the timestamp of the most recent rotation — back-to-back rotations refresh (and reuse) the same PreviousApiKeys row rather than stacking additional 24-hour windows on top of each other, so a project can never have more than one key in its grace period at any time.

Authentication: JWT required. Role: admin or higher.

:::warning Store the new key immediately The plaintext API key is returned exactly once in the response body. It cannot be retrieved again. Store it in your secrets manager (Azure Key Vault, AWS Secrets Manager, GitHub Actions secrets, etc.) before closing the response. :::

Request

POST /platform/v1/projects/{projectId}/keys/rotate
Authorization: Bearer <jwt_token>

No request body.

Response

200 OK

{
"api_key": "zf_live_sk_<new_full_key>",
"rotated_at": "2026-05-15T12:00:00.000Z"
}

Cache invalidation

Both the old prefix's tenant:{prefix} cache entry and the new prefix's entry are evicted from IDistributedCache so the next request resolves the project state straight from the DB. A Redis outage at this point fails open — rotation still succeeds, the worst-case window is one TTL tick of stale tenant state.

Errors

StatusCodeWhen
401unauthorizedMissing or invalid JWT.
403forbiddenCaller's role is below admin.
404project_not_foundProject ID does not exist.
409key_revokedThe key has been permanently revoked; rotation is no longer possible.

DELETE /platform/v1/projects/{projectId}/keys/revoke

Permanently revoke the project's API key. After this call, neither the current key nor any grace-window key from a prior rotation will be accepted — old workers immediately start receiving 401 unauthorized.

Authentication: JWT required. Role: admin or higher.

:::danger No recovery Revocation is permanent. There is no undo. To restore API access you would need to delete the project and create a new one (or contact support). :::

Request

DELETE /platform/v1/projects/{projectId}/keys/revoke
Authorization: Bearer <jwt_token>

No request body.

Response

200 OK

{
"revoked_at": "2026-05-15T12:00:00.000Z"
}

Errors

StatusCodeWhen
401unauthorizedMissing or invalid JWT.
403forbiddenCaller's role is below admin.
404project_not_foundProject ID does not exist.
409already_revokedThe key is already revoked.

GET /platform/v1/projects/{projectId}/members

List the accepted members of a project, ordered by invitation timestamp ascending (oldest first).

Authentication: JWT required. Role: any member of the project (viewer or higher).

Request

GET /platform/v1/projects/{projectId}/members
Authorization: Bearer <jwt_token>

Response

200 OK

{
"members": [
{
"user_id": "usr_01JAXBKM3N4P5Q6R7S8T9UVWXY",
"email": "owner@example.com",
"role": "owner",
"invited_by": null,
"invited_at": "2026-01-01T00:00:00Z",
"accepted_at": "2026-01-01T00:00:00Z"
},
{
"user_id": "usr_01JAXBKM4N5P6Q7R8S9T0UVWXY",
"email": "engineer@example.com",
"role": "admin",
"invited_by": "usr_01JAXBKM3N4P5Q6R7S8T9UVWXY",
"invited_at": "2026-02-01T10:00:00Z",
"accepted_at": "2026-02-01T11:30:00Z"
}
]
}
FieldTypeDescription
user_idstringMember's user ID.
emailstringMember's email address.
rolestringOne of owner, admin, member, viewer.
invited_bystring | nullUser ID of the inviter, or null for the project owner.
invited_atstring (ISO 8601)When the invitation was issued.
accepted_atstring (ISO 8601) | nullWhen the invitee accepted. Only accepted members are returned by this endpoint, so this is always populated in the response.

Errors

StatusCodeWhen
401unauthorizedMissing or invalid JWT.
403forbiddenCaller is not a member of this project.

POST /platform/v1/projects/{projectId}/invites

Issue a new email invitation to join the project. The invitee receives a transactional email with an accept link; they accept it via POST /platform/v1/invites/accept. Tokens are valid for 7 days.

Authentication: JWT required. Role: admin or higher.

The caller cannot invite someone to a role equal to or higher than their own (role_exceeds_caller).

Request

POST /platform/v1/projects/{projectId}/invites
Authorization: Bearer <jwt_token>
Content-Type: application/json

Body

FieldTypeRequiredDescription
emailstringYesInvitee's email address. Lower-cased server-side.
rolestringYesOne of admin, member, viewer. (owner cannot be assigned via invite.)
{
"email": "engineer@example.com",
"role": "member"
}

Response

200 OK

{
"token": "01JAXBKM3N4P5Q6R7S8T9UVWXY",
"project_id": "prj_01JAXBKM3N4P5Q6R7S8T9UVWXY",
"email": "engineer@example.com",
"role": "member",
"expires_at": "2026-05-22T12:00:00.000Z"
}
FieldTypeDescription
tokenstringThe invitation token. Also embedded in the accept link in the email.
project_idstringProject the invitation is for.
emailstringInvitee's normalized email.
rolestringRole the invitee will hold once they accept.
expires_atstring (ISO 8601)When the token stops being accepted (7 days after issue).

Inviting an existing member is idempotent. If the invitee email already maps to an accepted membership on the project, the invite is still recorded with a fresh token and email is still sent, but accepting the token will not create a duplicate ProjectMembership row — the existing membership is reused and POST /platform/v1/invites/accept returns 200 OK with the membership's current role. The invite endpoint therefore never returns a 409 already_member — re-inviting is silently a no-op at the membership layer.

Errors

StatusCodeWhen
400invalid_bodyBody is missing or not valid JSON.
400invalid_emailemail is missing.
400invalid_rolerole is missing or not one of admin, member, viewer. owner cannot be assigned via invite — the project creator is the immutable owner.
401unauthorizedMissing or invalid JWT.
403forbiddenCaller's role is below admin.
403role_exceeds_callerCaller tried to invite someone to a role at or above their own.
404project_not_foundProject ID does not exist.

PATCH /platform/v1/projects/{projectId}/members/{memberId}

Change an existing member's role.

Authentication: JWT required. Role: admin or higher.

Constraints, all enforced server-side:

  • Cannot change the owner — returns 409 cannot_modify_owner.
  • Cannot escalate beyond caller — assigning a role at or above the caller's own role returns 403 role_exceeds_caller.

Request

PATCH /platform/v1/projects/{projectId}/members/{memberId}
Authorization: Bearer <jwt_token>
Content-Type: application/json

Body

FieldTypeRequiredDescription
rolestringYesNew role: admin, member, or viewer.
{ "role": "admin" }

Response

200 OK

{
"user_id": "usr_01JAXBKM4N5P6Q7R8S9T0UVWXY",
"role": "admin"
}

Errors

StatusCodeWhen
400invalid_bodyBody is missing or not valid JSON.
400invalid_rolerole is missing or not a valid role string.
401unauthorizedMissing or invalid JWT.
403forbiddenCaller's role is below admin.
403role_exceeds_callerCaller tried to assign a role at or above their own.
404member_not_foundThe target user is not a member of this project.
409cannot_modify_ownerThe target user is the project owner.

DELETE /platform/v1/projects/{projectId}/members/{memberId}

Remove a member from the project.

Authentication: JWT required. Role: admin or higher.

Constraints:

  • Cannot remove the owner — returns 409 cannot_remove_owner. Transfer ownership first.
  • Only the owner can remove other admins — a non-owner admin trying to remove another admin gets 403 forbidden.

Request

DELETE /platform/v1/projects/{projectId}/members/{memberId}
Authorization: Bearer <jwt_token>

No request body.

Response

200 OK

{
"removed": true,
"user_id": "usr_01JAXBKM4N5P6Q7R8S9T0UVWXY"
}

Errors

StatusCodeWhen
401unauthorizedMissing or invalid JWT.
403forbiddenCaller's role is below admin, or caller is a non-owner admin trying to remove another admin.
404member_not_foundThe target user is not a member of this project.
409cannot_remove_ownerThe target user is the project owner.

See also

  • Account API — read the authenticated user record and account-level controls
  • Authentication — bearer-token, JWT, and API key formats used by this surface
  • Billing API — link a project to a Stripe subscription and read its plan