integrations

Base URL https://api.example.com
GET /api/v1/brands/{brand_id}/integrations

List Integration Overview

Overview of all configured providers with aggregated stats for a brand.

Parameters
brand_id integer path required
Responses
  • 200Successful Response
  • 422Validation Error
GET /api/v1/brands/{brand_id}/integrations
curl -X GET 'https://api.example.com/api/v1/brands/{brand_id}/integrations' \
  -H 'Authorization: Bearer '
import httpx

resp = httpx.get(
    "https://api.example.com/api/v1/brands/{brand_id}/integrations",
    headers={"Authorization": "Bearer "},
)
resp.raise_for_status()
print(resp.json())
const resp = await fetch("https://api.example.com/api/v1/brands/{brand_id}/integrations", {
  method: "GET",
  headers: { "Authorization": "Bearer " }
});
const data = await resp.json();
console.log(data);
package main

import (
	"net/http"
)

func main() {
	req, _ := http.NewRequest("GET", "https://api.example.com/api/v1/brands/{brand_id}/integrations", nil)
	req.Header.Set("Authorization", "Bearer ")
	resp, _ := http.DefaultClient.Do(req)
	defer resp.Body.Close()
}
POST /api/v1/brands/{brand_id}/integrations

Create Integration

Create a new API integration key. Brand admin only.

Parameters
brand_id integer path required
Responses
  • 200Successful Response
  • 422Validation Error
POST /api/v1/brands/{brand_id}/integrations
curl -X POST 'https://api.example.com/api/v1/brands/{brand_id}/integrations' \
  -H 'Authorization: Bearer ' \
  -H 'Content-Type: application/json' \
  -d '{
  "provider_name": "string",
  "key_label": "string",
  "credentials": {},
  "daily_limit": 0,
  "minute_limit": 0,
  "is_primary": false,
  "country_code": "string",
  "spend_limit_amount": 0.0,
  "spend_limit_period": "string",
  "spend_limit_action": "string",
  "share_with_pool": false
}'
import httpx

resp = httpx.post(
    "https://api.example.com/api/v1/brands/{brand_id}/integrations",
    headers={"Authorization": "Bearer ", "Content-Type": "application/json"},
    json={"provider_name": "string", "key_label": "string", "credentials": {}, "daily_limit": 0, "minute_limit": 0, "is_primary": false, "country_code": "string", "spend_limit_amount": 0.0, "spend_limit_period": "string", "spend_limit_action": "string", "share_with_pool": false},
)
resp.raise_for_status()
print(resp.json())
const resp = await fetch("https://api.example.com/api/v1/brands/{brand_id}/integrations", {
  method: "POST",
  headers: { "Authorization": "Bearer ", "Content-Type": "application/json" },
  body: JSON.stringify({"provider_name": "string", "key_label": "string", "credentials": {}, "daily_limit": 0, "minute_limit": 0, "is_primary": false, "country_code": "string", "spend_limit_amount": 0.0, "spend_limit_period": "string", "spend_limit_action": "string", "share_with_pool": false})
});
const data = await resp.json();
console.log(data);
package main

import (
	"net/http"
	"strings"
)

func main() {
	body := strings.NewReader(`{"provider_name": "string", "key_label": "string", "credentials": {}, "daily_limit": 0, "minute_limit": 0, "is_primary": false, "country_code": "string", "spend_limit_amount": 0.0, "spend_limit_period": "string", "spend_limit_action": "string", "share_with_pool": false}`)
	req, _ := http.NewRequest("POST", "https://api.example.com/api/v1/brands/{brand_id}/integrations", body)
	req.Header.Set("Authorization", "Bearer ")
	req.Header.Set("Content-Type", "application/json")
	resp, _ := http.DefaultClient.Do(req)
	defer resp.Body.Close()
}
Request body
{"key_label":"string","is_primary":false,"credentials":{},"daily_limit":0,"country_code":"string","minute_limit":0,"provider_name":"string","share_with_pool":false,"spend_limit_action":"string","spend_limit_amount":0,"spend_limit_period":"string"}
GET /api/v1/brands/{brand_id}/integrations/providers

List Provider Templates

List available provider templates with field schemas.

Parameters
brand_id integer path required
Responses
  • 200Successful Response
  • 422Validation Error
