Plans V5 (beta)

Create and edit several automatic collection plans per bot.

A plan is a name + when + conditions + input mode + where results go (union). A bot can have several, and each plan runs with its own conditions. It is the same resource as the Plans list on screen. See how plans ran in Plan runs and the data they collected in Datasets.

Endpoints

  • GET /v5/api/bots/{bot_id}/plans List plans
  • POST /v5/api/bots/{bot_id}/plans Create a plan (idempotency key required)
  • GET /v5/api/plans/{plan_id} One plan
  • PATCH /v5/api/plans/{plan_id} Edit only what you send
  • DELETE /v5/api/plans/{plan_id} Delete a plan (its datasets remain)
  • POST /v5/api/plans/{plan_id}/copy_settings Copy the bot settings values
  • POST /v5/api/plans/{plan_id}/preview Preview without saving
  • PUT /v5/api/plans/{plan_id}/excel Plan-specific Excel (not supported yet)

Plan ids and which accounts have them

A plan id is a string. A plan created in plan mode is a numeric string such as "512", and the computed plan 1 of an existing bot that has no plan row yet is "one-<bot ID>" (for example one-8f3a7c1e9b5d24f6). Renaming it, changing its conditions or turning it off switches the bot to plan mode and creates a plan row, so check the id in the response and use it from then on.

For accounts where multiple plans are not open, every path on this page (and run, runs and the plan_id filter of datasets) is 404 PLAN_NOT_FOUND.

Read

The list is in screen order (plan 1 first), not in the order plans were turned on. Plan 1 has linked: true and follows the bot settings; setting its conditions separately breaks that link.

FieldDescription
idThe plan id (string).
nameThe plan name. "Plan N" (in Korean) when not given at creation.
enabledWhether it is on.
linkedtrue means it follows the bot settings.
whenWhen it runs: one of the five types below.
label, label_enA readable sentence for when (Korean, English).
inputsThe conditions this plan uses, { param1: value }. The bot settings values when it follows them.
conditionsThe conditions shortened to a list [{ name, value }].
input_modedirect or excel.
union{ enabled, target_dataset_id }.
next_runsThe next five run times (while it is on).
Example request
curl 'https://www.hashscraper.com/v5/api/bots/8f3a7c1e9b5d24f6/plans' \
  -H 'Authorization: Bearer YOUR_API_KEY'
Example response 200
{
  "result": "success",
  "version": "v5",
  "request_id": "req_5a8c1f1c-9b2d-4e7c-9abf-3f7e0a4d1b21",
  "elapsed_sec": 0.0123,
  "count": 2,
  "data": {
    "bot_id": "8f3a7c1e9b5d24f6",
    "plans": [
      {
        "id": "one-8f3a7c1e9b5d24f6",
        "bot_id": "8f3a7c1e9b5d24f6",
        "name": "주기 1",
        "enabled": true,
        "linked": true,
        "when": {
          "type": "daily",
          "times": [
            "09:00"
          ]
        },
        "label": "매일 09:00 (KST)",
        "label_en": "Every day at 09:00 (KST)",
        "inputs": {
          "param1": "sneakers"
        },
        "conditions": [
          {
            "name": "Keyword",
            "value": "sneakers"
          }
        ],
        "input_mode": "direct",
        "union": {
          "enabled": false,
          "target_dataset_id": null
        },
        "next_runs": [
          "2026-10-05T09:00:00+09:00",
          "2026-10-06T09:00:00+09:00"
        ]
      },
      {
        "id": "512",
        "bot_id": "8f3a7c1e9b5d24f6",
        "name": "주기 2",
        "enabled": true,
        "linked": false,
        "when": {
          "type": "weekly",
          "every_weeks": 1,
          "anchor": null,
          "days": [
            "mon"
          ],
          "times": [
            "09:00"
          ]
        },
        "label": "매주 월요일 09:00 (KST)",
        "label_en": "Every Monday at 09:00 (KST)",
        "inputs": {
          "param1": "sneakers"
        },
        "conditions": [
          {
            "name": "Keyword",
            "value": "sneakers"
          }
        ],
        "input_mode": "direct",
        "union": {
          "enabled": false,
          "target_dataset_id": null
        },
        "next_runs": [
          "2026-10-05T09:00:00+09:00",
          "2026-10-12T09:00:00+09:00",
          "2026-10-19T09:00:00+09:00"
        ]
      }
    ]
  }
}
Example request
curl 'https://www.hashscraper.com/v5/api/plans/512' \
  -H 'Authorization: Bearer YOUR_API_KEY'
