MENU navbar-image

Introduction

ProxyOn provides a single REST API to manage subscribers, subscriptions, plans, entitlements, metered usage and invoices on top of Stripe. The subscriber portal is hosted by ProxyOn and the actual payment method is collected by Stripe. Authenticate with a project API key and call the v1 endpoints below.

Welcome to the ProxyOn API documentation. ProxyOn orchestrates Stripe Billing so your application can offer subscription management under its own brand. Card data is collected and updated by Stripe; ProxyOn hosts the rest of the billing experience.

Quick Start

Integrate ProxyOn in four base calls — subscriptions are managed entirely inside the ProxyOn-hosted whitelabel billing portal, so you never build payment or plan-management UI yourself:

  1. Upsert a subscriber when a user signs up: POST /v1/subscribers.
  2. Open the billing portal for self-service plan selection, changes and cancellation: POST /v1/portal-sessions → redirect the user to the returned url.
  3. Read entitlements to gate features and enforce limits: GET /v1/subscribers/{external_id}/entitlements.
  4. Listen for webhooks (subscription.*, entitlement.updated, invoice.*) to keep your app in sync.

Report metered usage with POST /v1/usage when you bill by consumption.

Base URL

Authentication

All API requests require authentication using your Project API Key (pxn_test_* or pxn_live_*). Include it in the Authorization header as a Bearer token.

Rate Limits

Idempotency

Use Idempotency-Key header for safe retries on write operations. Successful (2xx) and client-error (4xx) responses are cached for 24 hours and replayed on retry; only server errors (5xx) bypass the cache.

Authenticating requests

To authenticate requests, include an Authorization header with the value "Bearer {YOUR_AUTH_KEY}".

All authenticated endpoints are marked with a requires authentication badge in the documentation below.

Send the project API key as Bearer pxntest or Bearer pxnlive, or use the X-Project-Api-Key header with the same value.

Subscribers

Manage subscribers in your project

List subscribers

requires authentication

Returns the most recent subscribers in your project (up to 100). Use the email filter to look one up by address, or type to narrow the list to users or organizations. Cursor pagination is not exposed yet — pivot bootstrap migrations should rely on these filters.

Example request:
curl --request GET \
    --get "https://proxyon.teknoza.be/api/v1/subscribers?type=organization&email=user%40example.com" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://proxyon.teknoza.be/api/v1/subscribers"
);

const params = {
    "type": "organization",
    "email": "user@example.com",
};
Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200, List wrapper. `data[]` items follow the Subscriber resource shape.):


{
    "object": "list",
    "data": [
        {
            "object": "subscriber",
            "id": "sbr_01HXYZ",
            "external_id": "user_12345",
            "type": "user",
            "email": "user@example.com",
            "name": "John Doe",
            "metadata": [],
            "created_at": "2026-05-20T10:00:00+00:00",
            "updated_at": "2026-05-20T10:00:00+00:00"
        }
    ]
}
 

Example response (403, Forbidden — API key lacks the required scope `subscribers:read`.):


{
    "error": {
        "type": "insufficient_scope",
        "message": "API key lacks required scope: subscribers:read",
        "doc_url": "https://proxyon.teknoza.be/docs#insufficient_scope"
    }
}
 

Request      

GET api/v1/subscribers

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

Query Parameters

type   string  optional    

Filter by subscriber type ("user" or "organization"). Example: organization

email   string  optional    

Filter by exact email address. Example: user@example.com

Retrieve a subscriber

requires authentication

Returns the subscriber identified by your application's external_id. Useful to verify Proxyon has the latest profile information or to fetch the linked Stripe customer id.

Example request:
curl --request GET \
    --get "https://proxyon.teknoza.be/api/v1/subscribers/architecto" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://proxyon.teknoza.be/api/v1/subscribers/architecto"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


{
    "object": "subscriber",
    "id": "sbr_t3wwu9tawndnbcfv12mm",
    "external_id": "usr_vhq7hkqioymi",
    "type": "user",
    "email": "price.amber@example.org",
    "name": "Miss Jazlyn Keebler III",
    "metadata": null,
    "created_at": "2026-06-19T07:30:19+00:00",
    "updated_at": "2026-06-19T07:30:19+00:00"
}
 

Example response (403, Forbidden — API key lacks the required scope `subscribers:read`.):


{
    "error": {
        "type": "insufficient_scope",
        "message": "API key lacks required scope: subscribers:read",
        "doc_url": "https://proxyon.teknoza.be/docs#insufficient_scope"
    }
}
 

Example response (404, Not Found — the referenced resource does not exist for this project.):


{
    "error": {
        "type": "not_found",
        "message": "The requested resource was not found.",
        "doc_url": "https://proxyon.teknoza.be/docs#not_found"
    }
}
 

Request      

GET api/v1/subscribers/{external_id}

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

external_id   string     

The external ID of the subscriber. Example: architecto

Create or update a subscriber

requires authentication

Idempotently upserts a subscriber by external_id. Call this right after a customer signs up in your application — Proxyon will create the subscriber on Stripe if needed and mirror the identity locally. Pass an Idempotency-Key header to safely retry on network errors.

Example request:
curl --request POST \
    "https://proxyon.teknoza.be/api/v1/subscribers" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Idempotency-Key: idem_01JXY..." \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"external_id\": \"user_12345\",
    \"type\": \"user\",
    \"email\": \"user@example.com\",
    \"name\": \"John Doe\",
    \"metadata\": {
        \"plan\": \"premium\"
    }
}"
const url = new URL(
    "https://proxyon.teknoza.be/api/v1/subscribers"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Idempotency-Key": "idem_01JXY...",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "external_id": "user_12345",
    "type": "user",
    "email": "user@example.com",
    "name": "John Doe",
    "metadata": {
        "plan": "premium"
    }
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (200):


{
    "object": "subscriber",
    "id": "sbr_ukmzqdw4upzrxqrieddd",
    "external_id": "usr_tiy9jkm83crh",
    "type": "user",
    "email": "okon.justina@example.com",
    "name": "Misael Runte",
    "metadata": null,
    "created_at": "2026-06-19T07:30:20+00:00",
    "updated_at": "2026-06-19T07:30:20+00:00"
}
 

Example response (403, Forbidden — API key lacks the required scope `subscribers:write`.):


{
    "error": {
        "type": "insufficient_scope",
        "message": "API key lacks required scope: subscribers:write",
        "doc_url": "https://proxyon.teknoza.be/docs#insufficient_scope"
    }
}
 

Request      

POST api/v1/subscribers

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Idempotency-Key        

Example: idem_01JXY...

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

external_id   string     

Unique external identifier for the subscriber. Example: user_12345

type   string     

Subscriber type: "user" or "organization". Example: user

email   string  optional    

Email address of the subscriber. Example: user@example.com

name   string  optional    

Display name of the subscriber. Example: John Doe

metadata   object  optional    

Arbitrary key-value metadata.

Update a subscriber

requires authentication

Partially updates a subscriber. Only the fields you send are changed; existing email, type, name and metadata are preserved. Stripe is reconciled automatically.

Example request:
curl --request PATCH \
    "https://proxyon.teknoza.be/api/v1/subscribers/architecto" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Idempotency-Key: idem_01JXY..." \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"type\": \"organization\",
    \"email\": \"newemail@example.com\",
    \"name\": \"Jane Doe\",
    \"metadata\": {
        \"plan\": \"basic\"
    }
}"
const url = new URL(
    "https://proxyon.teknoza.be/api/v1/subscribers/architecto"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Idempotency-Key": "idem_01JXY...",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "type": "organization",
    "email": "newemail@example.com",
    "name": "Jane Doe",
    "metadata": {
        "plan": "basic"
    }
};

