Welcome to the official APIKlone documentation. APIKlone is an enterprise-grade API Virtualization and stateful mocking proxy. Instantly record backend traffic, replay mocked responses conditionally, and auto-generate complex schemas to unblock your frontend and testing teams.

Note: APIKlone runs completely locally or isolated in your cloud. There is no bloated desktop application required; simply configure the proxy via the intuitive web dashboard or headless REST API.

Core Concepts: What is a Config?

Throughout APIKlone, the primary unit of scale is the Configuration (or "Config").

A Config maps exactly to a single target host. When you create a proxy, all endpoints under that host are automatically covered. For instance, pointing APIKlone to your Salesforce instance counts as one config, no matter how many hundreds of Salesforce endpoints you intend to mock or pass-through.

Concept Description
Config A logical grouping tied to a specific upstream base URL (e.g. https://api.stripe.com).
Service Mock An individual endpoint mapping (e.g. /v1/charges) that contains headers, payloads, and matching rules.
Traffic Recording The ability for APIKlone to proxy a request natively, and seamlessly capture both the outbound request and inbound response for future local regeneration.

Deploy in your cloud

APIKlone Self-Hosted is one container plus somewhere permanent to keep its data. Pick your cloud for step-by-step commands, including how to scale it out.

Permanent storage is not optional. A container's own disk is wiped on every restart and upgrade. Each guide attaches storage so your configs, mocks, users and API keys survive.

APIKlone on Azure Container Apps

Runs the container on Azure Container Apps with an Azure Files share mounted as its data folder. You need the Azure CLI in a Bash shell; Azure Cloud Shell works.

ComputeContainer Apps
StorageAzure Files share
SecretsContainer Apps secrets
HTTPSIncluded
TimeAbout 15 minutes

Deploy

1

Sign in and set names

Registering the providers is needed once per subscription. The storage account name must be globally unique: 3 to 24 lowercase letters and numbers.

az login
az extension add --name containerapp --upgrade
az provider register --namespace Microsoft.App
az provider register --namespace Microsoft.OperationalInsights

RESOURCE_GROUP="apiklone-rg"
LOCATION="uksouth"
ENVIRONMENT="apiklone-env"
APP_NAME="apiklone"
STORAGE_ACCOUNT="apiklonedata$RANDOM"
FILE_SHARE="apiklone-data"
STORAGE_LINK="apiklonestorage"
2

Create the environment

az group create --name $RESOURCE_GROUP --location $LOCATION

az containerapp env create \
  --name $ENVIRONMENT \
  --resource-group $RESOURCE_GROUP \
  --location $LOCATION
3

Create the storage

A 5 GiB share is plenty for most teams; mock files are small.

az storage account create \
  --name $STORAGE_ACCOUNT \
  --resource-group $RESOURCE_GROUP \
  --location $LOCATION \
  --kind StorageV2 \
  --sku Standard_LRS \
  --min-tls-version TLS1_2

az storage share-rm create \
  --name $FILE_SHARE \
  --storage-account $STORAGE_ACCOUNT \
  --resource-group $RESOURCE_GROUP \
  --quota 5 \
  --enabled-protocols SMB
4

Attach the storage to the environment

Container Apps connects to Azure Files with the storage account key.

STORAGE_KEY=$(az storage account keys list \
  --account-name $STORAGE_ACCOUNT \
  --resource-group $RESOURCE_GROUP \
  --query "[0].value" --output tsv)

az containerapp env storage set \
  --name $ENVIRONMENT \
  --resource-group $RESOURCE_GROUP \
  --storage-name $STORAGE_LINK \
  --storage-type AzureFile \
  --azure-file-account-name $STORAGE_ACCOUNT \
  --azure-file-account-key $STORAGE_KEY \
  --azure-file-share-name $FILE_SHARE \
  --access-mode ReadWrite
5

Deploy APIKlone

A volume mount needs a YAML definition. Paste the block as it is; the shell fills in the variables. The mount options give the container's non-root user (ID 1000) ownership of the share.

ACTIVATION_KEY="paste-your-activation-key-here"

ENVIRONMENT_ID=$(az containerapp env show \
  --name $ENVIRONMENT \
  --resource-group $RESOURCE_GROUP \
  --query id --output tsv)

cat > apiklone.yaml <<EOF
location: $LOCATION
type: Microsoft.App/containerApps
properties:
  managedEnvironmentId: $ENVIRONMENT_ID
  configuration:
    activeRevisionsMode: Single
    ingress:
      external: true
      targetPort: 3000
      transport: auto
      allowInsecure: false
    secrets:
      - name: activation-key
        value: "$ACTIVATION_KEY"
  template:
    containers:
      - name: apiklone
        image: docker.io/apiklone/apiklone:latest
        resources:
          cpu: 0.5
          memory: 1Gi
        env:
          - name: ACTIVATION_KEY
            secretRef: activation-key
          - name: PORT
            value: "3000"
        probes:
          - type: Liveness
            httpGet:
              path: /health
              port: 3000
            periodSeconds: 30
          - type: Readiness
            httpGet:
              path: /health
              port: 3000
            periodSeconds: 10
        volumeMounts:
          - volumeName: apiklone-data
            mountPath: /app/data
    scale:
      minReplicas: 1
      maxReplicas: 1
    volumes:
      - name: apiklone-data
        storageType: AzureFile
        storageName: $STORAGE_LINK
        mountOptions: "uid=1000,gid=1000,dir_mode=0770,file_mode=0660"
EOF

az containerapp create \
  --name $APP_NAME \
  --resource-group $RESOURCE_GROUP \
  --yaml apiklone.yaml

rm apiklone.yaml   # it contains your activation key in plain text
6

Open it and sign in

az containerapp show \
  --name $APP_NAME \
  --resource-group $RESOURCE_GROUP \
  --query properties.configuration.ingress.fqdn --output tsv

Open https://<that-address> and sign in as admin / admin. Change that password straight away from the Team page: the address is public.

7

Check the data is on the share

You should see users.properties at least.

az storage file list \
  --share-name $FILE_SHARE \
  --account-name $STORAGE_ACCOUNT \
  --account-key $STORAGE_KEY \
  --query "[].name" --output tsv

Make it scalable

The file share is already shared by every replica, so scaling out is two changes: keep each signed-in browser on one replica, then raise the replica count.

# 1. Keep each browser on the replica that holds its sign-in session
az containerapp ingress sticky-sessions set \
  --name $APP_NAME \
  --resource-group $RESOURCE_GROUP \
  --affinity sticky

# 2. Run 2 to 5 replicas, adding one for every 50 concurrent requests
az containerapp update \
  --name $APP_NAME \
  --resource-group $RESOURCE_GROUP \
  --min-replicas 2 \
  --max-replicas 5 \
  --scale-rule-name http-load \
  --scale-rule-type http \
  --scale-rule-http-concurrency 50
  • Session affinity needs single revision mode and HTTP ingress, which is what this guide sets up.
  • For sustained heavy traffic, use a Premium file share: every mocked call reads its mock file from the share, so share latency becomes response latency.
  • To give one replica more headroom instead: az containerapp update --cpu 1.0 --memory 2.0Gi.

Read how APIKlone behaves with more than one replica before you scale out.

Day to day

Upgradeaz containerapp update --name $APP_NAME --resource-group $RESOURCE_GROUP --image docker.io/apiklone/apiklone:<version>
Change a settingaz containerapp update ... --set-env-vars APP_BASE_URL=https://<that-address>
Back upaz storage share snapshot --name $FILE_SHARE --account-name $STORAGE_ACCOUNT --account-key $STORAGE_KEY, or enable Azure Backup on the share for a schedule.
Read the logsaz containerapp logs show --name $APP_NAME --resource-group $RESOURCE_GROUP --tail 100 --follow
Remove everythingaz group delete --name $RESOURCE_GROUP deletes the app and all its data.

If something goes wrong

What you seeLikely cause
ACTIVATION FAILED in the log, replica restartingThe activation key is missing, mistyped or expired. Set the secret again and restart the revision.
EACCES / permission denied on /app/dataThe mount options are missing, or the storage link is not ReadWrite.
Volume mount error at start-upThe storage link name does not match step 4, or the account key was rotated. Re-run az containerapp env storage set with the current key.
Configs disappear after a restartThe share is not mounted at /app/data.

APIKlone on Google Cloud Run

Runs the container on Cloud Run and stores its data in a Cloud Storage bucket, which APIKlone supports natively. You need the gcloud CLI; Cloud Shell works.

ComputeCloud Run
StorageCloud Storage bucket
SecretsSecret Manager
HTTPSIncluded
TimeAbout 10 minutes

Deploy

1

Sign in and set names

Bucket names are global, so the project ID is used as a prefix.

gcloud auth login

PROJECT_ID="your-project-id"
REGION="europe-west2"
SERVICE="apiklone"
BUCKET="${PROJECT_ID}-apiklone-data"
SERVICE_ACCOUNT="apiklone-run"
SA_EMAIL="${SERVICE_ACCOUNT}@${PROJECT_ID}.iam.gserviceaccount.com"

gcloud config set project $PROJECT_ID
gcloud services enable \
  run.googleapis.com \
  secretmanager.googleapis.com \
  storage.googleapis.com
2

Create the storage bucket

APIKlone talks to Cloud Storage directly, so there is no volume to mount. Keep the bucket private.

gcloud storage buckets create gs://$BUCKET \
  --location=$REGION \
  --uniform-bucket-level-access \
  --public-access-prevention
3

Create an identity for the service

The service runs as its own service account, with access to this one bucket only. No key file is created or downloaded.

gcloud iam service-accounts create $SERVICE_ACCOUNT \
  --display-name="APIKlone on Cloud Run"

gcloud storage buckets add-iam-policy-binding gs://$BUCKET \
  --member="serviceAccount:$SA_EMAIL" \
  --role="roles/storage.objectAdmin"
4

Store the activation key

printf '%s' "paste-your-activation-key-here" | \
  gcloud secrets create apiklone-activation-key \
    --replication-policy=automatic \
    --data-file=-

gcloud secrets add-iam-policy-binding apiklone-activation-key \
  --member="serviceAccount:$SA_EMAIL" \
  --role="roles/secretmanager.secretAccessor"
5

Deploy APIKlone

Setting GCS_BUCKET_NAME switches APIKlone to bucket storage. --no-cpu-throttling keeps the CPU available between requests, which the config reload and delayed async callbacks rely on.

gcloud run deploy $SERVICE \
  --image=docker.io/apiklone/apiklone:latest \
  --region=$REGION \
  --service-account=$SA_EMAIL \
  --port=3000 \
  --allow-unauthenticated \
  --set-env-vars=GCS_BUCKET_NAME=$BUCKET \
  --set-secrets=ACTIVATION_KEY=apiklone-activation-key:latest \
  --cpu=1 \
  --memory=1Gi \
  --no-cpu-throttling \
  --min-instances=1 \
  --max-instances=1

--allow-unauthenticated makes the address public, which your applications need in order to call the mocks. APIKlone's own sign-in protects the admin screens.

6

Open it and sign in

gcloud run services describe $SERVICE \
  --region=$REGION \
  --format='value(status.url)'

Open that address and sign in as admin / admin. Change that password straight away from the Team page.

7

Check the data is in the bucket

You should see users.properties at least.

gcloud storage ls gs://$BUCKET

Make it scalable

Bucket storage is shared by every instance, so scaling out is one command.

gcloud run services update $SERVICE \
  --region=$REGION \
  --session-affinity \
  --min-instances=2 \
  --max-instances=5 \
  --concurrency=80
  • Cloud Run's session affinity is best effort. If an instance is removed or reaches its concurrency limit, the next request goes to another instance and that user signs in again.
  • Every mocked call reads its mock from the bucket. Keep the bucket in the same region as the service to keep that fast.
  • For production, pull the image through an Artifact Registry remote repository instead of directly from Docker Hub; Google recommends it for availability.

Read how APIKlone behaves with more than one replica before you scale out.

Day to day

Upgradegcloud run services update $SERVICE --region=$REGION --image=docker.io/apiklone/apiklone:<version>
Change a settinggcloud run services update $SERVICE --region=$REGION --update-env-vars=APP_BASE_URL=https://<that-address>
Back upTurn on object versioning so overwritten or deleted files can be restored: gcloud storage buckets update gs://$BUCKET --versioning
Read the logsgcloud run services logs read $SERVICE --region=$REGION --limit=100
Read the audit trailOn Cloud Run the audit file lives on the instance's temporary disk, so read audit entries from Cloud Logging instead: gcloud logging read 'resource.type="cloud_run_revision" AND textPayload:"[AUDIT]"' --limit=50

If something goes wrong

What you seeLikely cause
Deploy fails: container did not listen on the portUsually the activation key. Check the logs for ACTIVATION FAILED; the server exits before it starts listening.
Deploy fails with a permission error on the secretThe service account lacks secretAccessor on the secret (step 4).
403 storage errors in the logThe service account lacks storage.objectAdmin on the bucket (step 3), or the bucket name is wrong.
Configs disappear after a new revisionGCS_BUCKET_NAME is not set, so data went to the instance's temporary disk.
Deploy refused: unauthenticated access not allowedAn organisation policy blocks public services. Ask your cloud admin for an exception or put a load balancer in front.

APIKlone on AWS ECS Fargate

Runs the container on ECS Fargate behind an Application Load Balancer, with an EFS file system mounted as its data folder. You need the AWS CLI v2 in a Bash shell; CloudShell works.

ComputeECS Fargate
StorageEFS file system
SecretsSecrets Manager
HTTPSYour ACM certificate
TimeAbout 30 minutes

Deploy

1

Set names and find the network

This guide uses your account's default VPC and two of its public subnets. Use your own VPC and subnet IDs if you have a network standard.

export AWS_DEFAULT_REGION="eu-west-2"
APP="apiklone"

ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
VPC_ID=$(aws ec2 describe-vpcs \
  --filters Name=isDefault,Values=true \
  --query "Vpcs[0].VpcId" --output text)
read SUBNET_1 SUBNET_2 <<< $(aws ec2 describe-subnets \
  --filters Name=vpc-id,Values=$VPC_ID Name=default-for-az,Values=true \
  --query "Subnets[0:2].SubnetId" --output text)
2

Create the firewall rules

Three security groups, each only reachable from the one before it: internet → load balancer → container → file system.

ALB_SG=$(aws ec2 create-security-group --group-name $APP-alb \
  --description "APIKlone load balancer" --vpc-id $VPC_ID \
  --query GroupId --output text)
TASK_SG=$(aws ec2 create-security-group --group-name $APP-task \
  --description "APIKlone containers" --vpc-id $VPC_ID \
  --query GroupId --output text)
EFS_SG=$(aws ec2 create-security-group --group-name $APP-efs \
  --description "APIKlone file system" --vpc-id $VPC_ID \
  --query GroupId --output text)

aws ec2 authorize-security-group-ingress --group-id $ALB_SG \
  --protocol tcp --port 80 --cidr 0.0.0.0/0
aws ec2 authorize-security-group-ingress --group-id $ALB_SG \
  --protocol tcp --port 443 --cidr 0.0.0.0/0
aws ec2 authorize-security-group-ingress --group-id $TASK_SG \
  --protocol tcp --port 3000 --source-group $ALB_SG
aws ec2 authorize-security-group-ingress --group-id $EFS_SG \
  --protocol tcp --port 2049 --source-group $TASK_SG
3

Create the storage

An encrypted EFS file system, reachable from both subnets. The access point makes the container's non-root user (ID 1000) the owner of its own folder.

EFS_ID=$(aws efs create-file-system \
  --encrypted \
  --throughput-mode elastic \
  --tags Key=Name,Value=$APP-data \
  --query FileSystemId --output text)

# Wait until this prints "available" before continuing
aws efs describe-file-systems --file-system-id $EFS_ID \
  --query "FileSystems[0].LifeCycleState" --output text

aws efs create-mount-target --file-system-id $EFS_ID \
  --subnet-id $SUBNET_1 --security-groups $EFS_SG
aws efs create-mount-target --file-system-id $EFS_ID \
  --subnet-id $SUBNET_2 --security-groups $EFS_SG

ACCESS_POINT_ID=$(aws efs create-access-point \
  --file-system-id $EFS_ID \
  --posix-user Uid=1000,Gid=1000 \
  --root-directory 'Path=/apiklone,CreationInfo={OwnerUid=1000,OwnerGid=1000,Permissions=750}' \
  --query AccessPointId --output text)
4

Store the activation key

The execution role lets ECS pull the image, write logs and read this one secret, nothing else.

SECRET_ARN=$(aws secretsmanager create-secret \
  --name $APP/activation-key \
  --secret-string "paste-your-activation-key-here" \
  --query ARN --output text)

aws iam create-role --role-name $APP-execution \
  --assume-role-policy-document '{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Principal":{"Service":"ecs-tasks.amazonaws.com"},"Action":"sts:AssumeRole"}]}'

aws iam attach-role-policy --role-name $APP-execution \
  --policy-arn arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy

aws iam put-role-policy --role-name $APP-execution \
  --policy-name read-activation-key \
  --policy-document "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Action\":\"secretsmanager:GetSecretValue\",\"Resource\":\"$SECRET_ARN\"}]}"
5

Define the container

Paste the block as it is; the shell fills in the variables.

aws logs create-log-group --log-group-name /ecs/$APP
aws ecs create-cluster --cluster-name $APP

cat > task.json <<EOF
{
  "family": "$APP",
  "networkMode": "awsvpc",
  "requiresCompatibilities": ["FARGATE"],
  "cpu": "512",
  "memory": "1024",
  "executionRoleArn": "arn:aws:iam::${ACCOUNT_ID}:role/${APP}-execution",
  "containerDefinitions": [
    {
      "name": "apiklone",
      "image": "docker.io/apiklone/apiklone:latest",
      "essential": true,
      "portMappings": [{ "containerPort": 3000, "protocol": "tcp" }],
      "environment": [{ "name": "PORT", "value": "3000" }],
      "secrets": [{ "name": "ACTIVATION_KEY", "valueFrom": "$SECRET_ARN" }],
      "mountPoints": [{ "sourceVolume": "apiklone-data", "containerPath": "/app/data" }],
      "logConfiguration": {
        "logDriver": "awslogs",
        "options": {
          "awslogs-group": "/ecs/$APP",
          "awslogs-region": "$AWS_DEFAULT_REGION",
          "awslogs-stream-prefix": "apiklone"
        }
      }
    }
  ],
  "volumes": [
    {
      "name": "apiklone-data",
      "efsVolumeConfiguration": {
        "fileSystemId": "$EFS_ID",
        "transitEncryption": "ENABLED",
        "authorizationConfig": { "accessPointId": "$ACCESS_POINT_ID" }
      }
    }
  ]
}
EOF

aws ecs register-task-definition --cli-input-json file://task.json
6

Create the load balancer and start the service

ALB_ARN=$(aws elbv2 create-load-balancer --name $APP \
  --subnets $SUBNET_1 $SUBNET_2 --security-groups $ALB_SG \
  --query "LoadBalancers[0].LoadBalancerArn" --output text)

TG_ARN=$(aws elbv2 create-target-group --name $APP \
  --protocol HTTP --port 3000 --vpc-id $VPC_ID \
  --target-type ip --health-check-path /health \
  --query "TargetGroups[0].TargetGroupArn" --output text)

aws elbv2 create-listener --load-balancer-arn $ALB_ARN \
  --protocol HTTP --port 80 \
  --default-actions Type=forward,TargetGroupArn=$TG_ARN

aws ecs create-service \
  --cluster $APP \
  --service-name $APP \
  --task-definition $APP \
  --desired-count 1 \
  --launch-type FARGATE \
  --network-configuration "awsvpcConfiguration={subnets=[$SUBNET_1,$SUBNET_2],securityGroups=[$TASK_SG],assignPublicIp=ENABLED}" \
  --load-balancers "targetGroupArn=$TG_ARN,containerName=apiklone,containerPort=3000" \
  --health-check-grace-period-seconds 60

assignPublicIp=ENABLED lets the container pull its image from Docker Hub from a public subnet. The container itself still only accepts traffic from the load balancer.

7

Add HTTPS, then sign in

Get the load balancer's address:

aws elbv2 describe-load-balancers --load-balancer-arns $ALB_ARN \
  --query "LoadBalancers[0].DNSName" --output text

The listener from step 6 is plain HTTP. Use it only to check the service is up (/health). Before anyone signs in, point a domain at the load balancer, request a certificate for it in AWS Certificate Manager, and add an HTTPS listener.

CERT_ARN="arn-of-your-acm-certificate"

aws elbv2 create-listener --load-balancer-arn $ALB_ARN \
  --protocol HTTPS --port 443 \
  --certificates CertificateArn=$CERT_ARN \
  --default-actions Type=forward,TargetGroupArn=$TG_ARN

Then open https://<your-domain> and sign in as admin / admin. Change that password straight away from the Team page.

Make it scalable

The EFS file system is already shared by every task, so scaling out is two changes: keep each signed-in browser on one task, then let the service add tasks under load.

# 1. Keep each browser on the task that holds its sign-in session
aws elbv2 modify-target-group-attributes --target-group-arn $TG_ARN \
  --attributes \
    Key=stickiness.enabled,Value=true \
    Key=stickiness.type,Value=lb_cookie \
    Key=stickiness.lb_cookie.duration_seconds,Value=86400

# 2. Run 2 to 5 tasks, adding tasks when average CPU passes 60%
aws application-autoscaling register-scalable-target \
  --service-namespace ecs \
  --resource-id service/$APP/$APP \
  --scalable-dimension ecs:service:DesiredCount \
  --min-capacity 2 --max-capacity 5

aws application-autoscaling put-scaling-policy \
  --service-namespace ecs \
  --resource-id service/$APP/$APP \
  --scalable-dimension ecs:service:DesiredCount \
  --policy-name cpu-target \
  --policy-type TargetTrackingScaling \
  --target-tracking-scaling-policy-configuration '{"TargetValue":60.0,"PredefinedMetricSpecification":{"PredefinedMetricType":"ECSServiceAverageCPUUtilization"}}'
  • Tasks are spread across the two subnets, so the service survives the loss of one availability zone.
  • Every mocked call reads its mock file from EFS. The elastic throughput mode set in step 3 grows with demand, so no tuning is needed.
  • To give each task more headroom instead, raise cpu and memory in task.json and register it again.

Read how APIKlone behaves with more than one replica before you scale out.

Day to day

UpgradeChange the image tag in task.json, run aws ecs register-task-definition --cli-input-json file://task.json, then aws ecs update-service --cluster $APP --service $APP --task-definition $APP.
Change a settingAdd it to environment in task.json and roll it out the same way as an upgrade.
Back upTurn on daily automatic backups: aws efs put-backup-policy --file-system-id $EFS_ID --backup-policy Status=ENABLED
Read the logsaws logs tail /ecs/$APP --follow

If something goes wrong

What you seeLikely cause
Task stops with ResourceInitializationError mentioning EFSThe mount targets are not ready yet, or the file-system security group does not allow port 2049 from the container group (step 2).
Task stops: unable to retrieve secretThe execution role's policy does not cover the secret's ARN (step 4).
Task starts, then stops; log shows ACTIVATION FAILEDThe activation key is mistyped or expired.
Load balancer returns 503; targets unhealthyThe container group does not allow port 3000 from the load-balancer group, or the health check path is not /health.
Task cannot pull the imageThe subnets have no route to the internet. Use public subnets with a public IP, or private subnets with a NAT gateway.

How scaling works

One replica is enough for most teams. Before you run more than one, it helps to know what is shared between replicas and what is not.

Stubbed and passthrough traffic

Calls to your stubbed and passthrough endpoints carry no session, so any replica can answer them. This is the traffic that scales.

Data

Configs, mock files, users and API keys live in the shared storage, so every replica sees the same data. A new mock file is served by all replicas immediately.

Config changes

The replica you make a change on applies it at once. The others re-read their configs every 30 seconds, so allow up to that long for a new or changed endpoint to be live everywhere.

Sign-in sessions

Sessions are held in each replica's memory. Turn on session affinity so a signed-in browser stays on one replica. If that replica is removed or restarted, its users sign in again.

Async callbacks

Delayed callbacks are queued in the shared storage and each one is claimed by a single replica, so a callback is not sent twice.

Logs

The Server Logs page shows the replica you happen to be on. With several replicas, read logs in your cloud's log service, which collects all of them.

Guidelines

  • Keep at least one replica running. Scaling to zero makes the first call slow and stops delayed async callbacks from being sent.
  • Keep storage close. Every mocked call reads its mock from storage, so put the storage in the same region as the containers.
  • Lower the reload interval if 30 seconds is too long. Set POLL_INTERVAL_MS, for example to 10000.
  • Have one person change a config at a time. All configs are saved together; if two administrators save at the same moment on different replicas, the later save wins.
  • API keys work on every replica. Automation that uses the x-api-key header needs no session and no affinity.

Settings reference

APIKlone is configured with environment variables. Only the activation key is required.

VariableDefaultPurpose
ACTIVATION_KEYnone (required)Your licence key. The container exits at start-up without a valid one.
PORT3000Port the server listens on.
GCS_BUCKET_NAMEnoneStore data in this Cloud Storage bucket instead of the /app/data folder.
APP_BASE_URLhttp://localhost:3000Public address of the instance. Required for single sign-on, so the identity provider can send users back.
MANAGEMENT_API_KEYnoneA fixed API key for automation, accepted in the x-api-key header. Store it as a secret.
POLL_INTERVAL_MS30000How often each replica re-reads its configs from storage.
MOCK_MAX_DELAY_MS60000Longest response delay a mock file may set.
ASYNC_CALLBACK_BLOCK_PRIVATEfalseSet to true to stop async callbacks being sent to private network addresses.

For single sign-on, register $APP_BASE_URL/auth/saml/callback (SAML, HTTP-POST binding) or $APP_BASE_URL/auth/oidc/callback (OIDC) with your identity provider. The Team → Configure SSO page shows the exact URL.

Key management best practices

An APIKlone deployment holds a handful of secrets. Knowing what each one unlocks tells you how carefully to guard it.

The keys in a deployment

KeyWhat it unlocksWhere it lives
Activation keyYour licence. Anyone holding it can run APIKlone under your entitlement.The ACTIVATION_KEY setting.
Management API keyFull administrative access to the REST API.The optional MANAGEMENT_API_KEY setting.
API keysThe same API access, one key per pipeline or person.Created on the API Keys page; stored in the data store.
Storage credentialsEverything in the data store.Azure: the storage account key. Google Cloud and AWS: the service's identity, with no key to hold.
SSO client secretLets a service pose as APIKlone to your identity provider.Saved from the SSO settings page into the data store.
Admin passwordThe whole admin interface.Hashed, in the data store.

What to do

1

Keep keys in your cloud's secret store

Never put a key in a container image, a source repository, a deploy script or a chat message. Each guide above stores the activation key as a secret and passes it to the container by reference. Do the same for MANAGEMENT_API_KEY. On Azure, a Container Apps secret can also point at a Key Vault secret if your organisation centralises keys there.

2

Prefer identities to keys

On Google Cloud the service account attached to Cloud Run reaches the bucket; on AWS the task's roles reach the secret and the file system. Do not create service-account key files or IAM access keys for APIKlone: a key that does not exist cannot leak.

3

Grant the least access that works

Give the service access to its own bucket or share and its own secret, not to the whole project or account. The guides scope the storage role to one bucket and the secret permission to one secret for this reason.

4

Give each pipeline and person their own API key

Create labelled keys on the API Keys page instead of sharing the management key. A key is shown once, so store it in the pipeline's own secret store straight away. The audit trail records each action against the key's label, and one key can be revoked without breaking the others.

5

Rotate on a schedule, and whenever someone leaves

Revoke and replace API keys regularly. Rotate the Azure storage account key and then link the share again with the new key. Renew the activation key before it expires: the container will not start with an expired key. The commands for replacing the activation key are below.

6

Replace the default admin password first

A new instance starts with the well-known account admin / admin on a public address. Change it before anything else, use single sign-on where your licence includes it, and remove users who leave.

7

Treat the data store as sensitive

It holds password hashes, API keys, the SSO client secret and recorded traffic. Keep the bucket or share private, limit who can browse it, and leave encryption at rest switched on (it is by default on all three clouds).

8

Remember that recordings capture credentials

Record mode saves the requests it sees, including headers such as Authorization. Recordings and config export files can therefore contain live tokens for your real backends. Record against test systems where you can, and never commit an export file to a public repository.

9

Review the audit trail

Sign-ins, failed sign-ins, key generation and revocation, user changes and config changes are all recorded with who did them. Check Server Logs → Audit trail periodically, or alert on [AUDIT] lines in your cloud's log service.

Replacing the activation key

Store the new key, then restart so the container reads it.

CloudCommands
Azureaz containerapp secret set --name $APP_NAME --resource-group $RESOURCE_GROUP --secrets activation-key="<new-key>", then restart the active revision with az containerapp revision restart.
Google Cloudprintf '%s' "<new-key>" | gcloud secrets versions add apiklone-activation-key --data-file=-, then gcloud run services update $SERVICE --region=$REGION --update-secrets=ACTIVATION_KEY=apiklone-activation-key:latest.
AWSaws secretsmanager put-secret-value --secret-id $APP/activation-key --secret-string "<new-key>", then aws ecs update-service --cluster $APP --service $APP --force-new-deployment.

Self-Hosted Deployment (Docker / K8s)

For organizations requiring strict data-residency, APIKlone can be deployed inside your own perimeter using Docker. The deployment process is simple and entirely stateless (using a mounted data volume for your mock schemas).

Standard Docker Flow

Pull the latest image from our secure registry and start the container by injecting your Activation Key.

docker pull apiklone/apiklone:latest

docker run -d \
  --name apiklone-server \
  -e ACTIVATION_KEY="eyJhbGciOiJ..." \
  -p 3000:3000 \
  -v $(pwd)/data:/app/data \
  apiklone/apiklone:latest

Always mount a persistent volume (e.g., /app/data) so that your recorded endpoints and uploaded OpenAPI specs survive container restarts!

Activation Keys

For Self-Hosted instances, you must authenticate your server instance using a cryptographic Activation Key. You can generate and view your active keys by clicking "Profile -> My Key" in the top navigation.

Activation keys contain your configs limit data. The server reads this token offline on boot to validate your license. Trials default to a 30-day expiry window with virtually infinite configurability.

Traffic Recording

APIKlone can serve as a man-in-the-middle to automatically scrape realistic payload schemas without manual effort.

  • 1. Create a Config: Set the Target Server to your real backend URL.
  • 2. Enable Recording: Toggle the "Recording Mode" switch on the dashboard.
  • 3. Send Traffic: Point your frontend to the APIKlone proxy address. APIKlone transparently relays the request to the upstream server, intercepts the HTTP 200 JSON response, and saves it.
  • 4. Switch to Mocking: Turn "Recording Mode" off. Future requests matching that URL path will immediately be served locally without hitting the real backend.

Mocks from an OpenAPI spec

Upload a Swagger 2 or OpenAPI 3 file, as JSON or YAML, on the Mock From Spec page or through the REST API. Every operation with a JSON success response becomes a live endpoint with a sample response built from its schema, replying with the status code the spec documents (for example 201 for a create).

Real calls are matched back to the spec's path templates, so a call to /products/42 finds the mock for /products/{id}, and query strings are ignored for matching. The spec stays attached to the config, which is what makes contract validation possible.

Contract validation

Turn on Strict OAS Validation for a config created from a spec, either when you upload it or later from the View Configs page. APIKlone then enforces the spec on every call:

  • Requests are enforced, in every mode. A call that breaks the spec, such as a missing required field, a wrong type or a badly formatted ID, gets a 400 naming the field at fault and never reaches the mock or the backend.
  • Responses are labelled, in stubbed-response mode. Each mock response is checked and marked in the X-APIKlone-OAS-Validation header as Passed or Failed. Responses are never blocked, so a stale mock does not stop a test.
  • Every failure is logged in the server log with the reason.
HTTP/1.1 400 Bad Request

{
  "error": "OAS Schema Validation Failed",
  "details": [{
    "path": "qty",
    "errorCode": "required.openapi.requestValidation",
    "message": "must have required property 'qty'",
    "location": "body"
  }]
}

Validation follows the spec exactly. A field missing from required is optional, and extra fields are allowed unless the schema sets additionalProperties: false.

Async callbacks

Some APIs reply at once (often 202 Accepted) and send the real result to the consumer later. Turn on Async API when editing an endpoint, and in stubbed-response mode APIKlone serves the acknowledgement and then sends the callback after the delay you set.

  • Where to send it: a URL read from each request's body, a header or a query parameter, or a fixed URL.
  • What to send: method, headers and a payload filled from the original request, for example {{request.body.orderId}}, {{uuid}} or {{now}}.
  • When: up to 10 callbacks per endpoint, each with its own delay of up to 24 hours.
  • Reliably: callbacks are queued in storage, survive restarts, are retried if the consumer is down, and are sent once even with several replicas.

The Send test callback button, or the matching API call, sends it immediately so you can check the receiving side. In record mode only the acknowledgement is recorded; the real backend sends its own callback.

Dynamic responses and delays

In the mock editor, tick Dynamic response to fill placeholders in a mock's headers and body from each request, and set Response delay to hold it back by up to 60 seconds.

{
  "statusCode": 201,
  "delayMs": 300,
  "templating": true,
  "headers": { "Content-Type": "application/json" },
  "body": {
    "orderId": "{{uuid}}",
    "sku": "{{request.body.sku}}",
    "qty": "{{request.body.qty}}",
    "createdAt": "{{now}}"
  }
}

Available placeholders: {{request.body.x}}, {{request.query.x}}, {{request.headers.x}}, {{request.params.x}} (path parameters of spec-imported endpoints), {{request.pathSegments.N}}, {{request.path}}, {{request.method}}, {{uuid}}, {{now}} and {{timestamp}}. A value that is only a placeholder keeps its type, so "qty": "{{request.body.qty}}" comes back as a number.

Polymorphic Responses

To recreate complex business logic, you can configure Polymorphic mapping. This allows you to evaluate specific query parameters, headers, or JSON body fields, and return entirely different HTTP status codes or payloads.

For example, if you send {"amount": 5000}, return an HTTP 200. If you send {"amount": -50}, return an HTTP 400 Validation Error.

Semantic Caching

Drastically reduce costs when interacting with external LLM APIs (like OpenAI or Anthropic). APIKlone can calculate vector embeddings of inbound prompts and intelligently cache semantically similar requests.

By enabling Semantic Cache in your active configuration, identical or closely related prompts will be served from the local cache rather than incurring round-trip latency and token costs from the upstream API provider.

AI Guard Rails

Ensure safe and reliable API behavior by enforcing our AI-powered proxy proxy layer. When enabled, APIKlone actively scans inbound payload payloads against known malicious patterns (e.g., prompt injection, PII exfiltration).

Administrators can configure sensitivity thresholds directly from the dashboard, instantly blocking non-compliant traffic before it reaches your backend systems.

Managing Admin Users

APIKlone comes with an integrated login portal for your engineering team to collaborate on creating mocks.

For Self-Hosted, user profile data and passwords are mathematically secured using PBKDF2 hashing algorithms inside your local DB. To add a user, administrators can invite them directly via the "Users" panel natively inside the dashboard application.

Headless REST API (CI/CD integration)

Everything in the dashboard can be driven from a pipeline. Create an API key on the API Keys page (one per pipeline, so each shows by name in the audit trail) and send it in the x-api-key header. Your instance documents every endpoint at API Docs in the dashboard, with a Swagger UI and the OpenAPI file to download.

A typical pipeline imports the spec with validation on, points the tests at it, and removes it afterwards:

# 1. Create mocks from the spec, with contract validation on
curl -X POST https://<your-apiklone>/api/mock-from-spec \
  -H "x-api-key: $APIKLONE_KEY" \
  -F "specFile=@openapi.yaml" \
  -F "basePath=/orders" \
  -F "validateOAS=true"
# -> {"success":true,"groupId":"1791617154089_group","configName":"OrdersAPI",...}

# 2. Run your tests against https://<your-apiklone>/orders/...

# 3. Remove the config when the run is over
curl -X DELETE https://<your-apiklone>/api/configs/1791617154089_group \
  -H "x-api-key: $APIKLONE_KEY"
TaskEndpoint
List configsGET /api/configs
Create an endpoint, with an optional async callbackPOST /api/configs
Switch record, passthrough or stubbed responsePUT /config/{id}/mode
Turn contract validation on or offPUT /api/configs/group/{groupId}/validate
Add or replace a mock, with delay and placeholdersPOST /config/{id}/mocks, PUT /mocks/{config}/{file}
Send a test callbackPOST /config/{id}/async/test
Export, import, enable or disable a configGET …/{groupId}/export, POST /api/configs/import, PUT …/{groupId}/enabled