GET /api/v1/brands/{brand_id}/integrations/providers
curl -X GET 'https://api.example.com/api/v1/brands/{brand_id}/integrations/providers' \
  -H 'Authorization: Bearer '
import httpx

resp = httpx.get(
    "https://api.example.com/api/v1/brands/{brand_id}/integrations/providers",
    headers={"Authorization": "Bearer "},
)
resp.raise_for_status()
print(resp.json())
const resp = await fetch("https://api.example.com/api/v1/brands/{brand_id}/integrations/providers", {
  method: "GET",
  headers: { "Authorization": "Bearer " }
});
const data = await resp.json();
console.log(data);
package main

import (
	"net/http"
)

func main() {
	req, _ := http.NewRequest("GET", "https://api.example.com/api/v1/brands/{brand_id}/integrations/providers", nil)
	req.Header.Set("Authorization", "Bearer ")
	resp, _ := http.DefaultClient.Do(req)
	defer resp.Body.Close()
}
GET /api/v1/brands/{brand_id}/integrations/all

List Integrations

List all integration keys for a brand, optionally filtered by provider.

Parameters
brand_id integer path required
provider_name any query
Filter by provider name
Responses
  • 200Successful Response
  • 422Validation Error
GET /api/v1/brands/{brand_id}/integrations/all
curl -X GET 'https://api.example.com/api/v1/brands/{brand_id}/integrations/all' \
  -H 'Authorization: Bearer '
import httpx

resp = httpx.get(
    "https://api.example.com/api/v1/brands/{brand_id}/integrations/all",
    headers={"Authorization": "Bearer "},
)
resp.raise_for_status()
print(resp.json())
const resp = await fetch("https://api.example.com/api/v1/brands/{brand_id}/integrations/all", {
  method: "GET",
  headers: { "Authorization": "Bearer " }
});
const data = await resp.json();
console.log(data);
package main

import (
	"net/http"
)

func main() {
	req, _ := http.NewRequest("GET", "https://api.example.com/api/v1/brands/{brand_id}/integrations/all", nil)
	req.Header.Set("Authorization", "Bearer ")
	resp, _ := http.DefaultClient.Do(req)
	defer resp.Body.Close()
}
GET /api/v1/brands/{brand_id}/integrations/{integration_id}

Get Integration

Get a single integration key detail.

Parameters
brand_id integer path required
integration_id integer path required
Responses
  • 200Successful Response
  • 422Validation Error
GET /api/v1/brands/{brand_id}/integrations/{integration_id}
curl -X GET 'https://api.example.com/api/v1/brands/{brand_id}/integrations/{integration_id}' \
  -H 'Authorization: Bearer '
import httpx

resp = httpx.get(
    "https://api.example.com/api/v1/brands/{brand_id}/integrations/{integration_id}",
    headers={"Authorization": "Bearer "},
)
resp.raise_for_status()
print(resp.json())
const resp = await fetch("https://api.example.com/api/v1/brands/{brand_id}/integrations/{integration_id}", {
  method: "GET",
  headers: { "Authorization": "Bearer " }
});
const data = await resp.json();
console.log(data);
package main

import (
	"net/http"
)

func main() {
	req, _ := http.NewRequest("GET", "https://api.example.com/api/v1/brands/{brand_id}/integrations/{integration_id}", nil)
	req.Header.Set("Authorization", "Bearer ")
	resp, _ := http.DefaultClient.Do(req)
	defer resp.Body.Close()
}
PATCH /api/v1/brands/{brand_id}/integrations/{integration_id}

Update Integration

Update an integration key. Brand admin only.

Parameters
brand_id integer path required
integration_id integer path required
Responses
  • 200Successful Response
  • 422Validation Error
PATCH /api/v1/brands/{brand_id}/integrations/{integration_id}
curl -X PATCH 'https://api.example.com/api/v1/brands/{brand_id}/integrations/{integration_id}' \
  -H 'Authorization: Bearer ' \
  -H 'Content-Type: application/json' \
  -d '{
  "key_label": "string",
  "credentials": {},
  "daily_limit": 0,
  "minute_limit": 0,
  "is_primary": true,
  "is_active": true,
  "country_code": "string",
  "spend_limit_amount": 0.0,
  "spend_limit_period": "string",
  "spend_limit_action": "string",
  "share_with_pool": true
}'
import httpx

resp = httpx.patch(
    "https://api.example.com/api/v1/brands/{brand_id}/integrations/{integration_id}",
    headers={"Authorization": "Bearer ", "Content-Type": "application/json"},
    json={"key_label": "string", "credentials": {}, "daily_limit": 0, "minute_limit": 0, "is_primary": true, "is_active": true, "country_code": "string", "spend_limit_amount": 0.0, "spend_limit_period": "string", "spend_limit_action": "string", "share_with_pool": true},
)
resp.raise_for_status()
print(resp.json())
const resp = await fetch("https://api.example.com/api/v1/brands/{brand_id}/integrations/{integration_id}", {
  method: "PATCH",
  headers: { "Authorization": "Bearer ", "Content-Type": "application/json" },
  body: JSON.stringify({"key_label": "string", "credentials": {}, "daily_limit": 0, "minute_limit": 0, "is_primary": true, "is_active": true, "country_code": "string", "spend_limit_amount": 0.0, "spend_limit_period": "string", "spend_limit_action": "string", "share_with_pool": true})
});
const data = await resp.json();
console.log(data);
package main

import (
	"net/http"
	"strings"
)

func main() {
	body := strings.NewReader(`{"key_label": "string", "credentials": {}, "daily_limit": 0, "minute_limit": 0, "is_primary": true, "is_active": true, "country_code": "string", "spend_limit_amount": 0.0, "spend_limit_period": "string", "spend_limit_action": "string", "share_with_pool": true}`)
	req, _ := http.NewRequest("PATCH", "https://api.example.com/api/v1/brands/{brand_id}/integrations/{integration_id}", body)
	req.Header.Set("Authorization", "Bearer ")
	req.Header.Set("Content-Type", "application/json")
	resp, _ := http.DefaultClient.Do(req)
	defer resp.Body.Close()
}
Request body
{"is_active":true,"key_label":"string","is_primary":true,"credentials":{},"daily_limit":0,"country_code":"string","minute_limit":0,"share_with_pool":true,"spend_limit_action":"string","spend_limit_amount":0,"spend_limit_period":"string"}
DELETE /api/v1/brands/{brand_id}/integrations/{integration_id}

Delete Integration

Delete an integration key. Brand admin only.

Parameters
brand_id integer path required
integration_id integer path required
Responses
  • 200Successful Response
  • 422Validation Error
DELETE /api/v1/brands/{brand_id}/integrations/{integration_id}
curl -X DELETE 'https://api.example.com/api/v1/brands/{brand_id}/integrations/{integration_id}' \
  -H 'Authorization: Bearer '
import httpx

resp = httpx.delete(
    "https://api.example.com/api/v1/brands/{brand_id}/integrations/{integration_id}",
    headers={"Authorization": "Bearer "},
)
resp.raise_for_status()
print(resp.json())
const resp = await fetch("https://api.example.com/api/v1/brands/{brand_id}/integrations/{integration_id}", {
  method: "DELETE",
  headers: { "Authorization": "Bearer " }
});
const data = await resp.json();
console.log(data);
package main

import (
	"net/http"
)

func main() {
	req, _ := http.NewRequest("DELETE", "https://api.example.com/api/v1/brands/{brand_id}/integrations/{integration_id}", nil)
	req.Header.Set("Authorization", "Bearer ")
	resp, _ := http.DefaultClient.Do(req)
	defer resp.Body.Close()
}
POST /api/v1/brands/{brand_id}/integrations/{integration_id}/reset-usage

Reset Integration Usage

Reset daily and minute usage counters. Brand admin only.

Parameters
brand_id integer path required
integration_id integer path required
Responses
  • 200Successful Response
  • 422Validation Error
POST /api/v1/brands/{brand_id}/integrations/{integration_id}/reset-usage
curl -X POST 'https://api.example.com/api/v1/brands/{brand_id}/integrations/{integration_id}/reset-usage' \
  -H 'Authorization: Bearer '
import httpx

