# Your API keys

Test and live keys, secret and publishable, and your first authenticated request.

Every request to the AdProMaxPay API carries a key. The key tells the API which organization you are
and which mode you work in, test or live.

## The four kinds of key

| Prefix          | Kind        | Mode |
| --------------- | ----------- | ---- |
| `apx_sk_test_…` | Secret      | Test |
| `apx_pk_test_…` | Publishable | Test |
| `apx_sk_live_…` | Secret      | Live |
| `apx_pk_live_…` | Publishable | Live |

* A **secret key** acts for your whole organization. It belongs on your server, never in a web page
  or an app.
* A **publishable key** is made to sit in a web page or an app. Today every API route asks for a
  secret key.
* **Test keys** work as soon as you sign up. **Live keys** work once your account is activated.

Your sandbox's test secret key is shown once, when you sign up. Later keys are created on the
dashboard's API keys page, and a secret key is shown only once, when it is created: store it then.

## Send your first request

Put your test secret key in an environment variable, then ask the API who you are. Send the key as
a bearer token, or as the user name of HTTP Basic authentication with an empty password.

The answer is your account: its ID, its activation status (`sandbox` until activation) and the API
version it uses.

```bash
curl https://pay-api.adpromax.com/v1/account \
  -u "$APX_SECRET_KEY:"
```

```js title="account.mjs"
const response = await fetch('https://pay-api.adpromax.com/v1/account', {
  headers: { Authorization: `Bearer ${process.env.APX_SECRET_KEY}` },
})
const account = await response.json()
if (!response.ok)
  throw new Error(account.error.message)
console.log(account.id, account.activation_status)
```

```ts title="account.ts"
interface Account {
  id: string
  object: 'account'
  business_name: string
  activation_status: string
  api_version: string
}

interface ApiError {
  error: { type: string, code: string, message: string, request_id: string }
}

const response = await fetch('https://pay-api.adpromax.com/v1/account', {
  headers: { Authorization: `Bearer ${process.env.APX_SECRET_KEY}` },
})
if (!response.ok) {
  const { error } = (await response.json()) as ApiError
  throw new Error(`${error.code}: ${error.message}`)
}
const account = (await response.json()) as Account
console.log(account.id, account.activation_status)
```

```python title="account.py"
import os

import requests

response = requests.get(
    "https://pay-api.adpromax.com/v1/account",
    auth=(os.environ["APX_SECRET_KEY"], ""),
)
account = response.json()
if not response.ok:
    raise RuntimeError(account["error"]["message"])
print(account["id"], account["activation_status"])
```

```php title="account.php"
<?php
$ch = curl_init('https://pay-api.adpromax.com/v1/account');
curl_setopt_array($ch, [
    CURLOPT_USERPWD => getenv('APX_SECRET_KEY') . ':',
    CURLOPT_RETURNTRANSFER => true,
]);
$account = json_decode(curl_exec($ch), true);
if (curl_getinfo($ch, CURLINFO_RESPONSE_CODE) >= 400) {
    throw new RuntimeException($account['error']['message']);
}
echo $account['id'], ' ', $account['activation_status'], PHP_EOL;
```

```ruby title="account.rb"
require 'json'
require 'net/http'

uri = URI('https://pay-api.adpromax.com/v1/account')
request = Net::HTTP::Get.new(uri)
request.basic_auth(ENV.fetch('APX_SECRET_KEY'), '')

response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == 'https') do |http|
  http.request(request)
end
account = JSON.parse(response.body)
raise account.dig('error', 'message') unless response.is_a?(Net::HTTPSuccess)

puts "#{account['id']} #{account['activation_status']}"
```

```go title="account.go"
package main

import (
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"os"
)

type Account struct {
	ID               string `json:"id"`
	BusinessName     string `json:"business_name"`
	ActivationStatus string `json:"activation_status"`
	APIVersion       string `json:"api_version"`
}

type APIError struct {
	Error struct {
		Code    string `json:"code"`
		Message string `json:"message"`
	} `json:"error"`
}

func main() {
	req, err := http.NewRequest(http.MethodGet, "https://pay-api.adpromax.com/v1/account", nil)
	if err != nil {
		log.Fatal(err)
	}
	req.SetBasicAuth(os.Getenv("APX_SECRET_KEY"), "")

	res, err := http.DefaultClient.Do(req)
	if err != nil {
		log.Fatal(err)
	}
	defer res.Body.Close()

	if res.StatusCode >= 400 {
		var apiErr APIError
		json.NewDecoder(res.Body).Decode(&apiErr)
		log.Fatal(apiErr.Error.Message)
	}
	var account Account
	if err := json.NewDecoder(res.Body).Decode(&account); err != nil {
		log.Fatal(err)
	}
	fmt.Println(account.ID, account.ActivationStatus)
}
```

