> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.taktoria.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.taktoria.com/_mcp/server.

# Remove App

DELETE https://api.taktoria.com/api/v1/subscription/apps/{appSlug}

Take one app off the subscription, as unticking it on the billing
page does: it comes off now, with its unused time credited, and the
last app taken off cancels the subscription at the end of the period.
Its waiting list goes once it is off. The only way the API removes an
app. Requires `billing:write`.
`404 not_subscribed` when the workspace does not have it.

Reference: https://docs.taktoria.com/api-reference/billing/remove-app

## Authentication

- `Authorization` header (bearer token, required) — Bearer authentication of the form `Bearer <token>`, where token is your auth token.

## Servers

- `https://api.taktoria.com/api/v1` (Production, default)
- `http://localhost:3000/api/v1` (Local)

## Request

### Path parameters

- `appSlug` (string, required)

## Response

### 200

- `billingInterval` (enum, required) — How often the subscription is billed. Every line of one subscription shares it.
  - Allowed values: `month`, `year`
- `annualAvailable` (boolean, required) — Whether yearly billing can be chosen here. It needs a yearly price for every app.
- `billingAvailable` (boolean, required) — Whether this installation can take a subscription at all. When it cannot, `updateSubscription` answers `409 billing_unavailable`.
- `autoJoin` (boolean, required) — Whether any verified email domain has `autoJoin` on, so people can join without an invitation.
- `autoGrowSeats` (boolean, required) — The workspace's `autoGrowSeats` (`PATCH /organization`): with `autoJoin`, whether full apps grow for newcomers or they wait.
- `apps` (list of SubscriptionApp, required)
- `paymentIssue` (enum, optional) — Absent (null) while every payment has gone through.
  - Allowed values: `unpaid`, `past_due`, `payment_failed`
- `cardAddedAt` (datetime, optional) — When a card was put on file, by a completed Checkout or in the billing portal. Absent with no card, or once it is removed. Set, the workspace has subscribed: its trials carry on into the paid plan, and the app stops showing the trial banner.

## Errors

### 401 Unauthorized Error

- `error` (ErrorDetail, required)

### 403 Forbidden Error

Refused, and `error.code` says why. On an app's write endpoints, a signed-in session is refused `viewer_only` when the person is a Viewer of the app (they read it, and change nothing in it), and `seat_required` without a seat on it; a key is never either. Beyond those: `admin_required` when the change is an admin's (an app admin's for an app's admin powers, a workspace admin's for the workspace's settings), `forbidden` when the person may not change this one thing, `read_only_session` for a change only a key makes, `person_required` for what only a signed-in person does through their own account elsewhere (exporting a LaunchCal artifact to their docs tool), which a key cannot, `insufficient_scope` for a key without the scope, and `google_sign_in_required` for a session the workspace's Google rule turns away. Nothing was done.

- `error` (ErrorDetail, required)

### 404 Not Found Error

- `error` (ErrorDetail, required)

### 409 Conflict Error

The request conflicts with how things stand, and `error.code` says how: `conflict` in general, or an endpoint's own code (`draft`, `launched`, `steps_incomplete`, `ticketing_not_connected`, `docs_not_connected`, …), listed in its description and under Errors and pagination. Nothing was done.

- `error` (ErrorDetail, required)

### 502 Upstream Error

A service Taktoria depends on (Stripe, Vercel for app domains, or a ticketing system such as Linear, Jira, GitHub, Asana or ClickUp, or a docs tool a person exports to, Notion, Confluence or Google Docs) turned the change down or could not be reached. Nothing was saved; try again.

- `error` (ErrorDetail, required)

### 429 Too Many Requests Error

Rate limited (`rate_limited`): the key, the signed-in person, the workspace or an expensive endpoint's own budget is used up for now. `Retry-After` says how many seconds to wait, and `details` names the `policy`, its `limit`, `windowSeconds` and `retryAfter`. Every answer to an authenticated request carries the IETF `RateLimit-Policy` and `RateLimit` headers, so a client can slow down before it gets this. Nothing was done; wait and send it again. See "Rate limits".

- `error` (ErrorDetail, required)

## Types

### SubscriptionApp

One released app, and what the workspace pays for of it.

- `appSlug` (string, required)
- `billing` (enum, required) — What an app's price is charged on.
  - Allowed values: `per_seat`, `per_participant`
- `cancelAtPeriodEnd` (boolean, required)
- `seatsAssigned` (integer, required)
- `waiting` (integer, required) — How many people are on the app's waiting list: they joined while every seat was taken. Raising `seatsPaid` gives them seats at once, oldest first, as far as it goes. `GET /seats/waiting` names them.
- `growsAsPeopleJoin` (boolean, required) — Whether `seatsPaid` rises by itself as people join: true while the workspace has `autoJoin` and `autoGrowSeats` both on, for an app it subscribes to that gives new members a seat and whose seats are capped. A newcomer's default seat is then never refused for want of one: the cap rises by one for them, and Stripe's quantity with it, prorated. Settings → Billing notes it under the app.
- `minSeats` (integer, required) — The fewest seats this app is sold in: for Dory, its host seats. The floor is also never below `seatsAssigned`.
- `status` (enum, optional) — Absent for an app the workspace has never subscribed to.
  - Allowed values: `TRIALING`, `ACTIVE`, `PAST_DUE`, `CANCELED`, `INCOMPLETE`, `INCOMPLETE_EXPIRED`, `UNPAID`, `PAUSED`
