Authentication

Provider Center authenticates every consumer request with an API key issued to an Application. Business endpoints additionally require an X-End-Customer-Id header identifying the end-user tenant on whose behalf the call is made.

This page covers the key format, the required headers, how to obtain and rotate keys, and how end-customer identification ties back into the quota model.

The short version

POST /v1/influencers/search HTTP/1.1
Host: api-providers.fluvip.ai
Authorization: Bearer pk_live_7f2a1c93_8Kj4QmR2wXvP9nLs0tAbE1yUc6HoG5Dz
X-End-Customer-Id: 7a3b5c18-2d4e-4f6a-9b8c-1e3d2f4a5b6c
Content-Type: application/json

{ "query": "beauty creators, Colombia, 10k-100k followers" }

Two headers are mandatory on every business call:

| Header | Required | Purpose | |--------|----------|---------| | Authorization: Bearer <key> | yes | Identifies Organization + Application | | X-End-Customer-Id: <uuid> | yes (on /v1/* business endpoints) | Identifies the caller's tenant for quotas and audit |

Plus these optional headers, documented here for completeness:

| Header | Purpose | |--------|---------| | X-Idempotency-Key: <uuid> | Deduplicate a job submission for 24h | | X-Mode: sync \| job | Force async (job) mode | | X-Prefer-Provider: <slug> | Pin the call to one provider | | X-No-Failover: true | Disable failover on domain contracts | | X-Cache-Policy: bypass \| exact | Force cache miss, or opt into exact-match for non-cacheable endpoints |

API key format

Keys follow the pattern:

pk_live_<prefix8>_<secret40>

Example:

pk_live_7f2a1c93_8Kj4QmR2wXvP9nLs0tAbE1yUc6HoG5Dz
        └─ prefix ─┘ └──────── secret ─────────┘

Never log or commit the secret segment. If it leaks, rotate the key (see below).

Obtaining a key

Keys are created from the admin panel, scoped to an Application:

  1. Sign in to the admin panel with a magic-link email. Your user must have the ADMIN or DEVELOPER role in the Organization.
  2. Navigate to Applications → [your app] → API Keys.
  3. Click Create key. Optionally label it (e.g., production-server, ci-pipeline).
  4. The raw key appears in a one-time reveal dialog. Copy it into your secret store (AWS Secrets Manager, Vault, .env vault, etc.). Closing the dialog destroys the plaintext for good.

Equivalent GraphQL mutation used by the panel:

mutation RotateKey($keyId: ID!) {
  rotateApiKey(apiKeyId: $keyId) {
    id
    prefix        # visible
    secret        # returned once, never again
    expiresAt
  }
}

Sending requests

Minimal curl

curl -X POST https://api-providers.fluvip.ai/v1/influencers/search \
  -H "Authorization: Bearer $PROVIDER_CENTER_KEY" \
  -H "X-End-Customer-Id: $TEAM_ID" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "beauty creators, Colombia",
    "filters": { "followers": { "min": 10000, "max": 100000 } },
    "limit": 25
  }'

Node (fetch)

const res = await fetch("https://api-providers.fluvip.ai/v1/influencers/search", {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${process.env.PROVIDER_CENTER_KEY}`,
    "X-End-Customer-Id": teamId,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ query, filters, limit: 25 }),
});

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

const { data, meta } = await res.json();

Python (httpx)

import httpx, os

r = httpx.post(
    "https://api-providers.fluvip.ai/v1/influencers/search",
    headers={
        "Authorization": f"Bearer {os.environ['PROVIDER_CENTER_KEY']}",
        "X-End-Customer-Id": team_id,
    },
    json={"query": query, "filters": filters, "limit": 25},
    timeout=30,
)
r.raise_for_status()
payload = r.json()

The X-End-Customer-Id header

Provider Center enforces a three-level quota model: Organization → Application → EndCustomer. The first two are resolved from the API key. The third — EndCustomer — must be supplied by the caller on every business request.

An EndCustomer represents a tenant inside the consuming application. For fluvip-api that's a Fluvip team. For lucia-ai that's a workspace. The value you send is typically your own tenant's ID:

X-End-Customer-Id: 7a3b5c18-2d4e-4f6a-9b8c-1e3d2f4a5b6c

Just-in-time creation

You do not need to pre-register EndCustomers. The first request carrying an unknown (applicationId, X-End-Customer-Id) pair auto-creates the record, seeded from the Application's endCustomerQuotaTemplate. From that moment the admin panel shows the EndCustomer and its quota can be tuned independently.

This removes the need for a provisioning step on every new customer your app onboards — sending a consistent, stable ID per tenant is enough.

What counts as a good ID

When is it not required?

Only metadata endpoints are exempt: GET /v1/providers, GET /v1/usage/me, GET /v1/jobs/:id. Every business POST requires it. A request missing X-End-Customer-Id gets:

{
  "error": {
    "code": "INVALID_PARAMS",
    "message": "Missing required header: X-End-Customer-Id",
    "retryable": false,
    "requestId": "req_01HXA..."
  }
}

Key rotation

You should rotate keys:

Rotation flow

Two keys can coexist on the same Application, which is the supported rotation mechanism:

  1. In the admin panel, Applications → [app] → API Keys → Create key. Save the new secret in your secret store.
  2. Deploy your services with the new key. Verify traffic uses it (check the admin panel's Last used timestamp).
  3. Revoke the old key: API Keys → [old key] → Revoke. Revoked keys return 401 UNAUTHORIZED immediately.

Via GraphQL

mutation Rotate($id: ID!) {
  rotateApiKey(apiKeyId: $id) {
    id
    prefix
    secret            # the only time you will see it
  }
}

mutation Revoke($id: ID!) {
  revokeApiKey(apiKeyId: $id) { id revokedAt }
}

Security notes

How authentication maps to quotas

Every request runs through auth first, then quota enforcement. Resolution is:

  1. Authorization: Bearer ... → ApiKey → Application → Organization.
  2. X-End-Customer-Id → EndCustomer (created on first sight).
  3. The three scopes (orgId, applicationId, endCustomerId) feed the atomic Lua quota script described in Concepts → Quotas. All three must pass or the request is rejected with QUOTA_EXCEEDED.

In other words: even a valid API key can fail with 429 QUOTA_EXCEEDED if the EndCustomer — or the parent Application or Organization — has already consumed its window.

Errors you may see

| Code | HTTP | Cause | |------|------|-------| | UNAUTHORIZED | 401 | Missing, malformed, revoked, or expired API key | | INVALID_PARAMS | 400 | Missing X-End-Customer-Id or malformed header | | FORBIDDEN | 403 | Valid key but Application disabled or Organization suspended | | RATE_LIMITED | 429 | Per-key rate limit exceeded (default 100 req/s) | | QUOTA_EXCEEDED | 429 | Credit quota exhausted at the scope identified by error.scope |

See Concepts → Errors for the full envelope and retry guidance.

Next steps