```java title="Account.java"
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

public class Account {
    public static void main(String[] args) throws Exception {
        HttpRequest request = HttpRequest.newBuilder(URI.create("https://pay-api.adpromax.com/v1/account"))
            .header("Authorization", "Bearer " + System.getenv("APX_SECRET_KEY"))
            .GET()
            .build();
        HttpResponse<String> response = HttpClient.newHttpClient()
            .send(request, HttpResponse.BodyHandlers.ofString());
        if (response.statusCode() >= 400) {
            throw new IllegalStateException(response.body());
        }
        // The account as JSON: read it with your JSON library (Jackson, Gson…).
        System.out.println(response.body());
    }
}
```

```csharp title="Account.cs"
using System.Net.Http.Headers;
using System.Net.Http.Json;
using System.Text.Json;

var json = new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower };
using var http = new HttpClient();
http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
    "Bearer", Environment.GetEnvironmentVariable("APX_SECRET_KEY"));

using var response = await http.GetAsync("https://pay-api.adpromax.com/v1/account");
if (!response.IsSuccessStatusCode)
{
    var failure = await response.Content.ReadFromJsonAsync<ApiError>(json);
    throw new InvalidOperationException(failure?.Error.Message);
}
var account = await response.Content.ReadFromJsonAsync<Account>(json);
Console.WriteLine($"{account!.Id} {account.ActivationStatus}");

record Account(string Id, string BusinessName, string ActivationStatus, string ApiVersion);
record ApiError(ApiErrorBody Error);
record ApiErrorBody(string Code, string Message);
```

```rust title="main.rs"
// Cargo.toml: reqwest = { version = "0.12", features = ["blocking", "json"] }
//             serde = { version = "1", features = ["derive"] }
use serde::Deserialize;

#[derive(Deserialize)]
struct Account {
    id: String,
    activation_status: String,
}

#[derive(Deserialize)]
struct ApiError {
    error: ErrorBody,
}

#[derive(Deserialize)]
struct ErrorBody {
    message: String,
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let response = reqwest::blocking::Client::new()
        .get("https://pay-api.adpromax.com/v1/account")
        .bearer_auth(std::env::var("APX_SECRET_KEY")?)
        .send()?;
    if !response.status().is_success() {
        let failure: ApiError = response.json()?;
        return Err(failure.error.message.into());
    }
    let account: Account = response.json()?;
    println!("{} {}", account.id, account.activation_status);
    Ok(())
}
```

```json title="200 OK" response
{
  "id": "org_01k7cz9q3m8v6t2x4b5n7p1r0s",
  "object": "account",
  "business_name": "Atlas Shop",
  "country": "MA",
  "default_currency": "mad",
  "activation_status": "sandbox",
  "capabilities": {
    "card_payments": "inactive",
    "cash_payments": "inactive"
  },
  "api_version": "2026-11-01",
  "created": 1790847000
}
```

## When a key is refused

The API answers `401` or `403` with an error object; its `code` says why.

| Status | `code`                    | What happened                                                  |
| ------ | ------------------------- | -------------------------------------------------------------- |
| 401    | `api_key_missing`         | The request carried no key.                                    |
| 401    | `api_key_invalid`         | The key does not exist. Check for a missing character.         |
| 401    | `api_key_revoked`         | The key was revoked. Use another one.                          |
| 401    | `api_key_expired`         | The key was rolled and its overlap has ended. Use the new key. |
| 403    | `secret_key_required`     | A publishable key was used where a secret key is needed.       |
| 403    | `account_inactive`        | The account is suspended or closed.                            |
| 403    | `live_mode_not_activated` | A live key was used before the account was activated.          |

The error's `message` explains it in the language of your request, and its `request_id` identifies
the request if you write to support.

## Keep secret keys secret

* Read the key from an environment variable or a secret store; never commit it to a repository.
* Never put a secret key in a web page, a mobile app or a support ticket.
* If a secret key may have leaked, revoke it on the dashboard and create a new one.

Rolling, revoking and the rest: [Security](https://pay-docs.adpromax.com/en/account/security).
