Help improve docs quality by sharing anonymous interaction telemetry (no PII or token data).

Spring Quickstart

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

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

Prerequisites

  • Java 21+

  • Maven

  • A scoped non-production API key

Environment setup

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

cd examples/docs-quickstarts/spring && mvn dependency:go-offline

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.

private static Customer createCustomer(Config config, String idempotencyKey) throws Exception {
        String payload = """
            {"name":%s,"email":%s}
            """.formatted(
                jsonString("Docs quickstart " + config.runId()),
                jsonString("docs-quickstart+" + config.runId() + "@example.com")
            ).trim();
        ApiResponse response = requestJson(
            config,
            "POST",
            "/customers",
            payload,
            idempotencyKey,
            201
        );
        return new Customer(stringField(response.body(), "id"));
    }

Expected HTTP status: 201.

2. Select the isolated published plan

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

private static Plan selectPlan(Config config) throws Exception {
        ApiResponse response = requestJson(config, "GET", "/plans", null, null, 200);
        Matcher matcher = Pattern.compile(
            "\\\"id\\\"\\s*:\\s*\\\"((?:\\\\.|[^\\\"])*)\\\"",
            Pattern.DOTALL
        ).matcher(response.body());
        while (matcher.find()) {
            String candidateId = matcher.group(1);
            if (candidateId.equals(config.planId())) {
                return new Plan(candidateId);
            }
        }
        throw new IllegalStateException(
            "Isolated plan " + config.planId() + " is not published in this workspace"
        );
    }

Expected HTTP status: 200.

3. Attach the plan

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

private static Subscription attachPlan(
        Config config,
        String customerId,
        String planId,
        String idempotencyKey
    ) throws Exception {
        String payload = """
            {"customerId":%s,"planId":%s,"status":"active"}
            """.formatted(jsonString(customerId), jsonString(planId)).trim();
        ApiResponse response = requestJson(
            config,
            "POST",
            "/subscriptions",
            payload,
            idempotencyKey,
            201
        );
        return new Subscription(stringField(response.body(), "id"));
    }

Expected HTTP status: 201.

4. Check and enforce an entitlement

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

private static EntitlementDecision requireEntitlement(
        Config config,
        String customerId
    ) throws Exception {
        String path = "/entitlements/" + pathSegment(customerId)
            + "/" + pathSegment(config.featureKey());
        ApiResponse response = requestJson(config, "GET", path, null, null, 200);
        EntitlementDecision decision = new EntitlementDecision(
            booleanField(response.body(), "allowed"),
            stringField(response.body(), "featureKey"),
            stringField(response.body(), "reason"),
            stringField(response.body(), "reasonCode")
        );
        if (!decision.allowed()) {
            throw new IllegalStateException(
                "Entitlement denied (" + decision.reasonCode() + "): " + decision.reason()
            );
        }
        return decision;
    }

Expected HTTP status: 200.

5. Submit usage

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

private static String submitUsage(
        Config config,
        String customerId,
        String idempotencyKey
    ) throws Exception {
        String payload = """
            {"customerId":%s,"meterId":%s,"value":1}
            """.formatted(jsonString(customerId), jsonString(config.meterId())).trim();
        return requestJson(
            config,
            "POST",
            "/usage/events",
            payload,
            idempotencyKey,
            201
        ).body();
    }

Expected HTTP status: 201.

6. Verify the observed usage

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

private static String validateUsage(Config config, String customerId) throws Exception {
        String path = "/usage/" + pathSegment(customerId)
            + "/" + pathSegment(config.meterId());
        return requestJson(config, "GET", path, null, null, 200).body();
    }

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

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

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

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

Deploy + smoke test

Platform: Docker Compose.

  1. Build the Spring Boot jar and inject the seven quickstart environment variables.

  2. Run the service image before invoking the jar's smoke entry point.

(cd examples/docs-quickstarts/spring && mvn -q package && java -jar target/monetizekit-docs-quickstart-1.0.0.jar --smoke)

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

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

monetizekit auth service-token create docs-quickstart --scopes customers:view --ttl 30d
monetizekit customers list

Was this page helpful?