fetch(url, {
    method: "PATCH",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (200):


{
    "object": "subscriber",
    "id": "sbr_0rivd5fy51dq5eqclgai",
    "external_id": "usr_n3rtfkzsyqlj",
    "type": "user",
    "email": "lafayette.considine@example.com",
    "name": "Mr. Adriel Romaguera",
    "metadata": null,
    "created_at": "2026-06-19T07:30:20+00:00",
    "updated_at": "2026-06-19T07:30:20+00:00"
}
 

Example response (403, Forbidden — API key lacks the required scope `subscribers:write`.):


{
    "error": {
        "type": "insufficient_scope",
        "message": "API key lacks required scope: subscribers:write",
        "doc_url": "https://proxyon.teknoza.be/docs#insufficient_scope"
    }
}
 

Example response (404, Not Found — the referenced resource does not exist for this project.):


{
    "error": {
        "type": "not_found",
        "message": "The requested resource was not found.",
        "doc_url": "https://proxyon.teknoza.be/docs#not_found"
    }
}
 

Request      

PATCH api/v1/subscribers/{external_id}

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Idempotency-Key        

Example: idem_01JXY...

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

external_id   string     

The external ID of the subscriber. Example: architecto

Body Parameters

type   string  optional    

Subscriber type: "user" or "organization". Example: organization

email   string  optional    

New email address. Example: newemail@example.com

name   string  optional    

New display name. Example: Jane Doe

metadata   object  optional    

Updated metadata (merge).

Delete a subscriber

requires authentication

Soft-deletes a subscriber. Active subscriptions are cancelled at the period end by Stripe; the local row is kept for historic invoice access.

Example request:
curl --request DELETE \
    "https://proxyon.teknoza.be/api/v1/subscribers/architecto" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Idempotency-Key: idem_01JXY..." \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://proxyon.teknoza.be/api/v1/subscribers/architecto"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Idempotency-Key": "idem_01JXY...",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "DELETE",
    headers,
}).then(response => response.json());

Example response (204, Subscriber soft-deleted.):

Empty response
 

Example response (403, Forbidden — API key lacks the required scope `subscribers:write`.):


{
    "error": {
        "type": "insufficient_scope",
        "message": "API key lacks required scope: subscribers:write",
        "doc_url": "https://proxyon.teknoza.be/docs#insufficient_scope"
    }
}
 

Example response (404, Not Found — the referenced resource does not exist for this project.):


{
    "error": {
        "type": "not_found",
        "message": "The requested resource was not found.",
        "doc_url": "https://proxyon.teknoza.be/docs#not_found"
    }
}
 

Request      

DELETE api/v1/subscribers/{external_id}

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Idempotency-Key        

Example: idem_01JXY...

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

external_id   string     

The external ID of the subscriber. Example: architecto

Subscriptions

Subscription lifecycle and listing

List a subscriber's subscriptions

requires authentication

Returns every subscription for the subscriber, active ones first. Use this to display a billing history page or to find the subscription id to cancel, swap or resume.

Example request:
curl --request GET \
    --get "https://proxyon.teknoza.be/api/v1/subscribers/user_12345/subscriptions" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://proxyon.teknoza.be/api/v1/subscribers/user_12345/subscriptions"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200, List of the subscriber's subscriptions, active subscriptions first. `data[]` items follow the Subscription resource shape.):


{
    "object": "list",
    "data": [
        {
            "object": "subscription",
            "id": "sub_01HXYZ",
            "project_id": "1",
            "subscriber_id": "1",
            "plan_id": "1",
            "status": "active",
            "current_period_start": "2026-05-01T00:00:00+00:00",
            "current_period_end": "2026-06-01T00:00:00+00:00",
            "trial_ends_at": null,
            "cancel_at": null,
            "canceled_at": null,
            "metadata": [],
            "created_at": "2026-05-01T00:00:00+00:00",
            "updated_at": "2026-05-01T00:00:00+00:00"
        }
    ]
}
 

Example response (403, Forbidden — API key lacks the required scope `subscriptions:read`.):


{
    "error": {
        "type": "insufficient_scope",
        "message": "API key lacks required scope: subscriptions:read",
        "doc_url": "https://proxyon.teknoza.be/docs#insufficient_scope"
    }
}
 

Example response (404, Not Found — the referenced resource does not exist for this project.):


{
    "error": {
        "type": "not_found",
        "message": "The requested resource was not found.",
        "doc_url": "https://proxyon.teknoza.be/docs#not_found"
    }
}
 

Request      

GET api/v1/subscribers/{external_id}/subscriptions

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

external_id   string     

External identifier of the subscriber. Example: user_12345

Retrieve a subscription

requires authentication

Returns the subscription identified by its public id (sub_*) or numeric id. Includes the resolved plan, prices and current billing period bounds.

Example request:
curl --request GET \
    --get "https://proxyon.teknoza.be/api/v1/subscriptions/sub_01JXY..." \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://proxyon.teknoza.be/api/v1/subscriptions/sub_01JXY..."
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


{
    "object": "subscription",
    "id": "sub_vaxvvpaa0rvhe5qlpzwy",
    "project_id": "3",
    "subscriber_id": "2",
    "plan_id": "4",
    "status": "active",
    "current_period_start": "2026-06-01T00:00:00+00:00",
    "current_period_end": "2026-07-01T00:00:00+00:00",
    "trial_ends_at": null,
    "cancel_at": null,
    "canceled_at": null,
    "metadata": null,
    "created_at": "2026-06-19T07:30:20+00:00",
    "updated_at": "2026-06-19T07:30:20+00:00"
}
 

Example response (403, Forbidden — API key lacks the required scope `subscriptions:read`.):


{
    "error": {
        "type": "insufficient_scope",
        "message": "API key lacks required scope: subscriptions:read",
        "doc_url": "https://proxyon.teknoza.be/docs#insufficient_scope"
    }
}
 

Example response (404, Not Found — the referenced resource does not exist for this project.):


{
    "error": {
        "type": "not_found",
        "message": "The requested resource was not found.",
        "doc_url": "https://proxyon.teknoza.be/docs#not_found"
    }
}
 

Request      

GET api/v1/subscriptions/{id}

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

id   string     

Subscription public id (sub_*) or numeric id. Example: sub_01JXY...

