Skip to content

Payment methods ​

5 endpoints. Every path below is relative to the API base URL and already includes the /api/v1 prefix — see conventions for authentication, pagination, errors and idempotency.

Endpoint
GET/billing/payment-methodsList the organization's usable saved cards, default card first
POST/billing/payment-methodsMirror a Stripe payment method locally once its SetupIntent succeeded, optionally as the default
DELETE/billing/payment-methods/{paymentMethod}Detach a saved card from Stripe and mark the local record removed
POST/billing/payment-methods/{paymentMethod}/defaultMake a saved card the organization's default for auto reload and off-session charges
POST/billing/payment-methods/setup-intentCreate a Stripe SetupIntent and return the client secret the browser needs to collect a card

Endpoints ​

List the organization's usable saved cards, default card first ​

GET/billing/payment-methods
  • Requires billing.read.
  • Unpaginated, and removed or unusable cards are filtered out
bash
curl -X GET "$CALLAGENT_API_URL/billing/payment-methods" \
  -H "Authorization: Bearer $CALLAGENT_API_KEY" \
  -H "Accept: application/json"
js
const response = await fetch(`${process.env.CALLAGENT_API_URL}/billing/payment-methods`, {
  method: 'GET',
  headers: {
    Authorization: `Bearer ${process.env.CALLAGENT_API_KEY}`,
    Accept: 'application/json',
  },
});

if (!response.ok) {
  const { error } = await response.json();
  throw new Error(`${error.type}: ${error.message}`);
}

const { data } = await response.json();
php
<?php

$handle = curl_init(getenv('CALLAGENT_API_URL') . '/billing/payment-methods');

curl_setopt_array($handle, [
    CURLOPT_CUSTOMREQUEST => 'GET',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('CALLAGENT_API_KEY'),
        'Accept: application/json',
    ],
]);

$body = json_decode(curl_exec($handle), true);
$status = curl_getinfo($handle, CURLINFO_HTTP_CODE);
curl_close($handle);

if ($status >= 400) {
    throw new RuntimeException("{$body['error']['type']}: {$body['error']['message']}");
}

$data = $body['data'];
python
import os, requests

response = requests.get(
    f"{os.environ['CALLAGENT_API_URL']}/billing/payment-methods",
    headers={"Authorization": f"Bearer {os.environ['CALLAGENT_API_KEY']}"},
    timeout=30,
)
response.raise_for_status()
data = response.json()["data"]

Responses

StatusBody
200data-wrapped array

Errors

StatusWhen it happens
403Neither the API key scope nor the session user's permission allows billing.read

Example response, generated from the schema:

json
{
  "data": [
    {
      "id": null,
      "type": "string",
      "brand": "string",
      "last4": "string",
      "exp_month": 1,
      "exp_year": 1,
      "country": "string",
      "is_default": true,
      "expired": true,
      "status": "string",
      "display": "string"
    }
  ]
}

Mirror a Stripe payment method locally once its SetupIntent succeeded, optionally as the default ​

POST/billing/payment-methods
  • Requires billing.write.
  • payment_method_id is Stripe's pm_… token from the browser, not a CallAgent id; re-posting the same token updates the existing row

Request body

FieldTypeRequiredNotes
payment_method_idstringyesmax 191 chars
make_defaultboolean | null—
bash
curl -X POST "$CALLAGENT_API_URL/billing/payment-methods" \
  -H "Authorization: Bearer $CALLAGENT_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{
       "payment_method_id": "01JQ0000000000000000000000",
       "make_default": true
     }'
js
const response = await fetch(`${process.env.CALLAGENT_API_URL}/billing/payment-methods`, {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.CALLAGENT_API_KEY}`,
    Accept: 'application/json',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "payment_method_id": "01JQ0000000000000000000000",
    "make_default": true
  }),
});

if (!response.ok) {
  const { error } = await response.json();
  throw new Error(`${error.type}: ${error.message}`);
}

const { data } = await response.json();
php
<?php

$handle = curl_init(getenv('CALLAGENT_API_URL') . '/billing/payment-methods');

curl_setopt_array($handle, [
    CURLOPT_CUSTOMREQUEST => 'POST',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('CALLAGENT_API_KEY'),
        'Accept: application/json',
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode([
    'payment_method_id' => '01JQ0000000000000000000000',
    'make_default' => true,
]),
]);

$body = json_decode(curl_exec($handle), true);
$status = curl_getinfo($handle, CURLINFO_HTTP_CODE);
curl_close($handle);

if ($status >= 400) {
    throw new RuntimeException("{$body['error']['type']}: {$body['error']['message']}");
}

$data = $body['data'];
python
import os, requests

response = requests.post(
    f"{os.environ['CALLAGENT_API_URL']}/billing/payment-methods",
    headers={"Authorization": f"Bearer {os.environ['CALLAGENT_API_KEY']}"},
    json={
      "payment_method_id": "01JQ0000000000000000000000",
      "make_default": True
    },
    timeout=30,
)
response.raise_for_status()
data = response.json()["data"]

Responses

StatusBody
201data-wrapped object

Errors

StatusWhen it happens
403Neither the API key scope nor the session user's permission allows billing.write
422payment_method_id is missing or too long
503Stripe is not configured or unreachable

Example response, generated from the schema:

json
{
  "data": {
    "id": null,
    "type": "string",
    "brand": "string",
    "last4": "string",
    "exp_month": 1,
    "exp_year": 1,
    "country": "string",
    "is_default": true,
    "expired": true,
    "status": "string",
    "display": "string"
  }
}

Detach a saved card from Stripe and mark the local record removed ​

DELETE/billing/payment-methods/{paymentMethod}
  • Requires billing.write.
  • This response is not data-wrapped.
  • A card already detached at Stripe is still removed locally. Unlike the other card endpoints this one does not convert a missing Stripe configuration into a 503.

Path parameters

NameTypeRequiredNotes
paymentMethodstringyes
bash
curl -X DELETE "$CALLAGENT_API_URL/billing/payment-methods/01JQ0000000000000000000000" \
  -H "Authorization: Bearer $CALLAGENT_API_KEY" \
  -H "Accept: application/json"
js
const response = await fetch(`${process.env.CALLAGENT_API_URL}/billing/payment-methods/01JQ0000000000000000000000`, {
  method: 'DELETE',
  headers: {
    Authorization: `Bearer ${process.env.CALLAGENT_API_KEY}`,
    Accept: 'application/json',
  },
});

if (!response.ok) {
  const { error } = await response.json();
  throw new Error(`${error.type}: ${error.message}`);
}

const { data } = await response.json();
php
<?php

$handle = curl_init(getenv('CALLAGENT_API_URL') . '/billing/payment-methods/01JQ0000000000000000000000');

curl_setopt_array($handle, [
    CURLOPT_CUSTOMREQUEST => 'DELETE',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('CALLAGENT_API_KEY'),
        'Accept: application/json',
    ],
]);

$body = json_decode(curl_exec($handle), true);
$status = curl_getinfo($handle, CURLINFO_HTTP_CODE);
curl_close($handle);

if ($status >= 400) {
    throw new RuntimeException("{$body['error']['type']}: {$body['error']['message']}");
}

$data = $body['data'];
python
import os, requests

response = requests.delete(
    f"{os.environ['CALLAGENT_API_URL']}/billing/payment-methods/01JQ0000000000000000000000",
    headers={"Authorization": f"Bearer {os.environ['CALLAGENT_API_KEY']}"},
    timeout=30,
)
response.raise_for_status()
data = response.json()["data"]

Responses

StatusBody
204No content

Errors

StatusWhen it happens
403Neither the API key scope nor the session user's permission allows billing.write
404No payment method with that id in this organization

Make a saved card the organization's default for auto reload and off-session charges ​

POST/billing/payment-methods/{paymentMethod}/default
  • Requires billing.write.
  • Purely local: it clears the flag on the other cards and updates the billing account, and makes no Stripe call

Path parameters

NameTypeRequiredNotes
paymentMethodstringyes
bash
curl -X POST "$CALLAGENT_API_URL/billing/payment-methods/01JQ0000000000000000000000/default" \
  -H "Authorization: Bearer $CALLAGENT_API_KEY" \
  -H "Accept: application/json"
js
const response = await fetch(`${process.env.CALLAGENT_API_URL}/billing/payment-methods/01JQ0000000000000000000000/default`, {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.CALLAGENT_API_KEY}`,
    Accept: 'application/json',
  },
});

if (!response.ok) {
  const { error } = await response.json();
  throw new Error(`${error.type}: ${error.message}`);
}

const { data } = await response.json();
php
<?php

$handle = curl_init(getenv('CALLAGENT_API_URL') . '/billing/payment-methods/01JQ0000000000000000000000/default');

curl_setopt_array($handle, [
    CURLOPT_CUSTOMREQUEST => 'POST',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('CALLAGENT_API_KEY'),
        'Accept: application/json',
    ],
]);

$body = json_decode(curl_exec($handle), true);
$status = curl_getinfo($handle, CURLINFO_HTTP_CODE);
curl_close($handle);

if ($status >= 400) {
    throw new RuntimeException("{$body['error']['type']}: {$body['error']['message']}");
}

$data = $body['data'];
python
import os, requests

response = requests.post(
    f"{os.environ['CALLAGENT_API_URL']}/billing/payment-methods/01JQ0000000000000000000000/default",
    headers={"Authorization": f"Bearer {os.environ['CALLAGENT_API_KEY']}"},
    timeout=30,
)
response.raise_for_status()
data = response.json()["data"]

Responses

StatusBody
200data-wrapped object

Errors

StatusWhen it happens
403Neither the API key scope nor the session user's permission allows billing.write
404No usable payment method with that id in this organization

Example response, generated from the schema:

json
{
  "data": {
    "id": null,
    "type": "string",
    "brand": "string",
    "last4": "string",
    "exp_month": 1,
    "exp_year": 1,
    "country": "string",
    "is_default": true,
    "expired": true,
    "status": "string",
    "display": "string"
  }
}

Create a Stripe SetupIntent and return the client secret the browser needs to collect a card ​

POST/billing/payment-methods/setup-intent
  • Requires billing.write.
  • Returns 200 rather than 201, and pins the intent to card payment methods with off-session usage so auto reload can charge it later
bash
curl -X POST "$CALLAGENT_API_URL/billing/payment-methods/setup-intent" \
  -H "Authorization: Bearer $CALLAGENT_API_KEY" \
  -H "Accept: application/json"
js
const response = await fetch(`${process.env.CALLAGENT_API_URL}/billing/payment-methods/setup-intent`, {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.CALLAGENT_API_KEY}`,
    Accept: 'application/json',
  },
});

if (!response.ok) {
  const { error } = await response.json();
  throw new Error(`${error.type}: ${error.message}`);
}

const { data } = await response.json();
php
<?php

$handle = curl_init(getenv('CALLAGENT_API_URL') . '/billing/payment-methods/setup-intent');

curl_setopt_array($handle, [
    CURLOPT_CUSTOMREQUEST => 'POST',
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => [
        'Authorization: Bearer ' . getenv('CALLAGENT_API_KEY'),
        'Accept: application/json',
    ],
]);

$body = json_decode(curl_exec($handle), true);
$status = curl_getinfo($handle, CURLINFO_HTTP_CODE);
curl_close($handle);

if ($status >= 400) {
    throw new RuntimeException("{$body['error']['type']}: {$body['error']['message']}");
}

$data = $body['data'];
python
import os, requests

response = requests.post(
    f"{os.environ['CALLAGENT_API_URL']}/billing/payment-methods/setup-intent",
    headers={"Authorization": f"Bearer {os.environ['CALLAGENT_API_KEY']}"},
    timeout=30,
)
response.raise_for_status()
data = response.json()["data"]

Responses

StatusBody
200data-wrapped object

Errors

StatusWhen it happens
403Neither the API key scope nor the session user's permission allows billing.write
503Stripe is not configured or unreachable

Example response, generated from the schema:

json
{
  "data": {
    "client_secret": "string",
    "setup_intent_id": "01JQ0000000000000000000000",
    "customer_id": "01JQ0000000000000000000000"
  }
}

Documented against the code. Anything not yet implemented is labelled, never described as working.