Skip to content

HTTP API

You do not have to use a Guapocado SDK. The SDKs are small wrappers over the HTTP API.

Use the API directly when:

  • your service is not written in TypeScript
  • you have multiple microservices
  • you want to call Guapocado from a queue, worker, or internal platform
  • you want to generate a client from OpenAPI
  • you prefer explicit HTTP calls over SDK abstractions

End-user integrations call:

https://api.guapocado.dev

Sandbox versus production is selected by API key, not by changing the host.

Send your API key in the x-guapocado-key header:

Terminal window
curl https://api.guapocado.dev/v1/plans \
-H "x-guapocado-key: sk_guap_test_..."

Most write endpoints require a server key:

sk_guap_test_...
sk_guap_live_...

Browser-safe read endpoints can use a client key:

ck_guap_test_...
ck_guap_live_...

Never expose a server key in browser code, mobile apps, or public repositories.

For JSON requests, send:

content-type: application/json

All examples below use JSON.

Most runtime calls need a customerId.

That ID is your app’s stable billing identity:

  • user ID for individual products
  • organization ID for B2B SaaS
  • workspace ID for workspace billing
  • team ID for team billing

Keep it consistent. If checkout uses org_123, entitlement checks and usage consumption should also use org_123.

Check a feature:

Terminal window
curl "https://api.guapocado.dev/v1/entitlements/advanced-analytics/has?customerId=org_123" \
-H "x-guapocado-key: ck_guap_test_..."

Read usage:

Terminal window
curl "https://api.guapocado.dev/v1/usage/api-calls/balance?customerId=org_123" \
-H "x-guapocado-key: ck_guap_test_..."

Consume usage:

Terminal window
curl -X POST "https://api.guapocado.dev/v1/usage/api-calls/consume" \
-H "x-guapocado-key: sk_guap_test_..." \
-H "content-type: application/json" \
-d '{"customerId":"org_123","amount":1}'

Create checkout:

Terminal window
curl -X POST "https://api.guapocado.dev/v1/checkout" \
-H "x-guapocado-key: sk_guap_test_..." \
-H "content-type: application/json" \
-d '{
"customerId": "org_123",
"productKey": "pro",
"successUrl": "https://app.example.com/billing/success",
"cancelUrl": "https://app.example.com/billing"
}'

Each environment can expose an OpenAPI document generated from the pushed billing config:

Terminal window
curl "https://api.guapocado.dev/v1/docs/openapi.json" \
-H "x-guapocado-key: sk_guap_test_..."

Use this to generate clients for other languages or internal microservices.

The billing config JSON Schema is public:

Terminal window
curl "https://api.guapocado.dev/v1/schema/billing"

This is useful for editor tooling, validation, and agents that need to understand billing.config.ts shape.

Check whether a customer has a feature entitlement.

GET /v1/entitlements/{key}/has?customerId={customerId}

Example:

Terminal window
curl "https://api.guapocado.dev/v1/entitlements/sso/has?customerId=org_123" \
-H "x-guapocado-key: ck_guap_test_..."

Response:

true

Use a client key or server key.

Read a numeric allowance.

GET /v1/entitlements/{key}/limit?customerId={customerId}

Example:

Terminal window
curl "https://api.guapocado.dev/v1/entitlements/seats/limit?customerId=org_123" \
-H "x-guapocado-key: ck_guap_test_..."

Response:

{
"limit": 10,
"included": 10,
"purchased": 0,
"expansionAllowed": true,
"autoExpansionEnabled": false
}

Use the returned limit in your app:

if (activeSeats >= limit.limit) {
throw new Error("Seat limit reached");
}

Configure purchased limit expansion:

POST /v1/entitlements/{key}/limit/settings

Server key required.

Terminal window
curl -X POST "https://api.guapocado.dev/v1/entitlements/seats/limit/settings" \
-H "x-guapocado-key: sk_guap_test_..." \
-H "content-type: application/json" \
-d '{
"customerId": "org_123",
"purchased": 3,
"autoExpansionEnabled": false
}'

Read meter balance:

GET /v1/usage/{key}/balance?customerId={customerId}

Example:

Terminal window
curl "https://api.guapocado.dev/v1/usage/ai-credits/balance?customerId=org_123" \
-H "x-guapocado-key: ck_guap_test_..."

Response:

{
"balance": 31200,
"included": 40000,
"consumed": 8800,
"overage": 0,
"overageAllowed": true,
"overageEnabled": false,
"resets": "2026-07-01T00:00:00.000Z"
}

Consume usage:

POST /v1/usage/{key}/consume

Server key required.

Terminal window
curl -X POST "https://api.guapocado.dev/v1/usage/ai-credits/consume" \
-H "x-guapocado-key: sk_guap_test_..." \
-H "content-type: application/json" \
-d '{"customerId":"org_123","amount":250,"idempotencyKey":"req_abc"}'

idempotencyKey is optional. When provided, a retried request with the same key is applied at most once and returns the current balance.

If the customer does not have enough balance and overage is not enabled, the API returns 429 with the current balance fields.

Refund usage:

POST /v1/usage/{key}/refund

Server key required.

Terminal window
curl -X POST "https://api.guapocado.dev/v1/usage/ai-credits/refund" \
-H "x-guapocado-key: sk_guap_test_..." \
-H "content-type: application/json" \
-d '{"customerId":"org_123","amount":250,"idempotencyKey":"refund-req-abc"}'

The optional idempotency key makes retries safe. Refunds unwind actual recorded usage in reverse order—overage, the exact purchased-credit lots consumed, then included allowance—and return 409 if the amount exceeds refundable usage.

Configure overage:

POST /v1/usage/{key}/settings

Server key required.

Terminal window
curl -X POST "https://api.guapocado.dev/v1/usage/ai-credits/settings" \
-H "x-guapocado-key: sk_guap_test_..." \
-H "content-type: application/json" \
-d '{"customerId":"org_123","overageEnabled":true}'

Fetch several billing answers at once.

POST /v1/context

Server key required.

Terminal window
curl -X POST "https://api.guapocado.dev/v1/context" \
-H "x-guapocado-key: sk_guap_test_..." \
-H "content-type: application/json" \
-d '{
"customer": {
"id": "org_123",
"name": "Acme Inc.",
"email": "owner@example.com"
},
"features": ["advanced-analytics"],
"usage": ["api-calls"],
"limits": ["seats"],
"includePlans": true,
"includeSubscription": true
}'

Use this for pages or services that need a billing snapshot instead of several separate calls.

Create or update a customer:

POST /v1/customers

Server key required.

Terminal window
curl -X POST "https://api.guapocado.dev/v1/customers" \
-H "x-guapocado-key: sk_guap_test_..." \
-H "content-type: application/json" \
-d '{
"id": "org_123",
"name": "Acme Inc.",
"email": "owner@example.com",
"metadata": {
"planSource": "signup"
}
}'

List customers:

GET /v1/customers?limit=50&cursor={cursor}

Fetch a customer:

GET /v1/customers/{id}

Create a Stripe Checkout session for a pushed product.

POST /v1/checkout

Server key required.

Terminal window
curl -X POST "https://api.guapocado.dev/v1/checkout" \
-H "x-guapocado-key: sk_guap_test_..." \
-H "content-type: application/json" \
-d '{
"customerId": "org_123",
"productKey": "pro",
"successUrl": "https://app.example.com/billing/success",
"cancelUrl": "https://app.example.com/billing"
}'

Response:

{
"url": "https://checkout.stripe.com/c/pay/..."
}

Redirect the customer to the returned URL.

List products pushed from billing.config.ts.

GET /v1/plans

Server key required.

Terminal window
curl "https://api.guapocado.dev/v1/plans" \
-H "x-guapocado-key: sk_guap_test_..."

The endpoint is named plans for compatibility, but the returned records map to products in billing.config.ts.

List subscriptions:

GET /v1/subscriptions?customerId={customerId}&limit=50&cursor={cursor}

Server key required.

Change an existing subscription to another recurring product:

POST /v1/subscriptions/change
Terminal window
curl -X POST "https://api.guapocado.dev/v1/subscriptions/change" \
-H "x-guapocado-key: sk_guap_test_..." \
-H "content-type: application/json" \
-d '{"customerId":"org_123","planKey":"business"}'

Use checkout for new subscriptions. Use subscription change when the customer already has an active Stripe-managed subscription.

Cancel immediately and refund the latest paid invoice:

POST /v1/subscriptions/{subscriptionId}/refunds
Terminal window
curl -X POST "https://api.guapocado.dev/v1/subscriptions/sub_123/refunds" \
-H "x-guapocado-key: sk_guap_test_..." \
-H "content-type: application/json" \
-d '{"mode":"prorated","idempotencyKey":"refund-sub-123"}'

prorated uses the unused fraction of the invoice’s actual paid period, so February and 30/31-day months are calculated correctly. full refunds the unrefunded amount of the latest paid invoice. Both modes cancel immediately.

List one-time purchases:

GET /v1/purchases?customerId={customerId}&limit=50&cursor={cursor}

Server key required.

Terminal window
curl "https://api.guapocado.dev/v1/purchases?customerId=org_123" \
-H "x-guapocado-key: sk_guap_test_..."

Refund a purchase and revoke its remaining entitlement:

POST /v1/purchases/{purchaseId}/refunds
Terminal window
curl -X POST "https://api.guapocado.dev/v1/purchases/pur_123/refunds" \
-H "x-guapocado-key: sk_guap_test_..." \
-H "content-type: application/json" \
-d '{"mode":"prorated","idempotencyKey":"refund-pur-123"}'

full returns all unrefunded cash and revokes the remaining grant. prorated is available for a single metered-credit grant and refunds the same fraction of the purchase price as the fraction of credit still unused. Every request needs a stable idempotency key.

Most teams should use the CLI for config sync:

Terminal window
npx guap plan --test
npx guap push --test

The HTTP sync endpoints exist for automation:

POST /v1/sync/push
GET /v1/sync/pull

Server key required.

Use these only if you are building your own internal deploy pipeline around Guapocado config.

Errors use JSON:

{
"error": "customerId required"
}

Common statuses:

  • 400: invalid request or missing required input
  • 401: missing, invalid, or revoked API key
  • 403: client key used for server-key-only endpoint
  • 404: resource or entitlement not found
  • 429: rate limit or insufficient usage balance
  • 500: unexpected server error

For usage consumption, 429 can mean insufficient balance. The response also includes the current balance fields when available.

Rate limits are per API key.

Current defaults:

  • server keys: 1000 requests per minute
  • client keys: 200 requests per minute

Responses include rate-limit headers:

X-RateLimit-Limit
X-RateLimit-Remaining
X-RateLimit-Reset

If you receive 429, retry after the reset time for normal rate limiting.

import os
import requests
res = requests.get(
"https://api.guapocado.dev/v1/entitlements/advanced-analytics/has",
headers={"x-guapocado-key": os.environ["GUAPOCADO_API_KEY"]},
params={"customerId": "org_123"},
)
res.raise_for_status()
has_access = res.json()
req, err := http.NewRequest(
"GET",
"https://api.guapocado.dev/v1/usage/api-calls/balance?customerId=org_123",
nil,
)
if err != nil {
return err
}
req.Header.Set("x-guapocado-key", os.Getenv("GUAPOCADO_API_KEY"))
res, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer res.Body.Close()
require "net/http"
require "json"
uri = URI("https://api.guapocado.dev/v1/entitlements/sso/has")
uri.query = URI.encode_www_form(customerId: "org_123")
req = Net::HTTP::Get.new(uri)
req["x-guapocado-key"] = ENV.fetch("GUAPOCADO_API_KEY")
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
http.request(req)
end
has_access = JSON.parse(res.body)

Per-customer custom pricing and entitlement values. Server key required.

GET /v1/contracts/{customerId}
PUT /v1/contracts/{customerId}
DELETE /v1/contracts/{customerId}
Terminal window
curl -X PUT "https://api.guapocado.dev/v1/contracts/org_123" \
-H "x-guapocado-key: sk_guap_test_..." \
-H "content-type: application/json" \
-d '{"priceAmount":200000,"priceInterval":"month","entitlements":{"seats":{"included":500}}}'

See Enterprise deals.

Append-only attribution for mutating actions. Server key required.

GET /v1/audit?action={action}&resourceType={type}&limit=50
Terminal window
curl "https://api.guapocado.dev/v1/audit?action=config.push&limit=50" \
-H "x-guapocado-key: sk_guap_test_..."

See Audit log.

Use the SDK when you are in TypeScript and want typed helpers.

Use HTTP when you are integrating from other languages, simple microservices, internal platforms, or generated clients.

The product model is the same either way:

  • product keys
  • entitlement keys
  • customer IDs
  • features
  • meters
  • limits