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
Base URL
Section titled “Base URL”End-user integrations call:
https://api.guapocado.devSandbox versus production is selected by API key, not by changing the host.
Authentication
Section titled “Authentication”Send your API key in the x-guapocado-key header:
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.
Content Type
Section titled “Content Type”For JSON requests, send:
content-type: application/jsonAll examples below use JSON.
Customer IDs
Section titled “Customer IDs”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.
Quick Examples
Section titled “Quick Examples”Check a feature:
curl "https://api.guapocado.dev/v1/entitlements/advanced-analytics/has?customerId=org_123" \ -H "x-guapocado-key: ck_guap_test_..."Read usage:
curl "https://api.guapocado.dev/v1/usage/api-calls/balance?customerId=org_123" \ -H "x-guapocado-key: ck_guap_test_..."Consume usage:
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:
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" }'OpenAPI
Section titled “OpenAPI”Each environment can expose an OpenAPI document generated from the pushed billing config:
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.
Billing Schema
Section titled “Billing Schema”The billing config JSON Schema is public:
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.
Features
Section titled “Features”Check whether a customer has a feature entitlement.
GET /v1/entitlements/{key}/has?customerId={customerId}Example:
curl "https://api.guapocado.dev/v1/entitlements/sso/has?customerId=org_123" \ -H "x-guapocado-key: ck_guap_test_..."Response:
trueUse a client key or server key.
Limits
Section titled “Limits”Read a numeric allowance.
GET /v1/entitlements/{key}/limit?customerId={customerId}Example:
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/settingsServer key required.
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:
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}/consumeServer key required.
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}/refundServer key required.
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}/settingsServer key required.
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}'Context
Section titled “Context”Fetch several billing answers at once.
POST /v1/contextServer key required.
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.
Customers
Section titled “Customers”Create or update a customer:
POST /v1/customersServer key required.
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}Checkout
Section titled “Checkout”Create a Stripe Checkout session for a pushed product.
POST /v1/checkoutServer key required.
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.
Products
Section titled “Products”List products pushed from billing.config.ts.
GET /v1/plansServer key required.
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.
Subscriptions
Section titled “Subscriptions”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/changecurl -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}/refundscurl -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.
Purchases
Section titled “Purchases”List one-time purchases:
GET /v1/purchases?customerId={customerId}&limit=50&cursor={cursor}Server key required.
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}/refundscurl -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.
Config Sync
Section titled “Config Sync”Most teams should use the CLI for config sync:
npx guap plan --testnpx guap push --testThe HTTP sync endpoints exist for automation:
POST /v1/sync/pushGET /v1/sync/pullServer key required.
Use these only if you are building your own internal deploy pipeline around Guapocado config.
Error Responses
Section titled “Error Responses”Errors use JSON:
{ "error": "customerId required"}Common statuses:
400: invalid request or missing required input401: missing, invalid, or revoked API key403: client key used for server-key-only endpoint404: resource or entitlement not found429: rate limit or insufficient usage balance500: unexpected server error
For usage consumption, 429 can mean insufficient balance. The response also
includes the current balance fields when available.
Rate Limits
Section titled “Rate Limits”Rate limits are per API key.
Current defaults:
- server keys:
1000requests per minute - client keys:
200requests per minute
Responses include rate-limit headers:
X-RateLimit-LimitX-RateLimit-RemainingX-RateLimit-ResetIf you receive 429, retry after the reset time for normal rate limiting.
Language Examples
Section titled “Language Examples”Python
Section titled “Python”import osimport 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)Enterprise deals
Section titled “Enterprise deals”Per-customer custom pricing and entitlement values. Server key required.
GET /v1/contracts/{customerId}PUT /v1/contracts/{customerId}DELETE /v1/contracts/{customerId}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.
Audit log
Section titled “Audit log”Append-only attribution for mutating actions. Server key required.
GET /v1/audit?action={action}&resourceType={type}&limit=50curl "https://api.guapocado.dev/v1/audit?action=config.push&limit=50" \ -H "x-guapocado-key: sk_guap_test_..."See Audit log.
Choosing HTTP vs SDK
Section titled “Choosing HTTP vs SDK”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