Cancel a subscription

requires authentication

Cancels a subscription. By default it stays active until the end of the current billing period; pass at_period_end=false to cancel immediately and prorate the unused time.

Example request:
curl --request POST \
    "https://proxyon.teknoza.be/api/v1/subscriptions/sub_01JXY.../cancel" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Idempotency-Key: idem_01JXY..." \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"at_period_end\": true,
    \"reason\": \"Customer requested downgrade\"
}"
const url = new URL(
    "https://proxyon.teknoza.be/api/v1/subscriptions/sub_01JXY.../cancel"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Idempotency-Key": "idem_01JXY...",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "at_period_end": true,
    "reason": "Customer requested downgrade"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (200):


{
    "object": "subscription",
    "id": "sub_t2iwx7fs0gxsz2xwa7gd",
    "project_id": "13",
    "subscriber_id": "6",
    "plan_id": "7",
    "status": "active",
    "current_period_start": "2026-06-01T00:00:00+00:00",
    "current_period_end": "2026-07-01T00:00:00+00:00",
    "trial_ends_at": null,
    "cancel_at": null,
    "canceled_at": null,
    "metadata": null,
    "created_at": "2026-06-19T07:30:20+00:00",
    "updated_at": "2026-06-19T07:30:20+00:00"
}
 

Example response (403, Forbidden — API key lacks the required scope `subscriptions:write`.):


{
    "error": {
        "type": "insufficient_scope",
        "message": "API key lacks required scope: subscriptions:write",
        "doc_url": "https://proxyon.teknoza.be/docs#insufficient_scope"
    }
}
 

Example response (404, Not Found — the referenced resource does not exist for this project.):


{
    "error": {
        "type": "not_found",
        "message": "The requested resource was not found.",
        "doc_url": "https://proxyon.teknoza.be/docs#not_found"
    }
}
 

Example response (422, Domain error: `subscription_already_canceled`.):


{
    "error": {
        "type": "subscription_already_canceled",
        "message": "Subscription is already canceled.",
        "doc_url": "https://proxyon.teknoza.be/docs#subscription_already_canceled"
    }
}
 

Example response (422, Domain error: `subscription_not_actionable_cancel`.):


{
    "error": {
        "type": "subscription_not_actionable_cancel",
        "message": "Subscription state does not allow cancellation.",
        "doc_url": "https://proxyon.teknoza.be/docs#subscription_not_actionable_cancel"
    }
}
 

Example response (422, Domain error: `subscription_missing_stripe_id`.):


{
    "error": {
        "type": "subscription_missing_stripe_id",
        "message": "Subscription is missing its Stripe identifier.",
        "doc_url": "https://proxyon.teknoza.be/docs#subscription_missing_stripe_id"
    }
}
 

Request      

POST api/v1/subscriptions/{id}/cancel

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Idempotency-Key        

Example: idem_01JXY...

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

id   string     

Subscription public id (sub_*) or numeric id. Example: sub_01JXY...

Body Parameters

at_period_end   boolean  optional    

When true (default) the subscription is cancelled at the end of the current billing period; when false it is cancelled immediately. Example: true

reason   string  optional    

Optional human-readable reason stored on the subscription metadata. Example: Customer requested downgrade

Swap a subscription's plan price

requires authentication

Switches the subscription to a different plan_price_id. Stripe handles proration automatically — pass proration="none" to skip it and bill the new price starting from the next cycle.

Example request:
curl --request POST \
    "https://proxyon.teknoza.be/api/v1/subscriptions/sub_01JXY.../swap" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Idempotency-Key: idem_01JXY..." \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"plan_price_id\": 42,
    \"proration\": \"always\"
}"
const url = new URL(
    "https://proxyon.teknoza.be/api/v1/subscriptions/sub_01JXY.../swap"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Idempotency-Key": "idem_01JXY...",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "plan_price_id": 42,
    "proration": "always"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (200):


{
    "object": "subscription",
    "id": "sub_ekutbdkjle1uqdrqjfoi",
    "project_id": "16",
    "subscriber_id": "7",
    "plan_id": "8",
    "status": "active",
    "current_period_start": "2026-06-01T00:00:00+00:00",
    "current_period_end": "2026-07-01T00:00:00+00:00",
    "trial_ends_at": null,
    "cancel_at": null,
    "canceled_at": null,
    "metadata": null,
    "created_at": "2026-06-19T07:30:20+00:00",
    "updated_at": "2026-06-19T07:30:20+00:00"
}
 

Example response (403, Forbidden — API key lacks the required scope `subscriptions:write`.):


{
    "error": {
        "type": "insufficient_scope",
        "message": "API key lacks required scope: subscriptions:write",
        "doc_url": "https://proxyon.teknoza.be/docs#insufficient_scope"
    }
}
 

Example response (404, Not Found — the referenced resource does not exist for this project.):


{
    "error": {
        "type": "not_found",
        "message": "The requested resource was not found.",
        "doc_url": "https://proxyon.teknoza.be/docs#not_found"
    }
}
 

Example response (422, Domain error: `subscription_not_actionable_swap`.):


{
    "error": {
        "type": "subscription_not_actionable_swap",
        "message": "Subscription state does not allow swapping.",
        "doc_url": "https://proxyon.teknoza.be/docs#subscription_not_actionable_swap"
    }
}
 

Example response (422, Domain error: `subscription_missing_stripe_id`.):


{
    "error": {
        "type": "subscription_missing_stripe_id",
        "message": "Subscription is missing its Stripe identifier.",
        "doc_url": "https://proxyon.teknoza.be/docs#subscription_missing_stripe_id"
    }
}
 

Example response (422, Domain error: `plan_archived`.):


{
    "error": {
        "type": "plan_archived",
        "message": "Plan is archived and cannot be used for new subscriptions.",
        "doc_url": "https://proxyon.teknoza.be/docs#plan_archived"
    }
}
 

Example response (422, Domain error: `plan_price_inactive`.):


{
    "error": {
        "type": "plan_price_inactive",
        "message": "Plan price is inactive.",
        "doc_url": "https://proxyon.teknoza.be/docs#plan_price_inactive"
    }
}
 

Example response (422, Domain error: `subscription_price_not_synced`.):


{
    "error": {
        "type": "subscription_price_not_synced",
        "message": "Subscription price is not synced to Stripe.",
        "doc_url": "https://proxyon.teknoza.be/docs#subscription_price_not_synced"
    }
}
 

Request      

POST api/v1/subscriptions/{id}/swap

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Idempotency-Key        

Example: idem_01JXY...

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

id   string     

Subscription public id (sub_*) or numeric id. Example: sub_01JXY...

Body Parameters

plan_price_id   integer     

Identifier of the plan price to swap into. Example: 42

proration   string     

Proration behaviour. Allowed values: "always" (prorate immediately), "none" (no proration), "always_invoice" (prorate and immediately invoice any proration). Required. Example: always

Resume a cancelled subscription

requires authentication

Reverts a subscription that was previously cancelled at period end. Only works while the current billing period has not yet ended; after that the subscription must be re-created.

Example request:
curl --request POST \
    "https://proxyon.teknoza.be/api/v1/subscriptions/sub_01JXY.../resume" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Idempotency-Key: idem_01JXY..." \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://proxyon.teknoza.be/api/v1/subscriptions/sub_01JXY.../resume"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Idempotency-Key": "idem_01JXY...",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "POST",
    headers,
}).then(response => response.json());

