Skip to main content

Strategy Endpoints

Create and manage AI-generated extraction strategies via HTTP.

Generate strategy

Generate a new extraction strategy using AI.
POST /api/strategies/generate

Request body

{
  "url": "https://example.com/products",
  "description": "Extract product names and prices",
  "name": "Product Scraper",
  "output_schema": {"title": "string", "price": "number"},
  "filter_config": {
    "mode": "all",
    "conditions": [
      {"field": "price", "operator": "gt", "value": "10"}
    ]
  }
}
FieldTypeRequiredDescription
urlstringYesTarget webpage URL
descriptionstringYesWhat to extract
namestringYesStrategy name
output_schemaobjectNoDesired output JSON structure. See Output Schemas
filter_configobjectNoPost-extraction filter. See Filtering
strategy_group_idUUIDNoAdd the strategy to this group on creation

Response

{
  "strategy_id": "550e8400-e29b-41d4-a716-446655440000",
  "strategy": {
    "container": {...},
    "fields": {...}
  },
  "preview_data": [
    {"name": "Product A", "price": "$19.99"},
    {"name": "Product B", "price": "$29.99"}
  ],
  "attempts": 1
}

Response headers

This endpoint returns strategy quota headers on every response (success and the 403 thrown when the quota is exceeded), so clients can render quota state without an extra round-trip.

Idempotency

Pass an Idempotency-Key: <string> header to make retries safe — the server caches the response for 24 hours and replays it on subsequent requests with the same key. Cached replays do not run a new LLM job and do not consume additional quota.
ScenarioResult
First request with a given keyRuns normally; response cached for 24h
Retry with same key + same bodyReturns cached response with Idempotent-Replayed: true header
Retry with same key + different body422 Idempotency-Key reused with a different request body.
Retry while the original is still running409 Idempotent request still in progress. Retry shortly. plus Retry-After: 5
No header sentNo change in behavior — existing clients are unaffected
Notes:
  • Keys are scoped per user / API key, so two accounts cannot collide on the same string.
  • Use a client-generated unique value (e.g. uuidgen, or a stable hash of URL + description + run-id). Two semantically-equivalent retries must reuse the same key.
  • Pair this with your own retry-on-5xx logic to avoid duplicate strategies (and quota burn) on transient timeouts.
  • The 24h TTL is fixed and not configurable.

Example

curl -X POST https://api.meter.sh/api/strategies/generate \
  -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://news.ycombinator.com",
    "description": "Extract post titles and scores",
    "name": "HN Front Page"
  }'
Idempotent retry — first call runs, second call replays the cached response:
KEY=$(uuidgen)

# First request — runs normally, returns the new strategy.
curl -X POST https://api.meter.sh/api/strategies/generate \
  -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $KEY" \
  -d '{
    "url": "https://news.ycombinator.com",
    "description": "Extract post titles and scores",
    "name": "HN Front Page"
  }'

# Retry with the same key + same body — replays the cached response.
# Response includes the header `Idempotent-Replayed: true`. No quota is consumed.
curl -X POST https://api.meter.sh/api/strategies/generate \
  -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $KEY" \
  -d '{
    "url": "https://news.ycombinator.com",
    "description": "Extract post titles and scores",
    "name": "HN Front Page"
  }'
The same Idempotency-Key is also honored on POST /api/strategies/generate/stream.

Refine strategy

Improve an existing strategy with feedback.
POST /api/strategies/{strategy_id}/refine

Request body

{
  "feedback": "Also extract product images and SKU"
}

Response

Same as generate strategy response with updated preview data.

Example

curl -X POST https://api.meter.sh/api/strategies/550e8400-e29b-41d4-a716-446655440000/refine \
  -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{"feedback": "Also extract product images"}'

List strategies

Get all strategies for the authenticated user.
GET /api/strategies?limit=20&offset=0

Query parameters

ParameterTypeRequiredDescription
limitintegerNoMax results (default: 20)
offsetintegerNoResults to skip (default: 0)

Response

[
  {
    "strategy_id": "550e8400-e29b-41d4-a716-446655440000",
    "name": "Product Scraper",
    "description": "Extract product names and prices",
    "url": "https://example.com/products",
    "preview_data": [...],
    "created_at": "2025-01-15T10:30:00Z",
    "updated_at": "2025-01-15T10:30:00Z"
  }
]