Example response 200
{
  "result": "success",
  "version": "v5",
  "request_id": "req_5a8c1f1c-9b2d-4e7c-9abf-3f7e0a4d1b21",
  "elapsed_sec": 0.0123,
  "data": {
    "id": "512",
    "bot_id": "8f3a7c1e9b5d24f6",
    "name": "주기 2",
    "enabled": true,
    "linked": false,
    "when": {
      "type": "weekly",
      "every_weeks": 1,
      "anchor": null,
      "days": [
        "mon"
      ],
      "times": [
        "09:00"
      ]
    },
    "label": "매주 월요일 09:00 (KST)",
    "label_en": "Every Monday at 09:00 (KST)",
    "inputs": {
      "param1": "sneakers"
    },
    "conditions": [
      {
        "name": "Keyword",
        "value": "sneakers"
      }
    ],
    "input_mode": "direct",
    "union": {
      "enabled": false,
      "target_dataset_id": null
    },
    "next_runs": [
      "2026-10-05T09:00:00+09:00",
      "2026-10-12T09:00:00+09:00",
      "2026-10-19T09:00:00+09:00"
    ]
  }
}

When it runs: five types of when

Send when as the screen values, not as an expression. Times are "HH:MM" (24-hour, KST) and you can give several.

manual and custom cannot be used for a plan. To stop a plan, turn it off with enabled: false. A bad value gives 400 INVALID_PARAM with the reason in details.errors. An old expression that the screen cannot represent reads as { "type": "custom", "editable": false } and cannot be changed through this path.

typeFieldsMeaning
intervalminutesEvery N minutes.
dailytimesEvery day at the given times.
daysevery_days (2 or more), anchor (start date), timesEvery N days, counted from the start date.
weeklyevery_weeks (default 1), anchor, days (mon to sun), timesEvery N weeks on the given weekdays. When every_weeks is 2 or more, anchor sets the reference week (this week if omitted).
monthlydates (1 to 31 and "last"), timesEvery month on the given dates.
when · interval
{
  "type": "interval",
  "minutes": 10
}
when · daily
{
  "type": "daily",
  "times": [
    "09:00",
    "18:30"
  ]
}
when · days
{
  "type": "days",
  "every_days": 3,
  "anchor": "2026-10-05",
  "times": [
    "09:00"
  ]
}
when · weekly
{
  "type": "weekly",
  "every_weeks": 2,
  "anchor": "2026-10-05",
  "days": [
    "mon",
    "thu"
  ],
  "times": [
    "09:00"
  ]
}
when · monthly
{
  "type": "monthly",
  "dates": [
    1,
    15,
    "last"
  ],
  "times": [
    "06:00"
  ]
}

Create

Body: when (required), name ("Plan N" if omitted), inputs (filled with the current bot settings values if omitted), union (true, false or { enabled }), enabled (default true). Like plans from plan 2 on, a new plan has its own conditions from the start. On success you get 201 and the plan.

An idempotency key is required (without it you get 400 IDEMPOTENCY_KEY_REQUIRED). Resending with the same key returns the first response and does not create a second plan.