Example response (200):


{
    "object": "subscription",
    "id": "sub_ieqmfxekub3v6njrvxfh",
    "project_id": "19",
    "subscriber_id": "8",
    "plan_id": "9",
    "status": "active",
    "current_period_start": "2026-06-01T00:00:00+00:00",
    "current_period_end": "2026-07-01T00:00:00+00:00",
    "trial_ends_at": null,
    "cancel_at": null,
    "canceled_at": null,
    "metadata": null,
    "created_at": "2026-06-19T07:30:20+00:00",
    "updated_at": "2026-06-19T07:30:20+00:00"
}
 

Example response (403, Forbidden — API key lacks the required scope `subscriptions:write`.):


{
    "error": {
        "type": "insufficient_scope",
        "message": "API key lacks required scope: subscriptions:write",
        "doc_url": "https://proxyon.teknoza.be/docs#insufficient_scope"
    }
}
 

Example response (404, Not Found — the referenced resource does not exist for this project.):


{
    "error": {
        "type": "not_found",
        "message": "The requested resource was not found.",
        "doc_url": "https://proxyon.teknoza.be/docs#not_found"
    }
}
 

Example response (422, Domain error: `subscription_already_canceled`.):


{
    "error": {
        "type": "subscription_already_canceled",
        "message": "Subscription is already canceled.",
        "doc_url": "https://proxyon.teknoza.be/docs#subscription_already_canceled"
    }
}
 

Example response (422, Domain error: `subscription_cannot_resume`.):


{
    "error": {
        "type": "subscription_cannot_resume",
        "message": "Subscription cannot be resumed in its current state.",
        "doc_url": "https://proxyon.teknoza.be/docs#subscription_cannot_resume"
    }
}
 

Example response (422, Domain error: `subscription_missing_stripe_id`.):


{
    "error": {
        "type": "subscription_missing_stripe_id",
        "message": "Subscription is missing its Stripe identifier.",
        "doc_url": "https://proxyon.teknoza.be/docs#subscription_missing_stripe_id"
    }
}
 

Request      

POST api/v1/subscriptions/{id}/resume

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Idempotency-Key        

Example: idem_01JXY...

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

id   string     

Subscription public id (sub_*) or numeric id. Example: sub_01JXY...

Plans

List and retrieve plans in your project

List plans

requires authentication

Returns the active plans in your project, each with its active prices and the currency they are billed in. Use this to render a pricing page in your own UI; subscribers pick and pay for a plan inside the ProxyOn billing portal (POST /v1/portal-sessions).

Example request:
curl --request GET \
    --get "https://proxyon.teknoza.be/api/v1/plans?currency=USD" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://proxyon.teknoza.be/api/v1/plans"
);

const params = {
    "currency": "USD",
};
Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200, List of active plans. `data[]` items follow the Plan resource shape (with `prices[]`).):


{
    "object": "list",
    "data": [
        {
            "object": "plan",
            "id": "1",
            "key": "pro_monthly",
            "locale": "en",
            "name": "Pro Monthly",
            "description": "Professional plan billed monthly.",
            "status": "active",
            "interval_unit": "month",
            "interval_count": 1,
            "trial_days": 14,
            "metadata": [],
            "created_at": "2026-05-01T00:00:00+00:00",
            "updated_at": "2026-05-01T00:00:00+00:00",
            "prices": []
        }
    ]
}
 

Example response (403, Forbidden — API key lacks the required scope `plans:read`.):


{
    "error": {
        "type": "insufficient_scope",
        "message": "API key lacks required scope: plans:read",
        "doc_url": "https://proxyon.teknoza.be/docs#insufficient_scope"
    }
}
 

Request      

GET api/v1/plans

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

Query Parameters

currency   string  optional    

Filter plans by currency code (e.g. USD, EUR). Example: USD

Retrieve a plan by key

requires authentication

Returns a single plan and its active prices, identified by the human-readable plan key (e.g. pro, basic-monthly). Prefer this over numeric ids when wiring up your pricing UI.

Example request:
curl --request GET \
    --get "https://proxyon.teknoza.be/api/v1/plans/basic-monthly" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://proxyon.teknoza.be/api/v1/plans/basic-monthly"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


{
    "object": "plan",
    "id": "5",
    "key": "aut_adipisci",
    "locale": "en",
    "name": "Quidem nostrum Plan",
    "description": "",
    "status": "draft",
    "metadata": null,
    "created_at": "2026-06-19T07:30:20+00:00",
    "updated_at": "2026-06-19T07:30:20+00:00"
}
 

Example response (403, Forbidden — API key lacks the required scope `plans:read`.):


{
    "error": {
        "type": "insufficient_scope",
        "message": "API key lacks required scope: plans:read",
        "doc_url": "https://proxyon.teknoza.be/docs#insufficient_scope"
    }
}
 

Example response (404, Not Found — the referenced resource does not exist for this project.):


{
    "error": {
        "type": "not_found",
        "message": "The requested resource was not found.",
        "doc_url": "https://proxyon.teknoza.be/docs#not_found"
    }
}
 

Request      

GET api/v1/plans/{key}

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

key   string     

The key of the plan. Example: basic-monthly

Portal

Whitelabel billing portal sessions

Create a billing portal session

requires authentication

Issues a single-use magic link to the project's whitelabel ProxyOn billing portal so the subscriber can choose or change a plan, update their payment method, view invoices and cancel on their own. Redirect them to the returned url; when they leave the portal they are sent back to return_url.