- `seatsPaid` (integer, optional) — The most seats that can be handed out: the seats a per-seat app pays for, or Dory's host seats once they are billed. The same number as `seatCap` on the app. Absent when nothing caps them.
- `capacity` (integer, optional) — Dory only. Participants one event may hold before it bills the difference.
- `hostSeats` (integer, optional) — Dory only. Host seats paid for, on their own line. Absent while Dory's hosts are free, which they are where no host price is set.
- `minCapacity` (integer, optional) — Dory only. The smallest event capacity it is sold in.
- `trialEndsAt` (datetime, optional) — When a trial ends. A trial stays `TRIALING` to this date even once a card is on file (`cardAddedAt` on the subscription), and then carries on into the paid plan.
- `currentPeriodEnd` (datetime, optional)

### ErrorDetail

- `code` (string, required) — A stable machine-readable code such as `not_found`.
- `message` (string, required)
- `details` (any, optional)

## Examples

**Response**

```json
{
  "billingInterval": "month",
  "annualAvailable": true,
  "billingAvailable": true,
  "autoJoin": true,
  "autoGrowSeats": true,
  "apps": [
    {
      "appSlug": "appSlug",
      "billing": "per_seat",
      "cancelAtPeriodEnd": true,
      "seatsAssigned": 1,
      "waiting": 1,
      "growsAsPeopleJoin": true,
      "minSeats": 1,
      "status": "TRIALING",
      "seatsPaid": 1,
      "capacity": 1,
      "hostSeats": 1,
      "minCapacity": 1,
      "trialEndsAt": "2024-01-15T09:30:00Z",
      "currentPeriodEnd": "2024-01-15T09:30:00Z"
    },
    {
      "appSlug": "appSlug",
      "billing": "per_seat",
      "cancelAtPeriodEnd": true,
      "seatsAssigned": 1,
      "waiting": 1,
      "growsAsPeopleJoin": true,
      "minSeats": 1,
      "status": "TRIALING",
      "seatsPaid": 1,
      "capacity": 1,
      "hostSeats": 1,
      "minCapacity": 1,
      "trialEndsAt": "2024-01-15T09:30:00Z",
      "currentPeriodEnd": "2024-01-15T09:30:00Z"
    }
  ],
  "paymentIssue": "unpaid",
  "cardAddedAt": "2024-01-15T09:30:00Z"
}
```

**SDK Code**

```python
import requests

url = "https://api.taktoria.com/api/v1/subscription/apps/appSlug"

headers = {"Authorization": "Bearer <token>"}

response = requests.delete(url, headers=headers)

print(response.json())
```

```javascript
const url = 'https://api.taktoria.com/api/v1/subscription/apps/appSlug';
const options = {method: 'DELETE', headers: {Authorization: 'Bearer <token>'}};

try {
  const response = await fetch(url, options);
  const data = await response.json();
  console.log(data);
} catch (error) {
  console.error(error);
}
```

```go
package main

import (
	"fmt"
	"net/http"
	"io"
)

func main() {

	url := "https://api.taktoria.com/api/v1/subscription/apps/appSlug"

	req, _ := http.NewRequest("DELETE", url, nil)

	req.Header.Add("Authorization", "Bearer <token>")

	res, _ := http.DefaultClient.Do(req)

	defer res.Body.Close()
	body, _ := io.ReadAll(res.Body)

	fmt.Println(res)
	fmt.Println(string(body))

}
```

```ruby
require 'uri'
require 'net/http'

url = URI("https://api.taktoria.com/api/v1/subscription/apps/appSlug")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true

request = Net::HTTP::Delete.new(url)
request["Authorization"] = 'Bearer <token>'

response = http.request(request)
puts response.read_body
```

```java
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.Unirest;

HttpResponse<String> response = Unirest.delete("https://api.taktoria.com/api/v1/subscription/apps/appSlug")
  .header("Authorization", "Bearer <token>")
  .asString();
```

```php
<?php
require_once('vendor/autoload.php');

$client = new \GuzzleHttp\Client();

$response = $client->request('DELETE', 'https://api.taktoria.com/api/v1/subscription/apps/appSlug', [
  'headers' => [
    'Authorization' => 'Bearer <token>',
  ],
]);

echo $response->getBody();
```

```csharp
using RestSharp;

var client = new RestClient("https://api.taktoria.com/api/v1/subscription/apps/appSlug");
var request = new RestRequest(Method.DELETE);
request.AddHeader("Authorization", "Bearer <token>");
IRestResponse response = client.Execute(request);
```

```swift
import Foundation

let headers = ["Authorization": "Bearer <token>"]

let request = NSMutableURLRequest(url: NSURL(string: "https://api.taktoria.com/api/v1/subscription/apps/appSlug")! as URL,
                                        cachePolicy: .useProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.httpMethod = "DELETE"
request.allHTTPHeaderFields = headers

let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
  if (error != nil) {
    print(error as Any)
  } else {
    let httpResponse = response as? HTTPURLResponse
    print(httpResponse)
  }
})

dataTask.resume()
```