Socio API Authorization Pruebas de IA API Portales de revendedor
Un plan de prueba práctico para evitar el acceso entre inquilinos en las API de socios que crean clientes, emiten claves, agregan créditos, exportan uso y congelan el acceso.
A Partner API for reseller portals has to assume that every object ID in a request can be copied, guessed, replayed, logged by another integration, or passed through the wrong staff tool. The core problem is not whether customer IDs are opaque. The problem is whether every action proves that the caller, partner account, target tenant, object, property, and approval state belong together.
This matters most in an AI API reseller portal because the API usually exposes high-impact control-plane objects: downstream customers, virtual API keys, wallets, credit adjustments, usage exports, webhook destinations, staff users, and emergency freezes. A single missing authorization check can let one customer read another customer’s usage, let a staff token move credits across tenants, or let an automation token revoke a key it should never see.
Fact: OWASP API Security 2023 lists Broken Object Level Authorization as API1 and Broken Object Property Level Authorization as API3. In practical terms, object-level checks answer “can this caller act on this object?” while property-level checks answer “can this caller set or read this field?” Both are first-class concerns for Partner API design.
Define the Authorization Contract Before Writing Tests
Authorization tests are only useful if they test against an explicit policy contract. Start with a matrix that describes which actor can do which action on which target, under which conditions. Do not rely on endpoint names or UI roles as the policy.
A useful matrix has these columns:
- Actor type: partner owner, partner admin, finance operator, support operator, automation token, customer admin, customer read-only user.
- Partner account: the reseller or agency account that owns the downstream customer relationship.
- Target tenant: the downstream customer account being created, billed, inspected, suspended, or deleted.
- Object type: customer, virtual key, wallet, ledger entry, usage report, webhook endpoint, staff user, pricing profile, provider mapping.
- Action: create, read, list, update, revoke, top up, refund, export, freeze, unfreeze.
- Monetary authority: whether the action changes balance, credit limit, margin, settlement status, invoice state, or spend caps.
- Approval state: whether the request is pre-approved, awaiting approval, dual-approved, expired, or emergency-only.
For example, a support operator might be allowed to view a customer’s masked key metadata and freeze a key during an incident, but not create credits, change the pricing catalog, export full usage data, or unfreeze the key without approval. A finance operator might create a credit adjustment for a customer owned by the same partner, but only within a configured amount and only after an approval record exists.
Recommendation: write the matrix as a versioned artifact, then make every authorization test name reference a row or rule. When a policy changes, the diff should show both the contract change and the test change.
Use Tenant-Bound Object References
Opaque IDs reduce enumeration risk, but they do not prove authorization. A virtual key ID, wallet ID, ledger entry ID, usage export ID, or cursor token must be resolved inside the expected partner and tenant boundary.
A safe lookup pattern is:
resolve object where
object.id = request.object_id
and object.partner_id = actor.partner_id
and object.customer_id = request.customer_id
and actor has action_scope for object_type/action
and object.state allows action
The dangerous pattern is:
object = find_by_id(request.object_id)
if actor.has_scope("keys:revoke"):
revoke(object)
The second version authorizes possession of a scope, not authority over the target. It fails when a valid token for Partner A submits a key ID from Partner B, or when an operator for Customer A submits a wallet ID from Customer B.
Tenant binding should cover more than the obvious objects. Include cursor tokens for usage reports, export job IDs, webhook endpoint IDs, approval IDs, idempotency keys, and ledger adjustment IDs. These objects often feel “internal,” which is exactly why they become weak points.
Build a Minimal Cross-Tenant Test Fixture
Use a deliberately small fixture that makes wrong access easy to express. The goal is not broad data volume. The goal is clear separation.
Partner P1
Staff: p1_owner, p1_support, p1_finance
Token: p1_full_token, p1_support_token, p1_finance_token
Customer C1A
Key K1A
Wallet W1A
Usage cursor U1A
Ledger entry L1A
Customer C1B
Key K1B
Wallet W1B
Usage cursor U1B
Ledger entry L1B
Partner P2
Staff: p2_owner
Token: p2_full_token
Customer C2A
Key K2A
Wallet W2A
Usage cursor U2A
Ledger entry L2A
This fixture lets you test two failure classes. First, same-partner cross-customer access, where a caller from P1 tries to touch C1B while acting on C1A. Second, cross-partner access, where a P1 token tries to touch P2’s objects.
Recommendation: run the same fixture against the public Partner API and the reseller portal backend. UI restrictions do not protect direct API clients, background jobs, browser devtools, or compromised integration tokens.
Test Object-Level Authorization on Every High-Impact Endpoint
For every endpoint that accepts an object ID, create at least one positive test and several negative tests. Positive tests prove the fixture is valid. Negative tests prove the boundary holds.
Customer creation and lookup
Customer creation usually looks harmless because the target tenant does not exist yet. It still needs authorization because the caller may try to create customers under a different partner, assign a forbidden pricing profile, or set internal metadata.
Test cases:
- A partner owner can create a customer under the same partner.
- An automation token with customer-provisioning scope can create a customer under the same partner.
- A support-only token cannot create a customer.
- A P1 token cannot create a customer with
partner_id=P2. - A customer-level token cannot list sibling customers.
Key creation, revocation, and rotation
Key lifecycle actions are high impact because they grant runtime access and can interrupt production workloads. Provider control planes commonly expose administrative objects such as projects, workspaces, service accounts, members, and API keys through APIs. A gateway or reseller layer should treat its own virtual keys with the same care.
Test cases:
- P1 automation can create K1A only for C1A when its token includes key-write scope.
- P1 support cannot create K1A if the support role is read-only for keys.
- P1 support can freeze K1A only if emergency-freeze is in scope.
- P1 support cannot revoke K1B when the request context says C1A.
- P1 full token cannot revoke K2A.
- A customer admin for C1A cannot rotate, reveal, or revoke K1B.
For teams implementing Partner API automation, the important test is not just “does this token have key-write?” It is “does this token have key-write for this partner, this customer, this key, this state, and this operation?”
Credit top-ups and wallet changes
Money-moving actions need stricter tests than ordinary updates. A credit top-up can be correct syntactically and still be unauthorized because the wallet belongs to another tenant, the amount exceeds authority, the approval record targets another customer, or the idempotency key was first used for a different operation.
Test cases:
- P1 finance can top up W1A within its amount limit after approval.
- P1 finance cannot top up W1B using an approval created for W1A.
- P1 finance cannot top up W2A.
- P1 support cannot top up any wallet.
- An automation token cannot change credit limit unless it has explicit money-moving scope.
- A replayed idempotency key cannot redirect a prior W1A top-up to W1B.
Recommendation: treat idempotency keys as scoped to partner plus operation plus target tenant plus normalized request body. A retry should be safe, but it should not become a cross-tenant collision channel.
Usage reports and export cursors
Usage reports are easy to under-protect because they are read-only. In reseller systems, usage data can reveal customer names, workloads, model choices, costs, end-user identifiers, and commercial margins. Cursor tokens and export IDs must be tenant-bound too.
Test cases:
- C1A admin can read C1A usage buckets.
- C1A admin cannot read C1B usage by changing
customer_id. - P1 read-only analytics token can export usage for P1 customers only if export scope is present.
- P1 token cannot resume a P2 usage export with a copied cursor.
- A cursor created for a key-level report cannot be reused to fetch tenant-wide usage unless explicitly allowed.
If the portal exposes AI API customer usage metering, test both the summary views and the downloadable exports. Export paths often bypass the same controller or policy code used by interactive pages.
Test Property-Level Authorization on PATCH and Update Endpoints
Broken Object Property Level Authorization appears when a caller is allowed to update an object but can also set fields that should be internal, privileged, or owned by another workflow.
For Partner APIs, sensitive fields often include:
partner_id,customer_id, and parent ownership fields.provider_project_id,provider_workspace_id, and upstream key references.pricing_catalog_version,margin_tier, and discount overrides.wallet_balance,credit_limit,settlement_status, and invoice state.fraud_status,risk_score, and compliance flags.role,scopes, and approval bypass flags.
Use allowlists, not denylists. For example, a customer profile update endpoint might allow display_name, billing_email, and metadata.external_reference. It should reject or ignore margin_tier, credit_limit, and provider_project_id, even if the caller can update the customer object.
PATCH /partner/customers/C1A
{
"display_name": "Acme Support Team",
"margin_tier": "internal_zero_margin",
"provider_project_id": "provider-project-owned-by-C1B"
}
The expected result should be deterministic. Prefer a validation error that identifies disallowed fields without revealing sensitive internal values. Do not silently accept privileged fields and then ignore them unless your API contract already uses that pattern consistently.
Make Denials Non-Enumerating
Negative tests should check both status codes and response shape. If a P1 token requests K2A, the response should not reveal whether K2A exists under another partner.
A common rule is:
- Use
401when the caller is not authenticated. - Use
403when the caller is authenticated but lacks an action scope in its own boundary. - Use
404or a generic not-found response when the object is outside the caller’s boundary and existence should not be revealed.
The exact convention can vary, but the behavior must be consistent. The denial body should avoid messages such as “key belongs to another customer” or “wallet exists under partner P2.” That information is useful to attackers and rarely useful to legitimate clients.
Trade-off: generic denials make debugging harder for partner developers. Solve that with request IDs, structured audit logs, and support tooling, not by leaking cross-tenant existence in public API errors.
Log the Authorization Decision, Not Sensitive Payloads
Authorization tests should also assert that denials produce useful internal evidence. You need enough detail to reconstruct what happened during an incident without storing raw prompts, secrets, or unnecessary customer data.
Log fields should include:
request_idand trace correlation ID.actor_id, actor type, token ID, and role template.partner_idfrom the authenticated actor.- Requested target tenant and resolved target tenant, when resolution is allowed.
- Object type and action.
- Policy version and decision result.
- Denial reason code, such as
missing_scope,tenant_mismatch,approval_required, orfield_not_allowed.
Do not log full API keys, upstream provider secrets, prompts, raw usage payloads, or full export contents. For denied cross-tenant requests, be careful that internal logs are access-controlled; the public response can be generic while the security team still has enough evidence.
Automate the Harness in CI and Staging
The test harness should run in three places: unit tests for policy functions, integration tests for API endpoints, and staging probes against the deployed reseller portal backend. Each layer catches different failures.
A practical CI structure:
authz-tests/
fixtures/
partners.json
customers.json
roles.json
tokens.json
matrix/
keys.yaml
wallets.yaml
usage_exports.yaml
customers.yaml
tests/
test_keys_authz.ts
test_wallet_authz.ts
test_usage_authz.ts
test_patch_allowlists.ts
Each matrix row should define the actor, request, expected decision, and expected non-enumerating behavior.
- name: p1_support_cannot_revoke_p2_key
actor: p1_support_token
method: POST
path: /partner/customers/C2A/keys/K2A/revoke
expected_status: 404
expected_error_code: not_found
expected_audit_reason: tenant_mismatch
Add mutation-style variants that swap one identifier at a time: customer ID, key ID, wallet ID, approval ID, cursor, idempotency key, and webhook endpoint ID. These tests are repetitive by design. Repetition is acceptable when it proves that every identifier is independently scoped.
Operational Checklist
Before shipping a Partner API endpoint, use this checklist:
- The endpoint has a named authorization rule in the policy matrix.
- Every object lookup includes partner and tenant boundary conditions.
- Every list endpoint filters by actor boundary before pagination.
- Every cursor, export ID, webhook ID, approval ID, and idempotency key is scoped.
- PATCH endpoints use property allowlists by role and action.
- Money-moving actions require explicit scope and approval state.
- Emergency freeze is separated from unfreeze and credit authority.
- Denials avoid cross-tenant existence leaks.
- Audit logs record the authorization decision and policy version.
- The same negative tests run against API and portal backend paths.
Trade-Offs to Decide Explicitly
Fine-grained scopes reduce blast radius, but they increase partner onboarding complexity. Default role templates help: owner, finance, support, analytics, provisioning automation, and emergency response. Let advanced partners customize from those templates instead of starting from raw scopes.
Strict property allowlists slow down feature rollout because every new editable field needs a policy change. That friction is useful for billing, pricing, provider mapping, and key-management fields. For low-risk profile fields, the process can be lighter.
Approval flows reduce accidental or malicious credit changes, but they add latency to legitimate support operations. Keep emergency freeze fast, reversible, and separately scoped. Do not let the same emergency token add credits, change margins, export usage, and unfreeze accounts.
Prediction: reseller portals will keep exposing more administrative actions through Partner APIs because customers expect self-service provisioning, reporting, and automation. The systems that age well will be the ones with authorization contracts and negative tests, not the ones that rely on route naming, hidden UI controls, or opaque IDs alone.
Actionable Conclusion
Build the authorization harness before the Partner API becomes large enough that every endpoint has its own exception. Start with two partners, two customers under one partner, one customer under another partner, and a small set of tokens. Then test the actions that can hurt you most: key creation, key revocation, credit top-ups, usage exports, wallet updates, approval reuse, idempotency replay, and PATCH fields.
The contract is simple to state: a caller must have the right scope, under the right partner, for the right tenant, object, property, action, amount, and approval state. The implementation work is making that true on every endpoint, including the boring ones.