Example request:
curl --request POST \
    "https://proxyon.teknoza.be/api/v1/portal-sessions" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Idempotency-Key: idem_01JXY..." \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"subscriber_external_id\": \"user_12345\",
    \"return_url\": \"https:\\/\\/app.example.com\\/account\"
}"
const url = new URL(
    "https://proxyon.teknoza.be/api/v1/portal-sessions"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Idempotency-Key": "idem_01JXY...",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "subscriber_external_id": "user_12345",
    "return_url": "https:\/\/app.example.com\/account"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (200, Portal session created. Redirect the subscriber to the returned URL.):


{
    "url": "https://billing.example.com/portal/abcdef0123456789",
    "expires_at": "2026-01-15T10:15:00+00:00"
}
 

Example response (403, Forbidden — API key lacks the required scope `portal:write`.):


{
    "error": {
        "type": "insufficient_scope",
        "message": "API key lacks required scope: portal:write",
        "doc_url": "https://proxyon.teknoza.be/docs#insufficient_scope"
    }
}
 

Example response (404, Domain error: `subscriber_not_found`.):


{
    "error": {
        "type": "subscriber_not_found",
        "message": "Subscriber not found for this project.",
        "doc_url": "https://proxyon.teknoza.be/docs#subscriber_not_found"
    }
}
 

Example response (409, Domain error: `stripe_not_connected`.):


{
    "error": {
        "type": "stripe_not_connected",
        "message": "Connect and verify your Stripe account before using checkout or billing portal.",
        "doc_url": "https://proxyon.teknoza.be/docs#stripe_not_connected"
    }
}
 

Example response (422, Domain error: `subscriber_wrong_project`.):


{
    "error": {
        "type": "subscriber_wrong_project",
        "message": "Subscriber does not belong to the authenticated project.",
        "doc_url": "https://proxyon.teknoza.be/docs#subscriber_wrong_project"
    }
}
 

Request      

POST api/v1/portal-sessions

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Idempotency-Key        

Example: idem_01JXY...

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

subscriber_external_id   string     

External identifier of the subscriber to authenticate into the portal. Example: user_12345

return_url   string     

URL the portal redirects the subscriber to when they leave. Example: https://app.example.com/account

Entitlements

Resolve subscriber feature entitlements

Resolve a subscriber's entitlements

requires authentication

Returns the resolved feature flags and limits the subscriber currently has access to, based on their active subscription's plan. Cache this on the client for 60 seconds — Proxyon returns a strong ETag so you can revalidate with If-None-Match for a cheap 304 Not Modified.

Example request:
curl --request GET \
    --get "https://proxyon.teknoza.be/api/v1/subscribers/user_12345/entitlements" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "If-None-Match: \"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\"" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://proxyon.teknoza.be/api/v1/subscribers/user_12345/entitlements"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "If-None-Match": ""e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200, Returned with `Cache-Control: private, max-age=60` and a strong `ETag` of the JSON body. Send that ETag back as `If-None-Match` for a cheap revalidation.):


{
    "object": "entitlements",
    "data": {
        "subscriber_id": 42,
        "project_id": 7,
        "subscription_id": 311,
        "plan": {
            "key": "pro",
            "name": "Pro",
            "description": "Professional plan.",
            "interval_unit": "month",
            "interval_count": 1
        },
        "seats": 5,
        "entries": [
            {
                "key": "projects",
                "type": "quota",
                "value": 5,
                "remaining": 3,
                "period_start": "2026-06-01T00:00:00+00:00",
                "period_end": "2026-07-01T00:00:00+00:00"
            },
            {
                "key": "sso",
                "type": "boolean",
                "value": true,
                "remaining": null,
                "period_start": "2026-06-01T00:00:00+00:00",
                "period_end": "2026-07-01T00:00:00+00:00"
            }
        ],
        "generated_at": "2026-06-10T12:00:00+00:00"
    }
}
 

Example response (304, Entitlement set unchanged since the ETag in `If-None-Match`.):



 

Example response (403, Forbidden — API key lacks the required scope `entitlements:read`.):


{
    "error": {
        "type": "insufficient_scope",
        "message": "API key lacks required scope: entitlements:read",
        "doc_url": "https://proxyon.teknoza.be/docs#insufficient_scope"
    }
}
 

Example response (404, Not Found — the referenced resource does not exist for this project.):


{
    "error": {
        "type": "not_found",
        "message": "The requested resource was not found.",
        "doc_url": "https://proxyon.teknoza.be/docs#not_found"
    }
}
 

Request      

GET api/v1/subscribers/{external_id}/entitlements

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

If-None-Match        

Example: "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

external_id   string     

External identifier of the subscriber. Example: user_12345

Usage

Metered usage records and summaries

Get a subscriber's usage summary

requires authentication

Aggregates the subscriber's recorded usage for a single metered feature within the current billing period. Returns 404 no_active_subscription when the subscriber has no active subscription to report against.

Example request:
curl --request GET \
    --get "https://proxyon.teknoza.be/api/v1/subscribers/user_12345/usage?feature_key=api_calls&period=current" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"feature_key\": \"b\",
    \"period\": \"last\"
}"
const url = new URL(
    "https://proxyon.teknoza.be/api/v1/subscribers/user_12345/usage"
);

const params = {
    "feature_key": "api_calls",
    "period": "current",
};
Object.keys(params)
    .forEach(key => url.searchParams.append(key, params[key]));

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "feature_key": "b",
    "period": "last"
};