Example request
curl -X POST 'https://www.hashscraper.com/v5/api/bots/8f3a7c1e9b5d24f6/plans' \
  -H 'Authorization: Bearer YOUR_API_KEY' \
  -H "Idempotency-Key: $(uuidgen)" \
  -H 'Content-Type: application/json' \
  -d '{
  "name": "Monday morning",
  "when": {
    "type": "weekly",
    "days": [
      "mon"
    ],
    "times": [
      "09:00"
    ]
  },
  "inputs": {
    "param1": "sneakers"
  }
}'
Example response 201
{
  "result": "success",
  "version": "v5",
  "request_id": "req_5a8c1f1c-9b2d-4e7c-9abf-3f7e0a4d1b21",
  "elapsed_sec": 0.0123,
  "data": {
    "id": "512",
    "bot_id": "8f3a7c1e9b5d24f6",
    "name": "Monday morning",
    "enabled": true,
    "linked": false,
    "when": {
      "type": "weekly",
      "every_weeks": 1,
      "anchor": null,
      "days": [
        "mon"
      ],
      "times": [
        "09:00"
      ]
    },
    "label": "매주 월요일 09:00 (KST)",
    "label_en": "Every Monday at 09:00 (KST)",
    "inputs": {
      "param1": "sneakers"
    },
    "conditions": [
      {
        "name": "Keyword",
        "value": "sneakers"
      }
    ],
    "input_mode": "direct",
    "union": {
      "enabled": false,
      "target_dataset_id": null
    },
    "next_runs": [
      "2026-10-05T09:00:00+09:00",
      "2026-10-12T09:00:00+09:00",
      "2026-10-19T09:00:00+09:00"
    ]
  }
}

Edit

Of name, enabled, when, inputs, union and input_mode, only what you send changes (sending none gives MISSING_PARAM). input_mode accepts only direct. union.target_dataset_id must be a dataset made by this plan. Sending inputs or union for a plan that follows the bot settings breaks the link and gives it its own conditions.

Example request
curl -X PATCH 'https://www.hashscraper.com/v5/api/plans/512' \
  -H 'Authorization: Bearer YOUR_API_KEY' \
  -H "Idempotency-Key: $(uuidgen)" \
  -H 'Content-Type: application/json' \
  -d '{
  "enabled": false
}'
Example request
curl -X PATCH 'https://www.hashscraper.com/v5/api/plans/512' \
  -H 'Authorization: Bearer YOUR_API_KEY' \
  -H "Idempotency-Key: $(uuidgen)" \
  -H 'Content-Type: application/json' \
  -d '{
  "when": {
    "type": "daily",
    "times": [
      "07:30"
    ]
  },
  "inputs": {
    "param1": "boots"
  }
}'

Delete

Deleting a plan keeps the datasets it made. Plan 1 (the plan that follows the bot settings, or the computed plan 1) cannot be deleted and gives 409 PLAN_UNDELETABLE. To stop it, PATCH enabled: false.

Example request
curl -X DELETE 'https://www.hashscraper.com/v5/api/plans/512' \
  -H 'Authorization: Bearer YOUR_API_KEY' \
  -H "Idempotency-Key: $(uuidgen)"
Example response 200
{
  "result": "success",
  "version": "v5",
  "request_id": "req_5a8c1f1c-9b2d-4e7c-9abf-3f7e0a4d1b21",
  "elapsed_sec": 0.0123,
  "data": {
    "id": "512",
    "deleted": true
  }
}

Copy bot settings values

This is "Apply to plans too" on screen. It copies the current bot settings values into a plan whose conditions are set separately. Give fields an array of field names (name, name_en) or paramN to copy only those, or omit it for every field the bot uses. copied_fields in the response lists what was actually copied. A plan that follows the bot settings already reads the same values, so nothing changes ([]).

