Skip to content

fix: delete command fails for offers and campaigns — API requires batch body, not path-based DELETE #3

@andresgrandagmg

Description

@andresgrandagmg

Problem

The offers delete and campaigns delete commands silently fail or return unexpected responses because the CLI sends a path-based DELETE /offer/{id} request with no body, while the Voluum API requires a batch delete pattern: DELETE /offer with IDs in the request body.

Current behavior

voluum offers delete --id abc-123
# sends: DELETE /offer/abc-123  (no body)
# Voluum API returns 4xx or unexpected response

voluum campaigns delete --id xyz-456
# sends: DELETE /campaign/xyz-456  (no body)
# Voluum API returns 4xx or unexpected response

What the API actually expects

# DELETE /offer with body {"ids": ["abc-123"]}
curl -X DELETE https://api.voluum.com/offer \
  -H "cwauth-token: $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"ids": ["abc-123"]}'

# DELETE /campaign with body {"ids": ["xyz-456"]}
curl -X DELETE https://api.voluum.com/campaign \
  -H "cwauth-token: $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"ids": ["xyz-456"]}'

Root cause

Three things are wrong in the current implementation:

1. VoluumClient.delete() has no body parameter

// src/client/VoluumClient.ts
async delete<T>(path: string, query?: QueryParams): Promise<T> {
  return this.request<T>("DELETE", path, { query });
  //                                       ↑ no body — cannot send {"ids": [...]}
}

2. deletePath returns a per-resource path instead of the collection path

// src/endpoints.ts
offers: {
  deletePath: (id: string) => `/offer/${encodeURIComponent(id)}`, // ❌ wrong path
},
campaigns: {
  deletePath: (id: string) => `/campaign/${encodeURIComponent(id)}`, // ❌ wrong path
},

3. The delete commands send no body

// src/commands/offers.ts (same pattern in campaigns.ts)
const response = await context.client.delete<unknown>(ENDPOINTS.offers.deletePath(options.id));
//                                                                                 ↑ path has ID baked in, no body sent

Proposed Solution

1. Add optional body to VoluumClient.delete()

// src/client/VoluumClient.ts
async delete<T>(path: string, body?: unknown, query?: QueryParams): Promise<T> {
  return this.request<T>("DELETE", path, { body, query });
}

2. Update deletePath to the collection path for affected resources

// src/endpoints.ts
offers: {
  deletePath: "/offer",     // collection path — IDs go in the body
},
campaigns: {
  deletePath: "/campaign",
},

Note: deletePath type changes from (id: string) => string to string, consistent with listPath and createPath. Other resources (landers, flows, traffic-sources, affiliate-networks, tracker-domains) should be verified against the API to determine if they share the same batch-body pattern.

3. Add --ids flag for single and batch deletion

Replace the current --id flag with --ids (accepting a comma-separated list) so a single call can delete one or many resources:

// src/commands/offers.ts
const ids = options.ids.split(",").map((s) => s.trim()).filter(Boolean);
const response = await context.client.delete<unknown>(
  ENDPOINTS.offers.deletePath,
  { ids },
);

// src/commands/campaigns.ts
const ids = options.ids.split(",").map((s) => s.trim()).filter(Boolean);
const response = await context.client.delete<unknown>(
  ENDPOINTS.campaigns.deletePath,
  { ids },
);

Example usage:

# Single delete
voluum offers delete --ids abc-123

# Batch delete
voluum offers delete --ids abc-123,def-456,ghi-789

Test Plan

  • Update VoluumClient unit tests to cover delete() with a body
  • Update offers delete tests: verify DELETE /offer is called with {"ids": ["<id>"]}
  • Update campaigns delete tests: verify DELETE /campaign is called with {"ids": ["<id>"]}
  • Verify other resource delete commands (landers, flows, etc.) against the API and update accordingly
  • Manual smoke test: voluum offers delete --ids <real-id> returns success
  • Manual smoke test: voluum offers delete --ids <id1>,<id2> deletes both in a single request

Affected Files

  • src/client/VoluumClient.ts — add body param to delete()
  • src/endpoints.ts — update deletePath shape for offers and campaigns (and any other batch-body resources)
  • src/commands/offers.ts — replace --id with --ids, send {"ids": [...]} body
  • src/commands/campaigns.ts — replace --id with --ids, send {"ids": [...]} body
  • tests/client/VoluumClient.test.ts — update delete tests
  • tests/commands/offers.test.ts — update delete tests
  • tests/commands/campaigns.test.ts — update delete tests

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions