Skip to content

Projects and roles

A project holds servers, SSH keys, firewall groups, snapshots, ISOs, actions and orders, and the people who manage them. Each person has one role in each project they belong to. Paths address a project by its slug: /projects/acme/servers/.

Endpoints

Endpoint What it does
GET /projects/ List projects
POST /projects/ Create a project
GET /projects/{slug}/ Get a project
PATCH /projects/{slug}/ Rename a project
DELETE /projects/{slug}/ Delete a project
GET /projects/{slug}/members/ List members
PATCH /projects/{slug}/members/{user_id}/ Change a member's role
DELETE /projects/{slug}/members/{user_id}/ Remove a member
GET /projects/{slug}/invitations/ List invitations
POST /projects/{slug}/invitations/ Invite someone
DELETE /projects/{slug}/invitations/{id}/ Withdraw an invitation
POST /projects/{slug}/invitations/{id}/resend/ Resend an invitation

Roles

Member Admin Owner
Read everything, and poll actions and orders
Power, reboot and reinstall servers, and take and restore snapshots
Manage hostnames, SSH keys, firewalls, ISOs and reverse DNS
Order servers, upgrade them, buy bandwidth and cancel servers
Rename the project, and manage its members and invitations
Delete the project

A request your role doesn't allow answers 403 insufficient_role. A project you are not a member of answers 404 not_found, the same as one that doesn't exist, so a 404 never tells anyone a project exists.

A read-only key can only read, whatever your role.

Transferring a server to another account is done in the console. The API has no endpoint for it.

Project slugs

The slug is the project's id in every path. You choose it when you create the project, and it can't change afterwards.

  • 3 to 40 characters: lowercase letters, digits and hyphens.
  • It starts with a letter and doesn't end with a hyphen: ^<span class="tooltip hint--top" aria-label="1,38">a-z][a-z0-9-</span>[a-z0-9]$.
  • These words are reserved: api, admin, accounts, new, create, default, me, projects, servers, billing, support, static, media, www, hosthatch.
  • A slug is never used twice, not even after its project is deleted.

Leave the slug out and one is made from the name, with four random digits: Acme becomes acme-4821.

Projects created before these rules keep their slugs, which may have capital letters or underscores, and they work in every path.

Project readiness

A new project takes a moment to set up. Until it is ready, its ready field is false and every endpoint for its servers and other resources answers 403 project_not_ready. If the project's owner has no billing account yet, those endpoints answer 403 billing_account_required instead.

The project object

{
  "id": 7,
  "name": "Acme",
  "slug": "acme",
  "ready": true,
  "role": "owner",
  "created_at": "2026-10-19T10:00:00Z"
}
Field Type Description
id integer The project's id. Paths use the slug instead.
name string A label you can change. It never appears in paths.
slug string The project's id in paths. It never changes.
ready boolean false while the project is being set up.
role string Your role in it: owner, admin or member.
created_at string When it was created.

Projects

List projects

GET /projects/

Any member · Read-only keys: yes · Returns 200 Project[]

The projects you are a member of. A user without a project gets an empty list.

Create a project

POST /projects/

Any member · Read-only keys: no · Returns 201 Project

Create a project. You become its owner.

curl -X POST https://console.hosthatch.com/api/v1/projects/ \
  -H "Authorization: Bearer $HOSTHATCH_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "Acme", "slug": "acme-prod"}'
Field Type Description
name string Required.
slug string Optional. See Project slugs for the rules, and for what happens when you leave it out.

You can have up to 20 projects. One more answers 409 limit_reached. A slug that breaks the rules answers 400 validation_error, and one another project has, or had, answers 409 name_taken.

Get a project

GET /projects/{slug}/

Any member · Read-only keys: yes · Returns 200 Project

One project.

Rename a project

PATCH /projects/{slug}/

Admin or owner · Read-only keys: no · Returns 200 Project

Change the project's name, the only thing about it that can change. Sending a different slug answers 400 validation_error.

{"name": "Acme production"}

Delete a project

DELETE /projects/{slug}/

Owner · Read-only keys: no · Returns 204

Delete an empty project. It must have no servers, no SSH keys, no orders in progress and no running actions; otherwise the answer is 409 project_not_empty. While the deletion runs, new orders and actions in the project answer 409 project_deleting.

If you lose the answer, send the delete again: deleting a project that is already gone changes nothing.

Members

The member object:

{"user_id": 3, "name": "Jane Doe", "email": "[email protected]", "role": "admin"}

email is shown to admins and the owner only.

List members

GET /projects/{slug}/members/

Any member · Read-only keys: yes · Returns 200 Member[]

The project's members and their roles.

Change a member's role

PATCH /projects/{slug}/members/{user_id}/

Admin or owner · Read-only keys: no · Returns 200 Member

Change a member's role to admin or member. Nobody can change the owner's role, or make someone the owner.

{"role": "admin"}

Remove a member

DELETE /projects/{slug}/members/{user_id}/

Admin or owner, or the member themselves · Read-only keys: no · Returns 204

Remove someone from the project. Admins and the owner can remove anyone except the owner, and any member can remove themselves. Removing a member, or making an admin a member, also withdraws every invitation they sent for the project that is still waiting.

Invitations

You invite someone by email. The email holds a link to accept, which takes them to the console. An invitation can be accepted only while the person who sent it is still an admin or the owner of the project.

The invitation object:

{
  "id": "0b7e6a2c-5d1f-4c3a-9e8b-2f6d4a1c7e90",
  "email": "[email protected]",
  "role": "member",
  "invited_by": "Jane Doe",
  "created_at": "2026-10-19T10:00:00Z"
}

List invitations

GET /projects/{slug}/invitations/

Admin or owner · Read-only keys: yes · Returns 200 Invitation[]

The invitations that are waiting to be accepted.

Invite someone

POST /projects/{slug}/invitations/

Admin or owner · Read-only keys: no · Returns 201 Invitation

Invite someone, as member or admin.

{"email": "[email protected]", "role": "member"}

An email that already has an invitation waiting answers 409 invitation_pending: resend that one instead. Inviting needs a billing account of your own; without one the answer is 403 billing_account_required.

Withdraw an invitation

DELETE /projects/{slug}/invitations/{id}/

Admin or owner · Read-only keys: no · Returns 204

Withdraw an invitation. Its link stops working.

Resend an invitation

POST /projects/{slug}/invitations/{id}/resend/

Admin or owner · Read-only keys: no · Returns 200 Invitation

Send the invitation's email again. The answer is the invitation.

Errors

Status Code When
400 validation_error A field is invalid, such as a slug that breaks the rules or a changed slug.
403 insufficient_role Your role doesn't allow it.
403 billing_account_required Inviting without a billing account.
403 project_not_ready The project is still being set up.
404 not_found No such project, member or invitation, or you are not a member.
409 limit_reached You already have 20 projects.
409 name_taken The slug is taken, or was used before.
409 project_not_empty Deleting a project that still has resources, orders or actions.
409 project_deleting Ordering or starting an action while the project is being deleted.
409 invitation_pending The email address already has an invitation waiting.