# Add an App Store purchase

Checks the signed transaction from StoreKit and adds its credits once. Sending the same transaction again adds nothing. Finish the transaction in StoreKit after a 200.

`POST https://api.switchfile.io/v1/account/app-store/transactions`

Needs an API key: `Authorization: Bearer YOUR_API_KEY`. See [Authentication](https://switchfile.io/docs/authentication).

## Example

cURL:

```bash
curl "https://api.switchfile.io/v1/account/app-store/transactions" \
  -H "Authorization: Bearer $SWITCHFILE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "signed_transaction": "eyJhbGciOiJFUzI1NiIsIng1YyI6WyJNSUlF..."
  }'
```

Node.js:

```js
const res = await fetch("https://api.switchfile.io/v1/account/app-store/transactions", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.SWITCHFILE_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    signed_transaction: "eyJhbGciOiJFUzI1NiIsIng1YyI6WyJNSUlF..."
  }),
});
if (!res.ok) throw new Error(`HTTP ${res.status}: ${await res.text()}`);
console.log(await res.json());
```

PHP:

```php
<?php
$ch = curl_init('https://api.switchfile.io/v1/account/app-store/transactions');
curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_POSTFIELDS => json_encode([
        'signed_transaction' => 'eyJhbGciOiJFUzI1NiIsIng1YyI6WyJNSUlF...',
    ]),
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('SWITCHFILE_API_KEY'),
        'Content-Type: application/json',
    ],
    CURLOPT_RETURNTRANSFER => true,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);

if ($status >= 400) {
    throw new RuntimeException("HTTP $status: $response");
}
print_r(json_decode($response, true));
```

Python:

```python
import os
import requests

res = requests.post(
    "https://api.switchfile.io/v1/account/app-store/transactions",
    headers={"Authorization": f"Bearer {os.environ['SWITCHFILE_API_KEY']}"},
    json={
        "signed_transaction": "eyJhbGciOiJFUzI1NiIsIng1YyI6WyJNSUlF...",
    },
)
res.raise_for_status()
print(res.json())
```

## Request body

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `signed_transaction` | string | yes | Transaction.jwsRepresentation from StoreKit (10 to 64000 characters) |

## Responses

### 200: OK

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `balance` | string (integer) \| integer | yes |  |
| `credited` | boolean | yes | false: nothing was added now, because the transaction was added before or Apple refunded it (finish it in StoreKit anyway) |
| `credits` | string (integer) \| integer | yes | Credits this request added (0 when nothing was added) |
| `product_id` | string | yes |  |
| `refunded` | boolean | yes | true: Apple refunded the whole purchase, so it adds no credits |
| `transaction_id` | string | yes |  |

Example (application/json):

```json
{
  "transaction_id": "2000000812345678",
  "product_id": "io.switchfile.app.credits1000",
  "credits": 1000,
  "credited": true,
  "refunded": false,
  "balance": 2240
}
```

### 401: The API key is missing or not valid (`UNAUTHENTICATED`)

Example (application/problem+json):

```json
{
  "type": "https://switchfile.io/docs/api/v1#errors-unauthenticated",
  "title": "Unauthenticated",
  "status": 401,
  "code": "UNAUTHENTICATED",
  "detail": "Missing or invalid API key."
}
```

### 403: The purchase was made for another account (appAccountToken), or is a test purchase from an account that is not a test account (`TRANSACTION_NOT_YOURS`)

Example (application/problem+json):

```json
{
  "type": "https://switchfile.io/docs/api/v1#errors-transaction-not-yours",
  "title": "Transaction not yours",
  "status": 403,
  "code": "TRANSACTION_NOT_YOURS",
  "detail": "This purchase was made for another switchfile account."
}
```

### 404: Does not exist for this account (`NOT_FOUND`)

Example (application/problem+json):

```json
{
  "type": "https://switchfile.io/docs/api/v1#errors-not-found",
  "title": "Not found",
  "status": 404,
  "code": "NOT_FOUND",
  "detail": "Not found."
}
```

### 409: Already added to another account (`TRANSACTION_ALREADY_USED`)

Example (application/problem+json):

```json
{
  "type": "https://switchfile.io/docs/api/v1#errors-transaction-already-used",
  "title": "Transaction already used",
  "status": 409,
  "code": "TRANSACTION_ALREADY_USED",
  "detail": "This purchase was already added to another account."
}
```

### 422: Not signed by the App Store, or an unknown product (`INVALID_TRANSACTION`)

Example (application/problem+json):

```json
{
  "type": "https://switchfile.io/docs/api/v1#errors-invalid-transaction",
  "title": "Invalid transaction",
  "status": 422,
  "code": "INVALID_TRANSACTION",
  "detail": "The transaction is not signed by the App Store."
}
```

### 429: Too many requests or a daily quota is used up. See Retry-After (`RATE_LIMIT_EXCEEDED`)

Example (application/problem+json):

```json
{
  "type": "https://switchfile.io/docs/api/v1#errors-rate-limit-exceeded",
  "title": "Rate limit exceeded",
  "status": 429,
  "code": "RATE_LIMIT_EXCEEDED",
  "detail": "Too many requests. Try again in 12 seconds."
}
```

### 503: Not available right now or busy. See Retry-After (`PURCHASES_UNAVAILABLE`)

Example (application/problem+json):

```json
{
  "type": "https://switchfile.io/docs/api/v1#errors-purchases-unavailable",
  "title": "Purchases unavailable",
  "status": 503,
  "code": "PURCHASES_UNAVAILABLE",
  "detail": "Purchases in the app are not available right now."
}
```

Error bodies and codes are explained in [Errors](https://switchfile.io/docs/errors).