fetch(url, {
    method: "GET",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (200, Aggregated usage for the subscriber's active subscription, scoped to the current billing period.):


{
    "object": "usage_summary",
    "feature_key": "api_calls",
    "quantity": 1284,
    "period_start": "2026-05-01T00:00:00+00:00",
    "period_end": "2026-06-01T00:00:00+00:00",
    "subscription_public_id": "sub_01JXY..."
}
 

Example response (403, Forbidden — API key lacks the required scope `usage:read`.):


{
    "error": {
        "type": "insufficient_scope",
        "message": "API key lacks required scope: usage:read",
        "doc_url": "https://proxyon.teknoza.be/docs#insufficient_scope"
    }
}
 

Example response (404, Subscriber has no active subscription to summarise usage against.):


{
    "error": {
        "type": "no_active_subscription",
        "message": "Subscriber has no active subscription.",
        "doc_url": "/docs#no_active_subscription"
    }
}
 

Example response (404, Not Found — the referenced resource does not exist for this project.):


{
    "error": {
        "type": "not_found",
        "message": "The requested resource was not found.",
        "doc_url": "https://proxyon.teknoza.be/docs#not_found"
    }
}
 

Request      

GET api/v1/subscribers/{external_id}/usage

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

external_id   string     

External identifier of the subscriber. Example: user_12345

Query Parameters

feature_key   string     

Feature key to summarise usage for. Example: api_calls

period   string  optional    

Period to aggregate: "current" (default — the active billing period), "last" (the billing period immediately before the current one), or "last_30d" (rolling window over the last 30 days). Example: current

Body Parameters

feature_key   string     

Must match the regex /^[a-z][a-z0-9_]{0,254}$/. Must not be greater than 255 characters. Example: b

period   string  optional    

Example: last

Must be one of:
  • current
  • last
  • last_30d

Record metered usage

requires authentication

Records a usage event against the subscriber's active metered subscription item. Use this whenever the subscriber consumes a billable feature (API call, transcoded video, sent email, …).

Pass a stable idempotency_key per logical event — the same key always returns the same record, so it is safe to retry on network errors. The recorded quantity is forwarded to Stripe and reflected in the next invoice for the subscriber's current billing period.

Example request:
curl --request POST \
    "https://proxyon.teknoza.be/api/v1/usage" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Idempotency-Key: idem_01JXY..." \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"subscriber_external_id\": \"user_12345\",
    \"feature_key\": \"api_calls\",
    \"quantity\": 10,
    \"idempotency_key\": \"01JXY7QGJ8KZ...\",
    \"recorded_at\": \"2026-01-15T10:00:00Z\"
}"
const url = new URL(
    "https://proxyon.teknoza.be/api/v1/usage"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Idempotency-Key": "idem_01JXY...",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "subscriber_external_id": "user_12345",
    "feature_key": "api_calls",
    "quantity": 10,
    "idempotency_key": "01JXY7QGJ8KZ...",
    "recorded_at": "2026-01-15T10:00:00Z"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (200):


{
    "object": "usage_record",
    "id": "1",
    "feature_id": "6",
    "quantity": 7,
    "recorded_at": "2026-06-19T07:30:20+00:00",
    "idempotency_key": "usage_jabpoyoei97ev8uh4h25yfch",
    "created_at": "2026-06-19T07:30:21+00:00",
    "updated_at": "2026-06-19T07:30:21+00:00"
}
 

Example response (403, Forbidden — API key lacks the required scope `usage:write`.):


{
    "error": {
        "type": "insufficient_scope",
        "message": "API key lacks required scope: usage:write",
        "doc_url": "https://proxyon.teknoza.be/docs#insufficient_scope"
    }
}
 

Example response (404, Domain error: `no_active_subscription`.):


{
    "error": {
        "type": "no_active_subscription",
        "message": "This subscriber has no active subscription.",
        "doc_url": "https://proxyon.teknoza.be/docs#no_active_subscription"
    }
}
 

Example response (422, Domain error: `usage_invalid_quantity`.):


{
    "error": {
        "type": "usage_invalid_quantity",
        "message": "Usage quantity must be a positive integer.",
        "doc_url": "https://proxyon.teknoza.be/docs#usage_invalid_quantity"
    }
}
 

Example response (422, Domain error: `usage_correction_requires_sum_aggregation`.):


{
    "error": {
        "type": "usage_correction_requires_sum_aggregation",
        "message": "Usage corrections require sum aggregation.",
        "doc_url": "https://proxyon.teknoza.be/docs#usage_correction_requires_sum_aggregation"
    }
}
 

Example response (422, Domain error: `usage_recorded_at_in_future`.):


{
    "error": {
        "type": "usage_recorded_at_in_future",
        "message": "recorded_at must be at or before the current server time.",
        "doc_url": "https://proxyon.teknoza.be/docs#usage_recorded_at_in_future"
    }
}
 

Example response (422, Domain error: `usage_recorded_at_too_old`.):


{
    "error": {
        "type": "usage_recorded_at_too_old",
        "message": "recorded_at is older than the accepted backdating window.",
        "doc_url": "https://proxyon.teknoza.be/docs#usage_recorded_at_too_old"
    }
}
 

Example response (422, Domain error: `usage_subscription_not_active`.):


{
    "error": {
        "type": "usage_subscription_not_active",
        "message": "Subscription is not active; usage cannot be recorded.",
        "doc_url": "https://proxyon.teknoza.be/docs#usage_subscription_not_active"
    }
}
 

Example response (422, Domain error: `usage_unsupported_feature_type`.):


{
    "error": {
        "type": "usage_unsupported_feature_type",
        "message": "Feature type does not support usage records.",
        "doc_url": "https://proxyon.teknoza.be/docs#usage_unsupported_feature_type"
    }
}
 

Example response (422, Domain error: `usage_feature_not_in_plan`.):


{
    "error": {
        "type": "usage_feature_not_in_plan",
        "message": "Feature is not part of the subscription plan.",
        "doc_url": "https://proxyon.teknoza.be/docs#usage_feature_not_in_plan"
    }
}
 

Example response (422, Domain error: `subscription_no_items`.):


{
    "error": {
        "type": "subscription_no_items",
        "message": "Subscription has no priced items.",
        "doc_url": "https://proxyon.teknoza.be/docs#subscription_no_items"
    }
}
 

Example response (422, Domain error: `plan_cross_project_resource`.):


{
    "error": {
        "type": "plan_cross_project_resource",
        "message": "Plan resource belongs to a different project.",
        "doc_url": "https://proxyon.teknoza.be/docs#plan_cross_project_resource"
    }
}
 

Request      

POST api/v1/usage

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Idempotency-Key        

Example: idem_01JXY...

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

subscriber_external_id   string     

External identifier of the subscriber whose subscription will be charged. Example: user_12345

feature_key   string     

Metered feature key to record usage against. Example: api_calls

quantity   integer     

Quantity to record. Non-zero integer. Positive values record consumption; negative values post a correction (only allowed on Metered features whose Stripe Meter uses sum aggregation, or on Quota features). Example: 10

idempotency_key   string     

Unique key to deduplicate this usage record (max 100 chars — forwarded to Stripe as the meter event identifier). Same key returns the same record. Example: 01JXY7QGJ8KZ...

recorded_at   string  optional    

ISO-8601 timestamp when usage was observed. Defaults to the server time. Example: 2026-01-15T10:00:00Z

Invoices

List and retrieve invoices

List a subscriber's invoices

requires authentication

Returns invoices for the subscriber in reverse chronological order, including hosted invoice URLs and PDF links generated by Stripe. Ideal for a "Billing history" tab in your application.

Example request:
curl --request GET \
    --get "https://proxyon.teknoza.be/api/v1/subscribers/user_12345/invoices" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://proxyon.teknoza.be/api/v1/subscribers/user_12345/invoices"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200, List of invoices for the subscriber, newest first. `data[]` items follow the Invoice resource shape.):


{
    "object": "list",
    "data": [
        {
            "object": "invoice",
            "id": "inv_01HXYZ",
            "subscription_id": "1",
            "currency_id": "1",
            "currency_code": "USD",
            "status": "paid",
            "amount_due": 2900,
            "amount_paid": 2900,
            "hosted_invoice_url": "https://invoice.stripe.com/i/acct_xxx/inv_xxx",
            "pdf_url": "https://pay.stripe.com/invoice/xxx/pdf",
            "period_start": "2026-05-01T00:00:00+00:00",
            "period_end": "2026-06-01T00:00:00+00:00",
            "paid_at": "2026-05-02T10:00:00+00:00",
            "created_at": "2026-05-01T00:00:00+00:00",
            "updated_at": "2026-05-02T10:00:00+00:00"
        }
    ]
}
 

Example response (403, Forbidden — API key lacks the required scope `invoices:read`.):


{
    "error": {
        "type": "insufficient_scope",
        "message": "API key lacks required scope: invoices:read",
        "doc_url": "https://proxyon.teknoza.be/docs#insufficient_scope"
    }
}
 

Example response (404, Not Found — the referenced resource does not exist for this project.):


{
    "error": {
        "type": "not_found",
        "message": "The requested resource was not found.",
        "doc_url": "https://proxyon.teknoza.be/docs#not_found"
    }
}
 

Request      

GET api/v1/subscribers/{external_id}/invoices

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

external_id   string     

External identifier of the subscriber. Example: user_12345

Retrieve an invoice

requires authentication

Returns a single invoice by its public id (in_*) or numeric id, including the hosted Stripe URL and PDF download link.

Example request:
curl --request GET \
    --get "https://proxyon.teknoza.be/api/v1/invoices/in_01JXY..." \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://proxyon.teknoza.be/api/v1/invoices/in_01JXY..."
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


{
    "object": "invoice",
    "id": "in_zqkfw2oyq3hruqprcz4g",
    "subscription_id": "2",
    "currency_id": "1",
    "status": "open",
    "amount_due": 49055,
    "amount_paid": 0,
    "hosted_invoice_url": null,
    "pdf_url": null,
    "period_start": "2026-06-01T00:00:00+00:00",
    "period_end": "2026-07-01T00:00:00+00:00",
    "paid_at": null,
    "created_at": "2026-06-19T07:30:20+00:00",
    "updated_at": "2026-06-19T07:30:20+00:00"
}
 

Example response (403, Forbidden — API key lacks the required scope `invoices:read`.):


{
    "error": {
        "type": "insufficient_scope",
        "message": "API key lacks required scope: invoices:read",
        "doc_url": "https://proxyon.teknoza.be/docs#insufficient_scope"
    }
}
 

Example response (404, Not Found — the referenced resource does not exist for this project.):


{
    "error": {
        "type": "not_found",
        "message": "The requested resource was not found.",
        "doc_url": "https://proxyon.teknoza.be/docs#not_found"
    }
}
 

Request      

GET api/v1/invoices/{id}

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

id   string     

Invoice public id (in_*) or numeric id. Example: in_01JXY...

Webhook Endpoints

Manage outbound webhook endpoints for your project

List webhook endpoints

requires authentication

Returns every webhook endpoint configured for your project, with their current status and which event types they subscribe to. Use this to render a webhooks settings page in your dashboard.

Example request:
curl --request GET \
    --get "https://proxyon.teknoza.be/api/v1/webhook-endpoints" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://proxyon.teknoza.be/api/v1/webhook-endpoints"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200, List of webhook endpoints. `data[]` items follow the WebhookEndpoint resource shape.):


{
    "object": "list",
    "data": [
        {
            "object": "webhook_endpoint",
            "id": 1,
            "url": "https://example.test/webhooks/proxyon",
            "description": "Production webhook",
            "status": "active",
            "event_types": [
                "subscription.created",
                "invoice.paid"
            ],
            "consecutive_failures": 0,
            "last_success_at": "2026-05-20T09:00:00+00:00",
            "last_failure_at": null,
            "signing_secret_grace_ends_at": null,
            "created_at": "2026-05-01T00:00:00+00:00",
            "updated_at": "2026-05-20T09:00:00+00:00"
        }
    ]
}
 

Example response (403, Forbidden — API key lacks the required scope `webhooks:read`.):


{
    "error": {
        "type": "insufficient_scope",
        "message": "API key lacks required scope: webhooks:read",
        "doc_url": "https://proxyon.teknoza.be/docs#insufficient_scope"
    }
}
 

Request      

GET api/v1/webhook-endpoints

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

Retrieve a webhook endpoint

requires authentication

Returns a single webhook endpoint, including its current delivery status and last-success / failure counters.

Example request:
curl --request GET \
    --get "https://proxyon.teknoza.be/api/v1/webhook-endpoints/16" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://proxyon.teknoza.be/api/v1/webhook-endpoints/16"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "GET",
    headers,
}).then(response => response.json());

Example response (200):


{
    "object": "webhook_endpoint",
    "id": 1,
    "url": "https://example.com/webhooks/bfc53181-d647-36b2-9080-f9c2b76006f4",
    "description": "Qui commodi incidunt iure odit.",
    "status": "active",
    "event_types": [],
    "consecutive_failures": 0,
    "last_success_at": null,
    "last_failure_at": null,
    "signing_secret_grace_ends_at": null,
    "created_at": "2026-06-19T07:30:20+00:00",
    "updated_at": "2026-06-19T07:30:20+00:00"
}
 

Example response (403, Forbidden — API key lacks the required scope `webhooks:read`.):


{
    "error": {
        "type": "insufficient_scope",
        "message": "API key lacks required scope: webhooks:read",
        "doc_url": "https://proxyon.teknoza.be/docs#insufficient_scope"
    }
}
 

Example response (404, Not Found — the referenced resource does not exist for this project.):


{
    "error": {
        "type": "not_found",
        "message": "The requested resource was not found.",
        "doc_url": "https://proxyon.teknoza.be/docs#not_found"
    }
}
 

Request      

GET api/v1/webhook-endpoints/{id}

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

id   integer     

The webhook endpoint id. Example: 16

Create a webhook endpoint

requires authentication

Registers a new HTTPS URL to receive Proxyon webhook deliveries. The response contains a one-time-visible signing secret — store it securely on your server and use it to verify the Proxyon-Signature header on every delivery.

Example request:
curl --request POST \
    "https://proxyon.teknoza.be/api/v1/webhook-endpoints" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Idempotency-Key: idem_01JXY..." \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"url\": \"https:\\/\\/app.example.com\\/webhooks\\/proxyon\",
    \"event_types\": [
        \"subscription.created\",
        \"invoice.paid\"
    ],
    \"description\": \"Production receiver\"
}"
const url = new URL(
    "https://proxyon.teknoza.be/api/v1/webhook-endpoints"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Idempotency-Key": "idem_01JXY...",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "url": "https:\/\/app.example.com\/webhooks\/proxyon",
    "event_types": [
        "subscription.created",
        "invoice.paid"
    ],
    "description": "Production receiver"
};

fetch(url, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (201, Webhook endpoint created. `signing_secret` is exposed **once** — store it securely; subsequent reads of this endpoint will omit it.):


{
    "object": "webhook_endpoint",
    "id": 1,
    "url": "https://app.example.com/webhooks/proxyon",
    "description": "Production receiver",
    "status": "active",
    "event_types": [
        "subscription.created",
        "invoice.paid"
    ],
    "consecutive_failures": 0,
    "last_success_at": null,
    "last_failure_at": null,
    "signing_secret": "whsec_01JXY7Z3K2M5N6P8Q9R0S1T2U3",
    "signing_secret_grace_ends_at": null,
    "created_at": "2026-05-20T10:00:00+00:00",
    "updated_at": "2026-05-20T10:00:00+00:00"
}
 

Example response (403, Forbidden — API key lacks the required scope `webhooks:write`.):


{
    "error": {
        "type": "insufficient_scope",
        "message": "API key lacks required scope: webhooks:write",
        "doc_url": "https://proxyon.teknoza.be/docs#insufficient_scope"
    }
}
 

Request      

POST api/v1/webhook-endpoints

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Idempotency-Key        

Example: idem_01JXY...

Content-Type        

Example: application/json

Accept        

Example: application/json

Body Parameters

url   string     

HTTPS URL to receive webhook deliveries. Example: https://app.example.com/webhooks/proxyon

event_types   string[]     

List of webhook event types to subscribe to.

description   string  optional    

Optional description for this endpoint. Example: Production receiver

Update a webhook endpoint

requires authentication

Updates the URL, description, subscribed events or active status of a webhook endpoint. Setting status="disabled" pauses deliveries without losing the signing secret.

Example request:
curl --request PATCH \
    "https://proxyon.teknoza.be/api/v1/webhook-endpoints/16" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Idempotency-Key: idem_01JXY..." \
    --header "Content-Type: application/json" \
    --header "Accept: application/json" \
    --data "{
    \"url\": \"http:\\/\\/www.bailey.biz\\/quos-velit-et-fugiat-sunt-nihil-accusantium-harum.html\",
    \"event_types\": [
        \"architecto\"
    ],
    \"description\": \"Eius et animi quos velit et.\",
    \"status\": \"architecto\"
}"
const url = new URL(
    "https://proxyon.teknoza.be/api/v1/webhook-endpoints/16"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Idempotency-Key": "idem_01JXY...",
    "Content-Type": "application/json",
    "Accept": "application/json",
};