Example

curl https://api.meter.sh/api/strategies?limit=10 \
  -H "Authorization: Bearer sk_live_..."

Get strategy

Get details for a specific strategy.
GET /api/strategies/{strategy_id}

Response

Same format as list strategies items.

Example

curl https://api.meter.sh/api/strategies/550e8400-e29b-41d4-a716-446655440000 \
  -H "Authorization: Bearer sk_live_..."

Update strategy

Update a strategy’s filter configuration and/or output schema.
PATCH /api/strategies/{strategy_id}

Request body

{
  "filter_config": {
    "mode": "all",
    "conditions": [
      {"field": "price", "operator": "gt", "value": "50"},
      {"field": "in_stock", "operator": "equals", "value": "true"}
    ]
  },
  "output_schema": {"title": "string", "price": "number"}
}
FieldTypeRequiredDescription
filter_configobjectNoPost-extraction filter configuration. See Filtering
output_schemaobjectNoDesired output JSON structure. See Output Schemas

PATCH semantics

Each field follows three-state PATCH semantics:
Body shapeEffect on stored value
Field omitted from bodyLeft unchanged
Field present as null or {}Cleared
Field present with a valueReplaced
This applies independently to both filter_config and output_schema. To clear only one, send null for that field and omit the other.
Behavior change. Earlier versions of this endpoint cleared filter_config when it was omitted from the request body. The endpoint now treats omission as “leave unchanged” — consistent with standard PATCH semantics. If you were relying on the old behavior, send "filter_config": null explicitly to clear.

Response

Updated strategy details.

Examples

Replace the filter, leave the output schema unchanged:
curl -X PATCH https://api.meter.sh/api/strategies/550e8400-e29b-41d4-a716-446655440000 \
  -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "filter_config": {
      "mode": "all",
      "conditions": [
        {"field": "price", "operator": "gt", "value": "50"}
      ]
    }
  }'
Clear the output schema, leave the filter unchanged:
curl -X PATCH https://api.meter.sh/api/strategies/550e8400-e29b-41d4-a716-446655440000 \
  -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{"output_schema": null}'
Update both at once:
curl -X PATCH https://api.meter.sh/api/strategies/550e8400-e29b-41d4-a716-446655440000 \
  -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "output_schema": {"title": "string", "price": "number"},
    "filter_config": null
  }'

Strategy quota headers

Every response from a strategy-creation endpoint includes headers describing the caller’s current standing against the rolling 30-day strategy quota. These ride on successful responses and on the 403 thrown when the quota is exceeded, so a client can render quota state from any creation call without an extra round-trip.
HeaderValueNotes
X-Strategy-Quota-UsedIntegerStrategies counted in the rolling 30-day window. Counts both currently-active strategies and strategies created-then-deleted in the window.
X-Strategy-Quota-LimitIntegerPlan limit. -1 means unlimited (enterprise).
X-Strategy-Quota-ResetISO 8601 UTC timestampWhen the next quota slot frees (oldest counted strategy + 30d). Omitted for unlimited plans and when no strategies have been counted yet.
X-Strategy-Quota-Tierfree | hobby | pro | enterpriseThe plan tier used to compute the limit.
These headers are emitted by:
  • POST /api/strategies/generate
  • POST /api/strategies/generate/stream
  • POST /api/watch
For a pure read of the same numbers without making a creation call, use GET /api/account/quota.

Strategy audit log

Paginated audit log of strategy create and delete events, merged into a single timeline (most recent first).
GET /api/strategies/audit
The endpoint merges three event sources:
  • created events from currently-active strategies
  • created events from previously-deleted strategies (created within the window, then deleted)
  • deleted events
All created events have counted_against_quota: true (every created strategy consumed one slot of the rolling 30-day quota, whether or not it was later deleted). deleted events have counted_against_quota: false — they are tracking-only and do not consume quota.

Query parameters

ParameterTypeRequiredDescription
fromdatetime (ISO 8601)NoInclusive lower bound. Default: now - 30 days (matches the quota window).
todatetime (ISO 8601)NoInclusive upper bound. Default: now.
limitintegerNoPage size. Default: 100. Max: 500.
offsetintegerNoDefault: 0.

