# Go Quickstart

Complete the raw-HTTP MonetizeKit activation journey in a Go service.

<!-- docs-source -->
Source: https://learning.monetizekit.app/docs/quickstarts/go
<!-- /docs-source -->

Go uses the Raw HTTP path in Go. Complete every step with an isolated non-production workspace before deploying.

## Prerequisites

- Go 1.22+
- A scoped non-production API key

## Environment setup

```bash
MONETIZEKIT_BASE_URL=https://app.monetizekit.app/api/v1
MONETIZEKIT_API_KEY=${MONETIZEKIT_API_KEY}
MONETIZEKIT_PLAN_ID=${MONETIZEKIT_PLAN_ID}
MONETIZEKIT_FEATURE_KEY=${MONETIZEKIT_FEATURE_KEY}
MONETIZEKIT_DENIED_FEATURE_KEY=${MONETIZEKIT_DENIED_FEATURE_KEY}
MONETIZEKIT_METER_ID=${MONETIZEKIT_METER_ID}
MONETIZEKIT_RUN_ID=replace-with-a-unique-run-id
```

## Install commands

```bash
cd examples/docs-quickstarts/go && go mod download
```

## Happy path

### 1. Create a customer

Create a unique test customer. Raw HTTP variants send a deterministic idempotency key; the current Node SDK does not expose one for customer creation.

```go
func createCustomer(cfg config, idempotencyKey string) (customer, error) {
	var result customer
	err := requestJSON(
		cfg,
		http.MethodPost,
		"/customers",
		map[string]any{
			"name":  "Docs quickstart " + cfg.RunID,
			"email": "docs-quickstart+" + cfg.RunID + "@example.com",
		},
		idempotencyKey,
		http.StatusCreated,
		&result,
	)
	return result, err
}
```

Expected HTTP status: 201.

### 2. Select the isolated published plan

List published plans and select the exact MONETIZEKIT_PLAN_ID provisioned for this run.

```go
func selectPlan(cfg config) (plan, error) {
	var plans planList
	if err := requestJSON(cfg, http.MethodGet, "/plans", nil, "", http.StatusOK, &plans); err != nil {
		return plan{}, err
	}
	for _, candidate := range plans.Data {
		if candidate.ID == cfg.PlanID {
			return candidate, nil
		}
	}
	return plan{}, fmt.Errorf("isolated plan %s is not published in this workspace", cfg.PlanID)
}
```

Expected HTTP status: 200.

### 3. Attach the plan

Create the customer's subscription. Raw HTTP variants reuse a deterministic idempotency key.

```go
func attachPlan(
	cfg config,
	customerID string,
	planID string,
	idempotencyKey string,
) (subscription, error) {
	var result subscription
	err := requestJSON(
		cfg,
		http.MethodPost,
		"/subscriptions",
		map[string]any{"customerId": customerID, "planId": planID, "status": "active"},
		idempotencyKey,
		http.StatusCreated,
		&result,
	)
	return result, err
}
```

Expected HTTP status: 201.

### 4. Check and enforce an entitlement

Request the feature decision and stop protected work unless `allowed` is true.

```go
func requireEntitlement(cfg config, customerID string) (entitlementDecision, error) {
	var decision entitlementDecision
	path := fmt.Sprintf(
		"/entitlements/%s/%s",
		url.PathEscape(customerID),
		url.PathEscape(cfg.FeatureKey),
	)
	if err := requestJSON(cfg, http.MethodGet, path, nil, "", http.StatusOK, &decision); err != nil {
		return entitlementDecision{}, err
	}
	if !decision.Allowed {
		return entitlementDecision{}, fmt.Errorf(
			"entitlement denied (%s): %s",
			decision.ReasonCode,
			decision.Reason,
		)
	}
	return decision, nil
}
```

Expected HTTP status: 200.

### 5. Submit usage

Record one metered event with a deterministic idempotency key that remains stable across retries.

```go
func submitUsage(cfg config, customerID string, idempotencyKey string) (map[string]any, error) {
	result := map[string]any{}
	err := requestJSON(
		cfg,
		http.MethodPost,
		"/usage/events",
		map[string]any{"customerId": customerID, "meterId": cfg.MeterID, "value": 1},
		idempotencyKey,
		http.StatusCreated,
		&result,
	)
	return result, err
}
```

Expected HTTP status: 201.

### 6. Verify the observed usage

Read the meter after submission and include the observed result in the smoke output.

```go
func validateUsage(cfg config, customerID string) (map[string]any, error) {
	result := map[string]any{}
	path := fmt.Sprintf("/usage/%s/%s", url.PathEscape(customerID), url.PathEscape(cfg.MeterID))
	err := requestJSON(cfg, http.MethodGet, path, nil, "", http.StatusOK, &result)
	return result, err
}
```

Expected HTTP status: 200.

## Troubleshooting

### 401 missing_api_key / invalid_api_key

Confirm the API key is present, active, and uses the non-production prefix for the environment you intend to test.

[Read the full guide](/docs/troubleshooting/auth-issues)

### 403 missing scope

Issue a key with customers:create, plans:read, subscriptions:write, entitlements:read, usage:write, usage:read, and cleanup permissions.

[Read the full guide](/docs/troubleshooting/auth-issues)

### Entitlement denied

Confirm MONETIZEKIT_PLAN_ID is the isolated plan that grants MONETIZEKIT_FEATURE_KEY and does not grant MONETIZEKIT_DENIED_FEATURE_KEY.

[Read the full guide](/docs/guides/entitlement-patterns)

### A retry records duplicate usage

Keep MONETIZEKIT_RUN_ID stable for retries of the same logical smoke run so the generated Idempotency-Key is reused.

[Read the full guide](/docs/troubleshooting/metering-issues)

## Deploy + smoke test

Platform: Docker Compose.

1. Build the Go binary and inject the seven quickstart environment variables.
2. Run the service image before invoking the binary's smoke entry point.

```bash
(cd examples/docs-quickstarts/go && go run . --smoke)
```

Expected: One JSON object with outcome "passed", allowed and denied entitlement decisions, and observed usage data, followed by successful subscription/customer cleanup.

## CLI links

Use the CLI to authenticate and inspect the same workspace after the smoke test.

```bash
monetizekit customers list
```

```bash
monetizekit workspace list
```