let body = {
    "url": "http:\/\/www.bailey.biz\/quos-velit-et-fugiat-sunt-nihil-accusantium-harum.html",
    "event_types": [
        "architecto"
    ],
    "description": "Eius et animi quos velit et.",
    "status": "architecto"
};

fetch(url, {
    method: "PATCH",
    headers,
    body: JSON.stringify(body),
}).then(response => response.json());

Example response (200):


{
    "object": "webhook_endpoint",
    "id": 2,
    "url": "https://example.com/webhooks/a4855dc5-0acb-33c3-b921-f4291f719ca0",
    "description": null,
    "status": "active",
    "event_types": [],
    "consecutive_failures": 0,
    "last_success_at": null,
    "last_failure_at": null,
    "signing_secret_grace_ends_at": null,
    "created_at": "2026-06-19T07:30:20+00:00",
    "updated_at": "2026-06-19T07:30:20+00:00"
}
 

Example response (403, Forbidden — API key lacks the required scope `webhooks:write`.):


{
    "error": {
        "type": "insufficient_scope",
        "message": "API key lacks required scope: webhooks:write",
        "doc_url": "https://proxyon.teknoza.be/docs#insufficient_scope"
    }
}
 

Example response (404, Not Found — the referenced resource does not exist for this project.):


{
    "error": {
        "type": "not_found",
        "message": "The requested resource was not found.",
        "doc_url": "https://proxyon.teknoza.be/docs#not_found"
    }
}
 

Request      

PATCH api/v1/webhook-endpoints/{id}

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Idempotency-Key        

Example: idem_01JXY...

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

id   integer     

The webhook endpoint id. Example: 16

Body Parameters

url   string  optional    

New HTTPS URL. Example: http://www.bailey.biz/quos-velit-et-fugiat-sunt-nihil-accusantium-harum.html

event_types   string[]  optional    

Replace the full set of subscribed event types.

description   string  optional    

New description. Example: Eius et animi quos velit et.

status   string  optional    

New status: "active" or "disabled". Example: architecto

Delete a webhook endpoint

requires authentication

Permanently removes a webhook endpoint. In-flight deliveries are aborted; pending retries are discarded.

Example request:
curl --request DELETE \
    "https://proxyon.teknoza.be/api/v1/webhook-endpoints/16" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Idempotency-Key: idem_01JXY..." \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://proxyon.teknoza.be/api/v1/webhook-endpoints/16"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Idempotency-Key": "idem_01JXY...",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "DELETE",
    headers,
}).then(response => response.json());

Example response (204, Webhook endpoint deleted.):

Empty response
 

Example response (403, Forbidden — API key lacks the required scope `webhooks:write`.):


{
    "error": {
        "type": "insufficient_scope",
        "message": "API key lacks required scope: webhooks:write",
        "doc_url": "https://proxyon.teknoza.be/docs#insufficient_scope"
    }
}
 

Example response (404, Not Found — the referenced resource does not exist for this project.):


{
    "error": {
        "type": "not_found",
        "message": "The requested resource was not found.",
        "doc_url": "https://proxyon.teknoza.be/docs#not_found"
    }
}
 

Request      

DELETE api/v1/webhook-endpoints/{id}

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Idempotency-Key        

Example: idem_01JXY...

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

id   integer     

The webhook endpoint id. Example: 16

Rotate the signing secret

requires authentication

Generates a new signing secret for the webhook endpoint and returns it in the response (one-time view). The previous secret continues to sign deliveries for a 24-hour grace period so you can roll out the new one without dropping events.

Example request:
curl --request POST \
    "https://proxyon.teknoza.be/api/v1/webhook-endpoints/16/rotate" \
    --header "Authorization: Bearer {YOUR_AUTH_KEY}" \
    --header "Idempotency-Key: idem_01JXY..." \
    --header "Content-Type: application/json" \
    --header "Accept: application/json"
const url = new URL(
    "https://proxyon.teknoza.be/api/v1/webhook-endpoints/16/rotate"
);

const headers = {
    "Authorization": "Bearer {YOUR_AUTH_KEY}",
    "Idempotency-Key": "idem_01JXY...",
    "Content-Type": "application/json",
    "Accept": "application/json",
};


fetch(url, {
    method: "POST",
    headers,
}).then(response => response.json());

Example response (200, Rotated. The new `signing_secret` is returned **once** in plaintext. The previous secret remains valid for a grace window (see `signing_secret_grace_ends_at`).):


{
    "object": "webhook_endpoint",
    "id": 1,
    "url": "https://app.example.com/webhooks/proxyon",
    "description": "Production receiver",
    "status": "active",
    "event_types": [
        "subscription.created",
        "invoice.paid"
    ],
    "consecutive_failures": 0,
    "last_success_at": "2026-05-20T09:00:00+00:00",
    "last_failure_at": null,
    "signing_secret": "whsec_NEW01JXY7Z3K2M5N6P8Q9R0S1T2",
    "signing_secret_grace_ends_at": "2026-05-21T10:00:00+00:00",
    "created_at": "2026-05-01T00:00:00+00:00",
    "updated_at": "2026-05-20T10:00:00+00:00"
}
 

Example response (403, Forbidden — API key lacks the required scope `webhooks:write`.):


{
    "error": {
        "type": "insufficient_scope",
        "message": "API key lacks required scope: webhooks:write",
        "doc_url": "https://proxyon.teknoza.be/docs#insufficient_scope"
    }
}
 

Example response (404, Not Found — the referenced resource does not exist for this project.):


{
    "error": {
        "type": "not_found",
        "message": "The requested resource was not found.",
        "doc_url": "https://proxyon.teknoza.be/docs#not_found"
    }
}
 

Request      

POST api/v1/webhook-endpoints/{id}/rotate

Headers

Authorization        

Example: Bearer {YOUR_AUTH_KEY}

Idempotency-Key        

Example: idem_01JXY...

Content-Type        

Example: application/json

Accept        

Example: application/json

URL Parameters

id   integer     

The webhook endpoint id. Example: 16