floci-io/floci · error · AwsException

ApiKeyLimitExceededException

ApiKeyLimitExceededException

Error message

The API key exceeded a limit.

What it means

Thrown by createApiKey when the API already has 2 or more API keys (counted via apiKeyStore scan of keys prefixed apiId + '::'). This emulates AWS AppSync's per-API API-key quota (ApiKeyLimitExceededException).

Source

Thrown at src/main/java/io/github/hectorvent/floci/services/appsync/AppSyncService.java:576

        if (request.containsKey("description")) existing.setDescription((String) request.get("description"));
        if (request.containsKey("format")) existing.setFormat(parseEnum(TypeFormat.class, request.get("format")));
        typeStore.put(apiKey(apiId, typeName), existing);
        return existing;
    }

    public void deleteType(String apiId, String typeName) {
        assertSchemaNotBusy(apiId);
        getType(apiId, typeName);
        typeStore.delete(apiKey(apiId, typeName));
    }

    // ──────────────────────────── API Keys ────────────────────────────

    public ApiKey createApiKey(String apiId, Map<String, Object> request) {
        getGraphqlApi(apiId);
        long existingCount = apiKeyStore.scan(k -> k.startsWith(apiId + "::")).size();
        if (existingCount >= 2) {
            throw new AwsException("ApiKeyLimitExceededException",
                    "The API key exceeded a limit.", 400);
        }
        ApiKey key = new ApiKey();
        key.setId(generateShortId());
        key.setApiId(apiId);
        key.setDescription((String) request.get("description"));
        Object expiresValue = request.get("expires");
        if (expiresValue instanceof Long l) {
            key.setExpires(l);
        } else if (expiresValue instanceof Number n) {
            key.setExpires(n.longValue());
        } else if (expiresValue instanceof String s) {
            try {
                key.setExpires(Long.parseLong(s));
            } catch (NumberFormatException e) {
                try {
                    key.setExpires(java.time.Instant.parse(s).getEpochSecond());
                } catch (java.time.format.DateTimeParseException ex) {

View on GitHub (pinned to 62ff490619)

Solutions

  1. Delete an existing API key first (deleteApiKey) so the count drops below the limit
  2. Reuse an existing key via updateApiKey (e.g. extend 'expires') instead of creating a new one
  3. If keys leaked from old tests, list and clean them up with listApiKeys + deleteApiKey

Example fix

// before
appSync.createApiKey(apiId, Map.of("description", "rotated")); // 3rd key -> limit

// after
var keys = appSync.listApiKeys(apiId, null, null).getItems();
appSync.deleteApiKey(apiId, keys.get(0).getId()); // drop oldest
appSync.createApiKey(apiId, Map.of("description", "rotated"));
Defensive patterns

Strategy: validation

Validate before calling

var keys = appSync.listApiKeys(apiId, null, null).getItems();
if (keys.size() >= 2) {
    appSync.deleteApiKey(apiId, keys.get(0).getId()); // drop oldest
}
appSync.createApiKey(apiId, request);

Try / catch

try {
    appSync.createApiKey(apiId, request);
} catch (AwsException e) {
    if ("ApiKeyLimitExceededException".equals(e.getCode())) {
        appSync.deleteApiKey(apiId, oldestKeyId);
        appSync.createApiKey(apiId, request);
    } else throw e;
}

Prevention

When it happens

Trigger: Calling createApiKey on an apiId that already has two stored keys; e.g. creating a third key for rotation without deleting an old one.

Common situations: Key rotation scripts that create-then-delete instead of delete-then-create; tests that create fresh keys per iteration against the same API; long-lived emulator state accumulating keys across runs.

Related errors


AI-assisted analysis of floci-io/floci@62ff490619 (2026-08-14). Data as JSON: /api/errors/795985a56de4029b. Report an issue: GitHub.