resp = httpx.post(
    "https://api.example.com/api/v1/brands/{brand_id}/integrations/{integration_id}/reset-usage",
    headers={"Authorization": "Bearer "},
)
resp.raise_for_status()
print(resp.json())
const resp = await fetch("https://api.example.com/api/v1/brands/{brand_id}/integrations/{integration_id}/reset-usage", {
  method: "POST",
  headers: { "Authorization": "Bearer " }
});
const data = await resp.json();
console.log(data);
package main

import (
	"net/http"
)

func main() {
	req, _ := http.NewRequest("POST", "https://api.example.com/api/v1/brands/{brand_id}/integrations/{integration_id}/reset-usage", nil)
	req.Header.Set("Authorization", "Bearer ")
	resp, _ := http.DefaultClient.Do(req)
	defer resp.Body.Close()
}
GET /api/v1/brands/{brand_id}/integrations/spend/provider/{provider_name}

Get Provider Spend Overview

Get aggregated spend overview for a provider.

Parameters
brand_id integer path required
provider_name string path required
Responses
  • 200Successful Response
  • 422Validation Error
GET /api/v1/brands/{brand_id}/integrations/spend/provider/{provider_name}
curl -X GET 'https://api.example.com/api/v1/brands/{brand_id}/integrations/spend/provider/{provider_name}' \
  -H 'Authorization: Bearer '
import httpx

resp = httpx.get(
    "https://api.example.com/api/v1/brands/{brand_id}/integrations/spend/provider/{provider_name}",
    headers={"Authorization": "Bearer "},
)
resp.raise_for_status()
print(resp.json())
const resp = await fetch("https://api.example.com/api/v1/brands/{brand_id}/integrations/spend/provider/{provider_name}", {
  method: "GET",
  headers: { "Authorization": "Bearer " }
});
const data = await resp.json();
console.log(data);
package main

import (
	"net/http"
)

func main() {
	req, _ := http.NewRequest("GET", "https://api.example.com/api/v1/brands/{brand_id}/integrations/spend/provider/{provider_name}", nil)
	req.Header.Set("Authorization", "Bearer ")
	resp, _ := http.DefaultClient.Do(req)
	defer resp.Body.Close()
}
GET /api/v1/brands/{brand_id}/integrations/{integration_id}/spend

Get Integration Spend

Get spend data for a specific integration with history.

Parameters
brand_id integer path required
integration_id integer path required
Responses
  • 200Successful Response
  • 422Validation Error
GET /api/v1/brands/{brand_id}/integrations/{integration_id}/spend
curl -X GET 'https://api.example.com/api/v1/brands/{brand_id}/integrations/{integration_id}/spend' \
  -H 'Authorization: Bearer '
import httpx

resp = httpx.get(
    "https://api.example.com/api/v1/brands/{brand_id}/integrations/{integration_id}/spend",
    headers={"Authorization": "Bearer "},
)
resp.raise_for_status()
print(resp.json())
const resp = await fetch("https://api.example.com/api/v1/brands/{brand_id}/integrations/{integration_id}/spend", {
  method: "GET",
  headers: { "Authorization": "Bearer " }
});
const data = await resp.json();
console.log(data);
package main

import (
	"net/http"
)

func main() {
	req, _ := http.NewRequest("GET", "https://api.example.com/api/v1/brands/{brand_id}/integrations/{integration_id}/spend", nil)
	req.Header.Set("Authorization", "Bearer ")
	resp, _ := http.DefaultClient.Do(req)
	defer resp.Body.Close()
}
POST /api/v1/brands/{brand_id}/integrations/{integration_id}/test

Test Integration Connection

Test if an API key is working by making a lightweight API call.

Parameters
brand_id integer path required
integration_id integer path required
Responses
  • 200Successful Response
  • 422Validation Error
POST /api/v1/brands/{brand_id}/integrations/{integration_id}/test
curl -X POST 'https://api.example.com/api/v1/brands/{brand_id}/integrations/{integration_id}/test' \
  -H 'Authorization: Bearer '
import httpx

resp = httpx.post(
    "https://api.example.com/api/v1/brands/{brand_id}/integrations/{integration_id}/test",
    headers={"Authorization": "Bearer "},
)
resp.raise_for_status()
print(resp.json())
const resp = await fetch("https://api.example.com/api/v1/brands/{brand_id}/integrations/{integration_id}/test", {
  method: "POST",
  headers: { "Authorization": "Bearer " }
});
const data = await resp.json();
console.log(data);
package main