Example request
curl -X POST 'https://www.hashscraper.com/v5/api/plans/512/copy_settings' \
  -H 'Authorization: Bearer YOUR_API_KEY' \
  -H "Idempotency-Key: $(uuidgen)" \
  -H 'Content-Type: application/json' \
  -d '{
  "fields": [
    "Keyword"
  ]
}'
Example response 200
{
  "result": "success",
  "version": "v5",
  "request_id": "req_5a8c1f1c-9b2d-4e7c-9abf-3f7e0a4d1b21",
  "elapsed_sec": 0.0123,
  "data": {
    "id": "512",
    "bot_id": "8f3a7c1e9b5d24f6",
    "name": "주기 2",
    "enabled": true,
    "linked": false,
    "when": {
      "type": "weekly",
      "every_weeks": 1,
      "anchor": null,
      "days": [
        "mon"
      ],
      "times": [
        "09:00"
      ]
    },
    "label": "매주 월요일 09:00 (KST)",
    "label_en": "Every Monday at 09:00 (KST)",
    "inputs": {
      "param1": "sneakers"
    },
    "conditions": [
      {
        "name": "Keyword",
        "value": "sneakers"
      }
    ],
    "input_mode": "direct",
    "union": {
      "enabled": false,
      "target_dataset_id": null
    },
    "next_runs": [
      "2026-10-05T09:00:00+09:00",
      "2026-10-12T09:00:00+09:00",
      "2026-10-19T09:00:00+09:00"
    ],
    "copied_fields": [
      "param1"
    ]
  }
}

Preview

See how a when would run without saving it. Without when in the body, the plan's current value is used. You get the next count run times (1 to 20, default 5), sentences in Korean and English, and the monthly average runs_per_month. No estimated credits are returned.

Example request
curl -X POST 'https://www.hashscraper.com/v5/api/plans/512/preview' \
  -H 'Authorization: Bearer YOUR_API_KEY' \
  -H "Idempotency-Key: $(uuidgen)" \
  -H 'Content-Type: application/json' \
  -d '{
  "when": {
    "type": "monthly",
    "dates": [
      1,
      15
    ],
    "times": [
      "06:00"
    ]
  },
  "count": 3
}'
Example response 200
{
  "result": "success",
  "version": "v5",
  "request_id": "req_5a8c1f1c-9b2d-4e7c-9abf-3f7e0a4d1b21",
  "elapsed_sec": 0.0123,
  "data": {
    "id": "512",
    "when": {
      "type": "monthly",
      "dates": [
        1,
        15
      ],
      "times": [
        "06:00"
      ]
    },
    "label": "매월 1일, 15일 06:00 (KST)",
    "label_en": "Monthly on the 1st and 15th at 06:00 (KST)",
    "sentence": {
      "ko": "매월 1일, 15일 06:00 (KST)에 수집합니다.",
      "en": "Collects monthly on the 1st and 15th at 06:00 (KST)."
    },
    "next_runs": [
      "2026-10-15T06:00:00+09:00",
      "2026-11-01T06:00:00+09:00",
      "2026-11-15T06:00:00+09:00"
    ],
    "runs_per_month": 2
  }
}

Plan-specific Excel

Plans that run from Excel are outside the first scope of v5. This path exists only as an address and always returns 422 EXCEL_NOT_SUPPORTED_YET.

Example request
curl -X PUT 'https://www.hashscraper.com/v5/api/plans/512/excel' \
  -H 'Authorization: Bearer YOUR_API_KEY' \
  -H "Idempotency-Key: $(uuidgen)" \
  -F 'file=@products.xlsx'
Example response 422
{
  "result": "error",
  "version": "v5",
  "request_id": "req_5a8c1f1c-9b2d-4e7c-9abf-3f7e0a4d1b21",
  "code": "EXCEL_NOT_SUPPORTED_YET",
  "message": "Collecting with Excel input is not supported in v5 yet.",
  "elapsed_sec": 0.0011
}

Errors you meet when writing

CodeHTTPMeaning
PLAN_CONDITION_BLOCKED422This bot cannot have its own conditions per plan.
PLAN_NAME_TAKEN422A plan with the same name already exists.
PLAN_UNDELETABLE409Plan 1 cannot be deleted.
PLAN_MODE_REFUSED422This bot cannot be switched to plan mode (the reason is in details.reason; legacy_running when the old mechanism is running it).
EXCEL_NOT_SUPPORTED_YET422Excel input is not supported yet.
FORBIDDEN403Bots of a demo account cannot be changed.
INVALID_PARAM400A value is wrong. See details.errors.

Related guides