Response

{
  "events": [
    {
      "event_type": "created",
      "strategy_id": "550e8400-e29b-41d4-a716-446655440000",
      "name": "Product Scraper",
      "url": "https://example.com/products",
      "scraper_type": "html",
      "timestamp": "2026-05-10T14:32:11Z",
      "counted_against_quota": true
    },
    {
      "event_type": "deleted",
      "strategy_id": "441e8400-e29b-41d4-a716-446655440000",
      "name": "Old Scraper",
      "url": "https://example.com/old",
      "scraper_type": "html",
      "timestamp": "2026-05-09T09:10:00Z",
      "counted_against_quota": false
    }
  ],
  "count": 12,
  "limit": 100,
  "offset": 0,
  "window_start": "2026-04-17T14:32:11Z",
  "window_end": "2026-05-17T14:32:11Z"
}
FieldTypeDescription
events[].event_typestringcreated or deleted.
events[].counted_against_quotabooleantrue for every created event; false for deleted.
countintegerTotal events in the window across all pages.
window_start / window_enddatetimeEcho of the resolved from / to bounds.

Example

# Default last-30-days window, 50 events per page
curl "https://api.meter.sh/api/strategies/audit?limit=50" \
  -H "Authorization: Bearer sk_live_..."

# Custom window
curl "https://api.meter.sh/api/strategies/audit?from=2026-01-01T00:00:00Z&to=2026-02-01T00:00:00Z" \
  -H "Authorization: Bearer sk_live_..."

Compare manifest

Compare a manifest of known items against the latest scrape results for a strategy. This is a convenience endpoint that automatically uses the most recent completed job.
POST /api/strategies/{strategy_id}/compare-manifest

Request body

{
  "manifest": [
    {"name": "Acme Corp"},
    {"name": "Beta Industries"},
    {"name": "Gamma Solutions"}
  ],
  "match_fields": ["name"],
  "threshold": 80
}
FieldTypeRequiredDescription
manifestarrayYesList of known items (objects with at least the match_fields keys)
match_fieldsarrayYesField name(s) to fuzzy-match on (e.g., ["name"])
thresholdnumberNoMinimum match score 0-100 (default: 80)

Response

{
  "matched": [
    {
      "manifest_item": {"name": "Acme Corp"},
      "scraped_item": {"name": "Acme Corporation", "website": "acme.com"},
      "score": 90.0,
      "matched_on": "name"
    }
  ],
  "added": [
    {"name": "Delta Partners", "website": "delta.com"}
  ],
  "removed": [
    {"name": "Beta Industries"}
  ],
  "summary": {
    "matched": 1,
    "added": 1,
    "removed": 1,
    "manifest_count": 2,
    "scraped_count": 2
  },
  "threshold_used": 80.0,
  "match_fields_used": ["name"],
  "job_id": "660e8400-e29b-41d4-a716-446655440000"
}

Example

curl -X POST https://api.meter.sh/api/strategies/550e8400-e29b-41d4-a716-446655440000/compare-manifest \
  -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "manifest": [
      {"name": "Acme Corp"},
      {"name": "Beta Industries"}
    ],
    "match_fields": ["name"],
    "threshold": 80
  }'
Learn more about how fuzzy matching works and best practices for tuning the threshold in the Manifest Comparison concept guide.

Delete strategy

Delete a strategy and all associated jobs and schedules.
DELETE /api/strategies/{strategy_id}

Response

{
  "message": "Strategy deleted successfully"
}

Example

curl -X DELETE https://api.meter.sh/api/strategies/550e8400-e29b-41d4-a716-446655440000 \
  -H "Authorization: Bearer sk_live_..."
This action is irreversible and deletes all associated resources.

Error responses

StatusDescription
400Invalid request (missing required fields, invalid URL format)
401Invalid or missing API key
404Strategy not found
429Rate limit exceeded (strategy generation is rate-limited)
500Internal server error
503Service temporarily unavailable (AI service issues)
See REST API Errors for detailed error handling.

Next steps

Job Endpoints

Execute scrapes using strategies

Python SDK

Use the Python SDK instead

Strategies Concept

Learn about strategies

Need help?

Email me at mckinnon@meter.sh