Every API here is available over the APIs.io API and to AI agents over MCP.
openapi: 3.2.0
info:
title: Argus Negotiated pricing API
version: '1'
description: 'Argus API v1: spend, balances and rate-limit headroom across the APIs you pay for.
Authenticate reads with an `api` key as `Authorization: Bearer `. Every key sees one project.
Operations marked `x-access: session` are for the signed-in web app; API keys get `session_required`.
Operations marked `x-web: true` also accept the web app''s session, which API clients never use.
Operations with `x-mcp-key-types` are also MCP tools of the same name at `POST https://mcp.argus.town`, for keys of those types.
Send events with a source secret (`Authorization: Bearer argus_src_...` plus `Argus-Source`) or HMAC headers. A source credential can write events and read nothing; an API key cannot write events.
Responses are bare resources with an `object` tag; lists are `{object: "list", data, has_more, next_cursor}`.
Errors are `{error: {type, code, message, param, doc_url, next_step, request_id}}` with real HTTP statuses.
Unknown query parameters are rejected. Ids are prefixed (`conn_...`); bare ids are accepted on input.
Within v1 changes are additive: ignore fields you do not recognise.'
servers:
- url: https://api.argus.town
tags:
- name: Negotiated pricing
paths:
/v1/negotiated_pricing/{provider}:
put:
operationId: negotiated_pricing_put
tags:
- Negotiated pricing
summary: Set negotiated pricing
description: 'Record what your deal with a provider costs, so spend is priced at your rates instead of list price. `rates` are set one per `sku` and `unit` (rates you do not list are kept); `plan` replaces the negotiated subscription, and fields you omit from it are cleared. Every entry is validated and one bad entry rejects the request with `param` naming it: nothing is skipped silently. Events from the last 7 days that were left unpriced are repriced. `provider` is any provider slug your events use, catalog or not. At most 500 rates and 100 plans per project. List rates are read-only (`GET /v1/negotiated_pricing/{provider}?expand[]=list_rates`); resetting to list price is done in the app.'
security:
- bearerAuth: []
x-access: api_key
x-web: true
x-mcp-key-types:
- api
parameters:
- name: provider
in: path
required: true
description: Provider slug, for example `openrouter`.
schema:
type: string
- name: Idempotency-Key
in: header
required: false
description: '1 to 255 printable ASCII characters. A retry with the same key and body replays the first response (`Idempotent-Replayed: true`) for 24 hours; the same key with a different body is a 409, and so is a duplicate that arrives while the first request is still running.'
schema:
type: string
requestBody:
required: true
description: Rates, a plan, or both.
content:
application/json:
schema:
$ref: '#/components/schemas/NegotiatedPricingBody'
responses:
'200':
description: Success.
content:
application/json:
schema:
$ref: '#/components/schemas/NegotiatedPricing'
examples:
one rate:
summary: one rate
value:
object: negotiated_pricing
provider: openrouter
rates:
- sku: gpt-4o
unit: input_tokens
nano_usd_per_unit: 2000
plan: null
updated_at: '2026-10-01T09:30:00.000Z'
'400':
description: 'Error. Codes: invalid_request_body, invalid_parameter, invalid_idempotency_key, unknown_parameter, missing_parameter.'
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
examples:
malformed rate:
summary: malformed rate
value:
error:
type: invalid_request
code: invalid_parameter
message: 'Invalid value for "rates[0].nano_usd_per_unit": Expected a number between 0 and 1000000000000, actual -1'
param: rates[0].nano_usd_per_unit
doc_url: https://argus.town/docs/errors#invalid_parameter
request_id: null
'401':
description: 'Error. Codes: missing_credentials, invalid_api_key, session_expired.'
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
examples: {}
'403':
description: 'Error. Codes: key_type_not_allowed, session_required, csrf_check_failed.'
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
examples: {}
'404':
description: 'Error. Codes: provider_not_found.'
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
examples: {}
'409':
description: 'Error. Codes: idempotency_key_reused, quota_exceeded, idempotency_key_in_progress.'
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
examples: {}
'413':
description: 'Error. Codes: payload_too_large.'
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
examples: {}
'429':
description: 'Error. Codes: rate_limit_exceeded.'
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
examples: {}
headers:
Retry-After:
description: Seconds to wait before retrying.
schema:
type: integer
'500':
description: 'Error. Codes: internal_error.'
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
examples: {}
x-argus-examples:
- name: one rate
request:
method: PUT
path: /v1/negotiated_pricing/openrouter
body:
rates:
- sku: gpt-4o
unit: input_tokens
nano_usd_per_unit: 2000
status: 200
- name: malformed rate
request:
method: PUT
path: /v1/negotiated_pricing/openrouter
body:
rates:
- sku: gpt-4o
unit: input_tokens
nano_usd_per_unit: -1
status: 400
get:
operationId: negotiated_pricing_get
tags:
- Negotiated pricing
summary: Get negotiated pricing
description: The project's own rates and subscription for a provider; empty `rates` and a null `plan` when nothing is negotiated. `expand[]=list_rates` adds Argus's list rates, which negotiated rates replace one for one.
security:
- bearerAuth: []
x-access: api_key
x-web: true
x-mcp-key-types:
- api
parameters:
- name: provider
in: path
required: true
description: Provider slug, for example `openrouter`.
schema:
type: string
- name: expand[]
in: query
required: false
description: 'Fields to include. One or more of: list_rates.'
schema:
type: array
items:
type: string
enum:
- list_rates
style: form
explode: true
responses:
'200':
description: Success.
content:
application/json:
schema:
$ref: '#/components/schemas/NegotiatedPricing'
examples:
negotiated:
summary: negotiated
value:
object: negotiated_pricing
provider: openrouter
rates:
- sku: gpt-4o
unit: input_tokens
nano_usd_per_unit: 2000
plan: null
updated_at: '2026-10-01T09:30:00.000Z'
'400':
description: 'Error. Codes: invalid_parameter, unknown_parameter, missing_parameter.'
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
examples: {}
'401':
description: 'Error. Codes: missing_credentials, invalid_api_key, session_expired.'
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
examples: {}
'403':
description: 'Error. Codes: key_type_not_allowed, session_required, csrf_check_failed.'
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
examples: {}
'404':
description: 'Error. Codes: provider_not_found.'
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
examples: {}
'429':
description: 'Error. Codes: rate_limit_exceeded.'
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
examples: {}
headers:
Retry-After:
description: Seconds to wait before retrying.
schema:
type: integer
'500':
description: 'Error. Codes: internal_error.'
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
examples: {}
x-argus-examples:
- name: negotiated
request:
method: GET
path: /v1/negotiated_pricing/openrouter
status: 200
delete:
operationId: negotiated_pricing_delete
tags:
- Negotiated pricing
summary: Reset to list pricing
description: 'Remove every negotiated rate and the negotiated plan for a provider, or with `sku` and `unit` just that one rate (the plan stays). Session only: an API key is refused with `session_required`. A project with nothing negotiated, or no rate for that `sku` and `unit`, gets a 404.'
security:
- sessionCookie: []
x-access: session
parameters:
- name: provider
in: path
required: true
description: Provider slug, for example `openrouter`.
schema:
type: string
- name: sku
in: query
required: false
description: Reset only this rate; needs `unit`.
schema:
$ref: '#/components/schemas/NonEmptyString'
- name: unit
in: query
required: false
description: The unit of the rate to reset; needs `sku`.
schema:
$ref: '#/components/schemas/NonEmptyString'
responses:
'204':
description: Success. No content.
'400':
description: 'Error. Codes: invalid_parameter, unknown_parameter.'
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
examples: {}
'403':
description: 'Error. Codes: session_required.'
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
examples:
api key refused:
summary: api key refused
value:
error:
type: permission_error
code: session_required
message: This action needs a signed-in session in the Argus app. API keys cannot perform it.
param: null
doc_url: https://argus.town/docs/errors#session_required
next_step:
hint: Do this from the Argus app, signed in as a member of the project.
href: https://argus.town/settings
request_id: null
'404':
description: 'Error. Codes: provider_not_found, negotiated_pricing_not_found.'
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
examples: {}
'429':
description: 'Error. Codes: rate_limit_exceeded.'
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
examples: {}
headers:
Retry-After:
description: Seconds to wait before retrying.
schema:
type: integer
'500':
description: 'Error. Codes: internal_error.'
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
examples: {}
x-argus-examples:
- name: api key refused
request:
method: DELETE
path: /v1/negotiated_pricing/openrouter
status: 403
credential: api_key
- name: reset
request:
method: DELETE
path: /v1/negotiated_pricing/openrouter
status: 204
components:
schemas:
NextStep:
type: object
required:
- hint
properties:
hint:
type: string
method:
type: string
href:
type: string
additionalProperties: true
NegotiatedPricing:
type: object
required:
- object
- provider
- rates
- plan
- updated_at
properties:
object:
type: string
enum:
- negotiated_pricing
provider:
type: string
rates:
type: array
items:
$ref: '#/components/schemas/NegotiatedRate'
plan:
anyOf:
- $ref: '#/components/schemas/NegotiatedPlan'
- type: 'null'
updated_at:
anyOf:
- type: string
format: date-time
- type: 'null'
list_rates:
type: array
items:
$ref: '#/components/schemas/ListRate'
description: Argus's list rate for each `sku` and `unit` the provider is priced by, so you can see what a negotiated rate replaces. Present only with `expand[]=list_rates`.
additionalProperties: true
ListRate:
type: object
required:
- sku
- unit
- nano_usd_per_unit
- source
properties:
sku:
type: string
description: a string at most 120 character(s) long
title: maxLength(120)
minLength: 1
maxLength: 120
unit:
type: string
description: a string at most 60 character(s) long
title: maxLength(60)
minLength: 1
maxLength: 60
nano_usd_per_unit:
type: integer
description: a number between 0 and 1000000000000
title: between(0, 1000000000000)
minimum: 0
maximum: 1000000000000
source:
type: string
additionalProperties: true
NegotiatedPlan:
type: object
required:
- price_usd_micros
- included_amount
- included_meter_id
- overage_nano_usd_per_unit
properties:
price_usd_micros:
anyOf:
- type: integer
description: a number between 0 and 1000000000000
title: between(0, 1000000000000)
minimum: 0
maximum: 1000000000000
- type: 'null'
included_amount:
anyOf:
- type: integer
description: a number between 0 and 1000000000000000
title: between(0, 1000000000000000)
minimum: 0
maximum: 1000000000000000
- type: 'null'
included_meter_id:
anyOf:
- type: string
description: a string at most 120 character(s) long
title: maxLength(120)
minLength: 1
maxLength: 120
- type: 'null'
overage_nano_usd_per_unit:
anyOf:
- type: integer
description: a number between 0 and 1000000000000
title: between(0, 1000000000000)
minimum: 0
maximum: 1000000000000
- type: 'null'
additionalProperties: true
ErrorResponse:
type: object
required:
- error
properties:
error:
type: object
required:
- type
- code
- message
- param
- doc_url
- request_id
properties:
type:
type: string
enum:
- invalid_request
- authentication_error
- permission_error
- not_found
- conflict
- rate_limited
- plan_limit
- upstream_error
- api_error
code:
type: string
enum:
- unknown_parameter
- invalid_parameter
- invalid_cursor
- method_not_allowed
- missing_credentials
- invalid_api_key
- invalid_request_body
- missing_parameter
- invalid_idempotency_key
- payload_too_large
- too_many_events
- no_valid_events
- missing_source_credentials
- invalid_source_credentials
- timestamp_stale
- idempotency_key_reused
- idempotency_key_in_progress
- too_many_auth_failures
- origin_not_allowed
- key_type_not_allowed
- not_found
- provider_not_found
- connection_not_found
- sync_not_found
- connect_link_not_found
- alert_rule_not_found
- negotiated_pricing_not_found
- provider_not_connectable
- credential_invalid
- provider_unreachable
- label_conflict
- connection_not_pollable
- quota_exceeded
- session_required
- session_expired
- csrf_check_failed
- rate_limit_exceeded
- internal_error
message:
type: string
param:
anyOf:
- type: string
- type: 'null'
doc_url:
type: string
next_step:
$ref: '#/components/schemas/NextStep'
details:
type: object
required: []
properties: {}
additionalProperties:
$id: /schemas/unknown
title: unknown
request_id:
anyOf:
- type: string
- type: 'null'
additionalProperties: true
additionalProperties: true
NegotiatedPricingBody:
type: object
required: []
properties:
rates:
type: array
items:
$ref: '#/components/schemas/NegotiatedRate'
description: Rates to set, one per `sku` and `unit`; rates already set and not listed are kept. One bad entry rejects the whole request.
title: maxItems(100)
minItems: 1
maxItems: 100
plan:
$ref: '#/components/schemas/NegotiatedPlanInput'
additionalProperties: true
NegotiatedPlanInput:
type: object
required: []
properties:
price_usd_micros:
type: integer
description: a number between 0 and 1000000000000
title: between(0, 1000000000000)
minimum: 0
maximum: 1000000000000
included_amount:
type: integer
description: a number between 0 and 1000000000000000
title: between(0, 1000000000000000)
minimum: 0
maximum: 1000000000000000
included_meter_id:
type: string
description: a string at most 120 character(s) long
title: maxLength(120)
minLength: 1
maxLength: 120
overage_nano_usd_per_unit:
type: integer
description: a number between 0 and 1000000000000
title: between(0, 1000000000000)
minimum: 0
maximum: 1000000000000
additionalProperties: true
description: The negotiated subscription. Fields you omit are cleared.
NegotiatedRate:
type: object
required:
- sku
- unit
- nano_usd_per_unit
properties:
sku:
type: string
description: a string at most 120 character(s) long
title: maxLength(120)
minLength: 1
maxLength: 120
unit:
type: string
description: a string at most 60 character(s) long
title: maxLength(60)
minLength: 1
maxLength: 60
nano_usd_per_unit:
type: integer
description: a number between 0 and 1000000000000
title: between(0, 1000000000000)
minimum: 0
maximum: 1000000000000
additionalProperties: true
NonEmptyString:
type: string
description: a non empty string
title: nonEmptyString
minLength: 1
securitySchemes:
bearerAuth:
type: http
scheme: bearer
description: An `api` key.
sessionCookie:
type: apiKey
in: cookie
name: argus_session
description: The signed-in web app session. Operations that need it refuse every API key with `session_required`.
sourceSecret:
type: http
scheme: bearer
description: 'A source secret (`argus_src_...`), sent with the `Argus-Source` header. Write-only: it can send events and read nothing.'
sourceSignature:
type: apiKey
in: header
name: X-Argus-Signature
description: Hex HMAC-SHA256 of the timestamp, a dot and the raw body, keyed with the source secret, sent with `X-Argus-Source` and `X-Argus-Timestamp` (epoch ms, within 5 minutes).
x-error-codes:
unknown_parameter:
type: invalid_request
status: 400
description: The request has a query parameter this endpoint does not accept.
invalid_parameter:
type: invalid_request
status: 400
description: A parameter has a value this endpoint cannot use. `param` names it.
invalid_cursor:
type: invalid_request
status: 400
description: The cursor is malformed or does not belong to this list. Start again without it.
method_not_allowed:
type: invalid_request
status: 405
description: The path exists but not for this HTTP method. The Allow header lists the methods.
missing_credentials:
type: authentication_error
status: 401
description: No Authorization header was sent.
invalid_api_key:
type: authentication_error
status: 401
description: The key is unknown, revoked, or not an API key.
invalid_request_body:
type: invalid_request
status: 400
description: The body is not a JSON object (or, on `/v1/events`, not an event or an `events` array), or it is empty where fields are required.
missing_parameter:
type: invalid_request
status: 400
description: A required body field or header is missing. `param` names it.
invalid_idempotency_key:
type: invalid_request
status: 400
description: The Idempotency-Key header must be 1 to 255 printable ASCII characters.
payload_too_large:
type: invalid_request
status: 413
description: The body is over 1,000,000 bytes. Split the batch.
too_many_events:
type: invalid_request
status: 413
description: A batch holds at most 1,000 events. Split it.
no_valid_events:
type: invalid_request
status: 422
description: Every event in the batch was rejected, so nothing was queued. Send the same body to `POST /v1/events/validate` for each event's reason.
missing_source_credentials:
type: authentication_error
status: 401
description: 'No source credential was sent. Use `Authorization: Bearer <source secret>` with `Argus-Source: <source id>`, or the HMAC headers `X-Argus-Source`, `X-Argus-Timestamp` and `X-Argus-Signature`.'
invalid_source_credentials:
type: authentication_error
status: 401
description: The source is unknown or revoked, or the bearer secret or HMAC signature is wrong (sign the timestamp, a dot and the raw body, hex HMAC-SHA256). The cases are not distinguished.
timestamp_stale:
type: authentication_error
status: 401
description: The signed timestamp (epoch milliseconds) is more than 5 minutes from server time.
idempotency_key_reused:
type: conflict
status: 409
description: This Idempotency-Key was already used with a different request body or source. Use a new key for a new request.
idempotency_key_in_progress:
type: conflict
status: 409
description: A request with this Idempotency-Key is still running. Retry with the same key after a moment; it replays the response once the first request finishes.
too_many_auth_failures:
type: rate_limited
status: 429
description: Too many failed authentication attempts from this address. Wait `Retry-After` seconds. Valid credentials keep working from an address over budget, except that a credential the server has not seen recently gets only a small number of lookups per window, tracked per server instance; a client sharing an address with an attacker can be briefly locked out.
origin_not_allowed:
type: permission_error
status: 403
description: A browser Origin that is not allow-listed called the MCP server. Non-browser clients send no Origin.
key_type_not_allowed:
type: permission_error
status: 403
description: This key's type cannot call this endpoint.
not_found:
type: not_found
status: 404
description: No such endpoint.
provider_not_found:
type: not_found
status: 404
description: No provider has this slug.
connection_not_found:
type: not_found
status: 404
description: No connection with this id in the key's project. When a request body names the connection (`param` says where) the status is 422.
sync_not_found:
type: not_found
status: 404
description: No sync with this id in the key's project.
connect_link_not_found:
type: not_found
status: 404
description: No connect link with this id in the key's project.
alert_rule_not_found:
type: not_found
status: 404
description: No alert rule with this id in the key's project.
negotiated_pricing_not_found:
type: not_found
status: 404
description: The project has no negotiated pricing for this provider.
provider_not_connectable:
type: invalid_request
status: 422
description: The provider is unknown or Argus cannot poll it with a credential (telemetry only).
credential_invalid:
type: invalid_request
status: 422
description: The provider rejected the credential. `message` carries the provider's reason; the credential is never echoed.
provider_unreachable:
type: upstream_error
status: 502
description: Argus could not reach the provider to check the credential. Retry.
label_conflict:
type: conflict
status: 409
description: Another connection to this provider already has this label. Update that connection instead, or choose another label.
connection_not_pollable:
type: conflict
status: 409
description: The connection has no credential to poll with (telemetry only) or is suspended, so there is nothing to sync.
quota_exceeded:
type: plan_limit
status: 409
description: The project reached a limit on this kind of resource. `details` holds `resource`, `limit` and `used`.
session_required:
type: permission_error
status: 403
description: This operation needs a signed-in session in the Argus app; API keys cannot perform it.
session_expired:
type: authentication_error
status: 401
description: The web app's session is missing, expired or signed out. Only the signed-in web app sees it.
csrf_check_failed:
type: permission_error
status: 403
description: 'A web-app write did not come from the app itself: it lacks `x-argus-csrf`, or its `Origin` or `Sec-Fetch-Site` is not the app''s. API keys never see it.'
rate_limit_exceeded:
type: rate_limited
status: 429
description: The key exceeded its request budget. Wait `Retry-After` seconds.
internal_error:
type: api_error
status: 500
description: Something failed on our side. Retry; quote `request_id` if it persists.