diff --git a/publicapi/device.md b/publicapi/device.md index bff0e3eef..ebb466fe5 100644 --- a/publicapi/device.md +++ b/publicapi/device.md @@ -208,6 +208,9 @@ You can also [list all devices in the tailnet](#list-tailnet-devices) to get the - Get device posture attributes: [`GET /api/v2/device/{deviceID}/attributes`](#get-device-posture-attributes) - Set custom device posture attributes: [`POST /api/v2/device/{deviceID}/attributes/{attributeKey}`](#set-device-posture-attributes) - Delete custom device posture attributes: [`DELETE /api/v2/device/{deviceID}/attributes/{attributeKey}`](#delete-custom-device-posture-attributes) +- [**Device invites**](#invites-to-a-device) + - List device invites: [`GET /api/v2/device/{deviceID}/device-invites`](#list-device-invites) + - Create device invites: [`POST /api/v2/device/{deviceID}/device-invites`](#create-device-invites) ### Subnet routes @@ -793,3 +796,112 @@ curl -X DELETE "https://api.tailscale.com/api/v2/device/11055/attributes/custom: ### Response The response is 2xx on success. The response body is currently an empty JSON object. + +## Invites to a device + +The device sharing invite methods let you create and list [invites to share a device](https://tailscale.com/kb/1084/sharing). + +## List device invites + +```http +GET /api/v2/device/{deviceID}/device-invites +``` + +List all share invites for a device. + +### Parameters + +#### `deviceID` (required in URL path) + +The ID of the device. + +### Request example + +```sh +curl -X GET "https://api.tailscale.com/api/v2/device/11055/device-invites" \ +-u "tskey-api-xxxxx:" +``` + +### Response + +```jsonc +[ + { + "id": "12345", + "created": "2024-05-08T20:19:51.777861756Z", + "tailnetId": 59954, + "deviceId": 11055, + "sharerId": 22011, + "allowExitNode": true, + "email": "user@example.com", + "lastEmailSentAt": "2024-05-08T20:19:51.777861756Z", + "inviteUrl": "https://login.tailscale.com/admin/invite/", + "accepted": false + }, + { + "id": "12346", + "created": "2024-04-03T21:38:49.333829261Z", + "tailnetId": 59954, + "deviceId": 11055, + "sharerId": 22012, + "inviteUrl": "https://login.tailscale.com/admin/invite/", + "accepted": true, + "acceptedBy": { + "id": 33223, + "loginName": "someone@example.com", + "profilePicUrl": "" + } + } +] +``` + +## Create device invites + +```http +POST /api/v2/device/{deviceID}/device-invites +``` + +Create new share invites for a device. + +### Parameters + +#### `deviceID` (required in URL path) + +The ID of the device. + +#### List of invite requests (required in `POST` body) + +Each invite request is an object with the following optional fields: + +- **`multiUse`:** (Optional) Specify whether the invite can be accepted more than once. When set to `true`, it results in an invite that can be accepted up to 1,000 times. +- **`allowExitNode`:** (Optional) Specify whether the invited user can use the device as an exit node when it advertises as one. +- **`email`:** (Optional) Specify the email to send the created invite. If not set, the endpoint generates and returns an invite URL (but doesn't send it out). + +### Request example + +```sh +curl -X POST "https://api.tailscale.com/api/v2/device/11055/device-invites" \ +-u "tskey-api-xxxxx:" \ +-H "Content-Type: application/json" \ +--data-binary '[{"multiUse": true, "allowExitNode": true, "email":"user@example.com"}]' +``` + +### Response + +```jsonc +[ + { + "id": "12347", + "created": "2024-05-08T20:29:45.842358533Z", + "tailnetId": 59954, + "deviceId": 11055, + "sharerId": 22012, + "multiUse": true, + "allowExitNode": true, + "email": "user@example.com", + "lastEmailSentAt": "2024-05-08T20:29:45.842358533Z", + "inviteUrl": "https://login.tailscale.com/admin/invite/", + "accepted": false + } +] +``` diff --git a/publicapi/deviceinvites.md b/publicapi/deviceinvites.md new file mode 100644 index 000000000..2c32c9ebe --- /dev/null +++ b/publicapi/deviceinvites.md @@ -0,0 +1,221 @@ +# Device invites + +A device invite is an invitation that shares a device with an external user (a user not in the device's tailnet). + +Each device invite has a unique ID that is used to identify the invite in API calls. +You can find all device invite IDs for a particular device by [listing all device invites for a device](#list-device-invites). + +### Attributes + +```jsonc +{ + // id (strings) is the unique identifier for the invite. + // Supply this value wherever {deviceInviteId} is indicated in the endpoint. + "id": "12346", + + // created is the creation time of the invite. + "created": "2024-04-03T21:38:49.333829261Z", + + // tailnetId is the ID of the tailnet to which the shared device belongs. + "tailnetId": 59954, + + // deviceId is the ID of the device being shared. + "deviceId": 11055, + + // sharerId is the ID of the user who created the share invite. + "sharerId": 22012, + + // multiUse specifies whether this device invite can be accepted more than + // once. + "multiUse": false, + + // allowExitNode specifies whether the invited user is able to use the + // device as an exit node when the device is advertising as one. + "allowExitNode": true, + + // email is the email to which the invite was sent. + // If empty, the invite was not emailed to anyone, but the inviteUrl can be + // shared manually. + "email": "user@example.com", + + // lastEmailSentAt is the last time the invite was attempted to be sent to + // Email. Only ever set if Email is not empty. + "lastEmailSentAt": "2024-04-03T21:38:49.333829261Z", + + // inviteUrl is the link to accept the invite. + // Anyone with this link can accept the invite. + // It is not restricted to the person to which the invite was emailed. + "inviteUrl": "https://login.tailscale.com/admin/invite/", + + // accepted is true when share invite has been accepted. + "accepted": true, + + // acceptedBy is set when the invite has been accepted. + // It holds information about the user who accepted the share invite. + "acceptedBy": { + // id is the ID of the user who accepted the share invite. + "id": 33223, + + // loginName is the login name of the user who accepted the share invite. + "loginName": "someone@example.com", + + // profilePicUrl is optionally the profile pic URL for the user who accepted + // the share invite. + "profilePicUrl": "" + } +} +``` + +# API + +**[Device invites](#device-invites)** + +- Get device invite: [`GET /api/v2/device-invites/{deviceInviteId}`](#get-device-invite) +- Delete device invite: [`DELETE /api/v2/device-invites/{deviceInviteId}`](#delete-device-invite) +- Resend device invite (by email): [`POST /api/v2/device-invites/{deviceInviteId}/resend`](#resend-device-invite) +- Accept device invite [`POST /api/v2/device-invites/-/accept`](#accept-device-invite) + +## Get device invite + +```http +GET /api/v2/device-invites/{deviceInviteId} +``` + +Retrieve the specified device invite. + +### Parameters + +#### `deviceInviteId` (required in URL path) + +The ID of the device share invite. + +### Request example + +```sh +curl "https://api.tailscale.com/api/v2/device-invites/12346" \ + -u "tskey-api-xxxxx:" +``` + +### Response + +```jsonc +{ + "id": "12346", + "created": "2024-04-03T21:38:49.333829261Z", + "tailnetId": 59954, + "deviceId": 11055, + "sharerId": 22012, + "multiUse": true, + "allowExitNode": true, + "email": "user@example.com", + "lastEmailSentAt": "2024-04-03T21:38:49.333829261Z", + "inviteUrl": "https://login.tailscale.com/admin/invite/", + "accepted": false +} +``` + +## Delete device invite + +```http +DELETE /api/v2/device-invites/{deviceInviteId} +``` + +Delete the specified device invite. + +### Parameters + +#### `deviceInviteId` (required in URL path) + +The ID of the device share invite. + +### Request example + +```sh +curl -X DELETE "https://api.tailscale.com/api/v2/device-invites/12346" \ + -u "tskey-api-xxxxx:" +``` + +### Response + +The response is 2xx on success. The response body is an empty JSON object. + +## Resend device invite + +```http +POST /api/v2/device-invites/{deviceInviteId}/resend +``` + +Resend the specified device invite by email. You can only use this if the specified invite was originally created with an email specified. Refer to [creating device invites for a device](#create-device-invites). + +Note: Invite resends are rate limited to one per minute. + +### Parameters + +#### `deviceInviteId` (required in URL path) + +The ID of the device share invite. + +### Request example + +```sh +curl -X POST "https://api.tailscale.com/api/v2/device-invites/12346/resend" \ + -u "tskey-api-xxxxx:" +``` + +### Response + +The response is 2xx on success. The response body is an empty JSON object. + +## Accept device invite + +```http +POST /api/v2/device-invites/-/accept +``` + +Resend the specified device invite by email. This can only be used if the specified invite was originally created with an email specified. +See [creating device invites for a device](#create-device-invites). + +Note that invite resends are rate limited to once per minute. + +### Parameters + +#### `invite` (required in `POST` body) + +The URL of the invite (in the form "https://login.tailscale.com/admin/invite/{code}") or the "{code}" component of the URL. + +### Request example + +```sh +curl -X POST "https://api.tailscale.com/api/v2/device-invites/-/accept" \ + -u "tskey-api-xxxxx:" \ + -H "Content-Type: application/json" \ + --data-binary '[{"invite": "https://login.tailscale.com/admin/invite/xxxxxx"}]' +``` + +### Response + +```jsonc +{ + "device": { + "id": "11055", + "os": "iOS", + "name": "my-phone", + "fqdn": "my-phone.something.ts.net", + "ipv4": "100.x.y.z", + "ipv6": "fd7a:115c:x::y:z", + "includeExitNode": false + }, + "sharer": { + "id": "22012", + "displayName": "Some User", + "loginName": "someuser@example.com", + "profilePicURL": "" + }, + "acceptedBy": { + "id": "33233", + "displayName": "Another User", + "loginName": "anotheruser@exmaple2.com", + "profilePicURL": "" + } +} +``` diff --git a/publicapi/overview.md b/publicapi/overview.md index 91e5ab085..db0f3db1d 100644 --- a/publicapi/overview.md +++ b/publicapi/overview.md @@ -68,6 +68,9 @@ The Tailscale API does not currently support pagination. All results are returne - Get device posture attributes: [`GET /api/v2/device/{deviceID}/attributes`](./device.md#get-device-posture-attributes) - Set custom device posture attributes: [`POST /api/v2/device/{deviceID}/attributes/{attributeKey}`](./device.md#set-device-posture-attributes) - Delete custom device posture attributes: [`DELETE /api/v2/device/{deviceID}/attributes/{attributeKey}`](./device.md#delete-custom-device-posture-attributes) +- [**Device invites**](./device.md#invites-to-a-device) + - List device invites: [`GET /api/v2/device/{deviceID}/device-invites`](./device.md#list-device-invites) + - Create device invites: [`POST /api/v2/device/{deviceID}/device-invites`](./device.md#create-device-invites) **[Tailnet](./tailnet.md#tailnet)** @@ -97,3 +100,19 @@ The Tailscale API does not currently support pagination. All results are returne - Get split DNS: [`GET /api/v2/tailnet/{tailnet}/dns/split-dns`](./tailnet.md#get-split-dns) - Update split DNS: [`PATCH /api/v2/tailnet/{tailnet}/dns/split-dns`](./tailnet.md#update-split-dns) - Set split DNS: [`PUT /api/v2/tailnet/{tailnet}/dns/split-dns`](./tailnet.md#set-split-dns) +- [**User invites**](./tailnet.md#tailnet-user-invites) + - List user invites: [`GET /api/v2/tailnet/{tailnet}/user-invites`](./tailnet.md#list-user-invites) + - Create user invites: [`POST /api/v2/tailnet/{tailnet}/user-invites`](./tailnet.md#create-user-invites) + +**[User invites](./userinvites.md#user-invites)** + +- Get user invite: [`GET /api/v2/user-invites/{userInviteId}`](./userinvites.md#get-user-invite) +- Delete user invite: [`DELETE /api/v2/user-invites/{userInviteId}`](./userinvites.md#delete-user-invite) +- Resend user invite (by email): [`POST /api/v2/user-invites/{userInviteId}/resend`](#resend-user-invite) + +**[Device invites](./deviceinvites.md#device-invites)** + +- Get device invite: [`GET /api/v2/device-invites/{deviceInviteId}`](./deviceinvites.md#get-device-invite) +- Delete device invite: [`DELETE /api/v2/device-invites/{deviceInviteId}`](./deviceinvites.md#delete-device-invite) +- Resend device invite (by email): [`POST /api/v2/device-invites/{deviceInviteId}/resend`](./deviceinvites.md#resend-device-invite) +- Accept device invite [`POST /api/v2/device-invites/-/accept`](#accept-device-invite) diff --git a/publicapi/tailnet.md b/publicapi/tailnet.md index d6426b62b..19452b7b2 100644 --- a/publicapi/tailnet.md +++ b/publicapi/tailnet.md @@ -52,6 +52,9 @@ When specifying a tailnet in the API, you can: - Get split DNS: [`GET /api/v2/tailnet/{tailnet}/dns/split-dns`](#get-split-dns) - Update split DNS: [`PATCH /api/v2/tailnet/{tailnet}/dns/split-dns`](#update-split-dns) - Set split DNS: [`PUT /api/v2/tailnet/{tailnet}/dns/split-dns`](#set-split-dns) +- [**User invites**](#tailnet-user-invites) + - List user invites: [`GET /api/v2/tailnet/{tailnet}/user-invites`](#list-user-invites) + - Create user invites: [`POST /api/v2/tailnet/{tailnet}/user-invites`](#create-user-invites) ## Policy File @@ -1316,3 +1319,103 @@ The response is a JSON object containing the updated map of split DNS settings. ```jsonc {} ``` + +## Tailnet user invites + +The tailnet user invite methods let you create and list [invites](https://tailscale.com/kb/1371/invite-users). + +## List user invites + +```http +GET /api/v2/tailnet/{tailnet}/user-invites +``` + +List all user invites that haven't been accepted. + +### Parameters + +#### `tailnet` (required in URL path) + +The tailnet organization name. + +### Request example + +```sh +curl -X GET "https://api.tailscale.com/api/v2/tailnet/example.com/user-invites" \ +-u "tskey-api-xxxxx:" +``` + +### Response + +```jsonc +[ + { + "id": "29214", + "role": "member", + "tailnetId": 12345, + "inviterId": 34567, + "email": "user@example.com", + "lastEmailSentAt": "2024-05-09T16:13:16.084568545Z", + "inviteUrl": "https://login.tailscale.com/uinv/" + }, + { + "id": "29215", + "role": "admin", + "tailnetId": 12345, + "inviterId": 34567, + "inviteUrl": "https://login.tailscale.com/uinv/" + } +] +``` + +## Create user invites + +```http +POST /api/v2/tailnet/{tailnet}/user-invites +``` + +Create new user invites to join the tailnet. + +### Parameters + +#### `tailnet` (required in URL path) + +The tailnet organization name. + +#### List of invite requests (required in `POST` body) + +Each invite request is an object with the following optional fields: + +- **`role`:** (Optional) Specify a [user role](https://tailscale.com/kb/1138/user-roles) to assign the invited user. Defaults to the `"member"` role. Valid options are: + - `"member"`: Assign the Member role. + - `"admin"`: Assign the Admin role. + - `"it-admin"`: Assign the IT admin role. + - `"network-admin"`: Assign the Network admin role. + - `"billing-admin"`: Assign the Billing admin role. + - `"auditor"`: Assign the Auditor role. +- **`email`:** (Optional) Specify the email to send the created invite. If not set, the endpoint generates and returns an invite URL (but doesn't send it out). + +### Request example + +```sh +curl -X POST "https://api.tailscale.com/api/v2/tailnet/example.com/user-invites" \ +-u "tskey-api-xxxxx:" \ +-H "Content-Type: application/json" \ +--data-binary '[{"role": "admin", "email":"user@example.com"}]' +``` + +### Response + +```jsonc +[ + { + "id": "29214", + "role": "admin", + "tailnetId": 12345, + "inviterId": 34567, + "email": "user@example.com", + "lastEmailSentAt": "2024-05-09T16:23:26.91778771Z", + "inviteUrl": "https://login.tailscale.com/uinv/" + } +] +``` diff --git a/publicapi/userinvites.md b/publicapi/userinvites.md new file mode 100644 index 000000000..cccabf940 --- /dev/null +++ b/publicapi/userinvites.md @@ -0,0 +1,144 @@ +# User invites + +A user invite is an active invitation that lets a user join a tailnet with a pre-assigned [user role](https://tailscale.com/kb/1138/user-roles). + +Each user invite has a unique ID that is used to identify the invite in API calls. +You can find all user invite IDs for a particular tailnet by [listing user invites](#list-user-invites). + +### Attributes + +```jsonc +{ + // id (string) is the unique identifier for the invite. + // Supply this value wherever {userInviteId} is indicated in the endpoint. + "id": "12346", + + // role is the tailnet user role to assign to the invited user upon accepting + // the invite. Value options are "member", "admin", "it-admin", "network-admin", + // "billing-admin", and "auditor". + "role": "admin", + + // tailnetId is the ID of the tailnet to which the user was invited. + "tailnetId": 59954, + + // inviterId is the ID of the user who created the invite. + "inviterId": 22012, + + // email is the email to which the invite was sent. + // If empty, the invite was not emailed to anyone, but the inviteUrl can be + // shared manually. + "email": "user@example.com", + + // lastEmailSentAt is the last time the invite was attempted to be sent to + // Email. Only ever set if `email` is not empty. + "lastEmailSentAt": "2024-04-03T21:38:49.333829261Z", + + // inviteUrl is included when `email` is not part of the tailnet's domain, + // or when `email` is empty. It is the link to accept the invite. + // + // When included, anyone with this link can accept the invite. + // It is not restricted to the person to which the invite was emailed. + // + // When `email` is part of the tailnet's domain (has the same @domain.com + // suffix as the tailnet), the user can join the tailnet automatically by + // logging in with their domain email at https://login.tailscale.com/start. + // They'll be assigned the specified `role` upon signing in for the first + // time. + "inviteUrl": "https://login.tailscale.com/admin/invite/" +} +``` + +# API + +**[User invites](#user-invites)** + +- Get user invite: [`GET /api/v2/user-invites/{userInviteId}`](#get-user-invite) +- Delete user invite: [`DELETE /api/v2/user-invites/{userInviteId}`](#delete-user-invite) +- Resend user invite (by email): [`POST /api/v2/user-invites/{userInviteId}/resend`](#resend-user-invite) + +## Get user invite + +```http +GET /api/v2/user-invites/{userInviteId} +``` + +Retrieve the specified user invite. + +### Parameters + +#### `userInviteId` (required in URL path) + +The ID of the user invite. + +### Request example + +```sh +curl "https://api.tailscale.com/api/v2/user-invites/29214" \ + -u "tskey-api-xxxxx:" +``` + +### Response + +```jsonc +{ + "id": "29214", + "role": "admin", + "tailnetId": 12345, + "inviterId": 34567, + "email": "user@example.com", + "lastEmailSentAt": "2024-05-09T16:23:26.91778771Z", + "inviteUrl": "https://login.tailscale.com/uinv/" +} +``` + +## Delete user invite + +```http +DELETE /api/v2/user-invites/{userInviteId} +``` + +Delete the specified user invite. + +### Parameters + +#### `userInviteId` (required in URL path) + +The ID of the user invite. + +### Request example + +```sh +curl -X DELETE "https://api.tailscale.com/api/v2/user-invites/29214" \ + -u "tskey-api-xxxxx:" +``` + +### Response + +The response is 2xx on success. The response body is an empty JSON object. + +## Resend user invite + +```http +POST /api/v2/user-invites/{userInviteId}/resend +``` + +Resend the specified user invite by email. You can only use this if the specified invite was originally created with an email specified. Refer to [creating user invites for a tailnet](#create-user-invites). + +Note: Invite resends are rate limited to one per minute. + +### Parameters + +#### `userInviteId` (required in URL path) + +The ID of the user invite. + +### Request example + +```sh +curl -X POST "https://api.tailscale.com/api/v2/user-invites/29214/resend" \ + -u "tskey-api-xxxxx:" +``` + +### Response + +The response is 2xx on success. The response body is an empty JSON object.