import (
	"net/http"
)

func main() {
	req, _ := http.NewRequest("POST", "https://api.example.com/api/v1/brands/{brand_id}/integrations/{integration_id}/test", nil)
	req.Header.Set("Authorization", "Bearer ")
	resp, _ := http.DefaultClient.Do(req)
	defer resp.Body.Close()
}
POST /api/v1/brands/{brand_id}/integrations/{integration_id}/sync-billing

Sync Integration Billing

Trigger billing sync for an integration using its billing adapter.

Parameters
brand_id integer path required
integration_id integer path required
Responses
  • 200Successful Response
  • 422Validation Error
POST /api/v1/brands/{brand_id}/integrations/{integration_id}/sync-billing
curl -X POST 'https://api.example.com/api/v1/brands/{brand_id}/integrations/{integration_id}/sync-billing' \
  -H 'Authorization: Bearer '
import httpx

resp = httpx.post(
    "https://api.example.com/api/v1/brands/{brand_id}/integrations/{integration_id}/sync-billing",
    headers={"Authorization": "Bearer "},
)
resp.raise_for_status()
print(resp.json())
const resp = await fetch("https://api.example.com/api/v1/brands/{brand_id}/integrations/{integration_id}/sync-billing", {
  method: "POST",
  headers: { "Authorization": "Bearer " }
});
const data = await resp.json();
console.log(data);
package main

import (
	"net/http"
)

func main() {
	req, _ := http.NewRequest("POST", "https://api.example.com/api/v1/brands/{brand_id}/integrations/{integration_id}/sync-billing", nil)
	req.Header.Set("Authorization", "Bearer ")
	resp, _ := http.DefaultClient.Do(req)
	defer resp.Body.Close()
}
POST /api/v1/admin/integrations/rotate-keys

Rotate All Keys

Re-encrypt every active integration's ``credentials_encrypted`` with the newest Fernet key. Used by the key-rotation runbook (docs/services/fastapi/key-rotation.md, shipped in 3.2): after a new CREDENTIALS_ENCRYPTION_KEY is promoted and the old one moved to CREDENTIALS_ENCRYPTION_KEY_PREVIOUS, this drains every row off the retired key so the previous key can eventually be removed. SCOPE (3.2b): rotates the ``credentials_encrypted`` column ONLY. The three OAuth columns (``access_token_encrypted`` / ``refresh_token_encrypted`` / ``id_token_encrypted``) are NOT yet re-encrypted here — see the runbook's "Deferred follow-up". For VayaPin's current providers (google_maps / aftership / twilio, all ``api_key`` auth) those columns are NULL, so a single-column pass fully drains the old key. Do NOT retire CREDENTIALS_ENCRYPTION_KEY_PREVIOUS once any OAuth-auth integration exists until the OAuth columns are covered, or those tokens strand. - Super-admin only (``require_super_admin``). - Keyset pagination over ``id`` (index-friendly, stable under concurrent inserts), committing one batch of 100 per transaction to keep locks short. - Idempotent: rotating a token already on the newest key is a no-op re-encrypt. - Failure-isolated: a row whose token decrypts with no configured key is recorded in ``failed_ids`` and skipped — it never aborts the rest of the run. - No plaintext is logged or returned; ``rotate_credential`` stays ciphertext→ciphertext.

Responses
  • 200Successful Response
POST /api/v1/admin/integrations/rotate-keys
curl -X POST 'https://api.example.com/api/v1/admin/integrations/rotate-keys' \
  -H 'Authorization: Bearer '
import httpx

resp = httpx.post(
    "https://api.example.com/api/v1/admin/integrations/rotate-keys",
    headers={"Authorization": "Bearer "},
)
resp.raise_for_status()
print(resp.json())
const resp = await fetch("https://api.example.com/api/v1/admin/integrations/rotate-keys", {
  method: "POST",
  headers: { "Authorization": "Bearer " }
});
const data = await resp.json();
console.log(data);
package main

import (
	"net/http"
)

func main() {
	req, _ := http.NewRequest("POST", "https://api.example.com/api/v1/admin/integrations/rotate-keys", nil)
	req.Header.Set("Authorization", "Bearer ")
	resp, _ := http.DefaultClient.Do(req)
	defer resp.Body.Close()
}