Skip to content
PeriplusDocs
Open app
Pins

Pinskeep captures longer

Keep exact captures available so you can return to the same source material.

Pin captures, not URLs

Every capture is kept for seven days after collection. After that, it is scheduled for deletion unless a pin keeps it. Pin any available captures in the shared corpus—not only those your organization collected. A pin keeps that exact set; later captures of the same pages are not added.

  1. Enter exact capture IDs, write SQL returning capture_id, or choose a saved query returning capture_id.
  2. Choose how many days to keep them and give the pin a recognizable name.
  3. Create the pin. It is active immediately.

SQL runs once

SQL selections run once, when you create the pin, and the exact result is locked in. Running the same query beforehand shows what it would select today, but it is only an estimate: the corpus can change before you create the pin. Membership never changes afterwards; create another pin for a different set.

Instead of sql, give saved_query_id and its parameters to pin the result of a saved query in your organization. It runs once, exactly like sql, and the pin records saved_query_id as its provenance. This also requires permission to read saved queries; a saved query from another organization is not found.

Selections that exceed your organization’s per-pin limit, return a truncated result, or select nothing fail rather than creating a partial pin. Every selected capture must still be available.

Expiry

A pin made with explicit capture IDs, SQL or a saved query keeps every capture until days after the pin was created, not after the page was captured. A pin_days pin on a capture, crawl or schedule instead keeps each capture days after it joins the pin (below), so its captures expire at different times; the pin's expiry is the latest of them. Changing days moves every live capture’s expiry by the difference: shortening lets captures expire sooner, and lengthening again does not revive the ones that expired. Extending retention takes effect immediately for captures the pin still keeps; captures already near their expiry are verified first. Deleting a pin removes future protection; it does not delete captures or erase usage already recorded.

When a pin ends, its captures are scheduled for deletion once they are at least seven days old, unless another pin keeps them. This does not start a fresh seven-day period. Once a capture has been deleted, pinning cannot bring it back.

A pin's captures count and capture_days cover the captures it keeps now: a member drops out when its own expiry passes, and expired members are removed a day later, so a long-running schedule's pin holds only its live captures. expires_at is when the pin stops keeping its last capture: the latest deadline among its live captures. Changing days moves the expiry of live captures only (expired ones are not revived), and expires_at moves with them, so shortening lowers it. Once no capture is live it keeps its last, already past value, and removing expired members never changes it, so an expired pin stays expired.

Find pins that expire soon

Pins are listed newest first. To see what lapses next, list by expiry: order=expires puts the nearest expiry first, and pins without captures yet (a capture, crawl or schedule pin before its first success) come last because they have no expiry. status=active keeps pins that have not expired yet; status=expired lists the ones that have. search keeps pins whose name contains the text, ignoring case. All of these combine with project_id; continue with cursor set to the previous page's next_cursor. Home shows your active pins expiring soonest. Agents use pin_list with order="expires" and status="active".

Python
from periplus_sdk import Client

with Client() as client:
    for pin in client.pins.list(order="expires", status="active", limit=10).items:
        print(pin.expires_at, pin.name)

Pin as you collect

Captures and crawls accept pin_days, and every schedule has one (by default every_days + 7: until the next run plus a week). Periplus creates one pin for that source, and each successful capture it produces joins the pin, expiring pin_days after it joins. A capture joins when its request records it: normally moments after capture, but later when the request reuses a capture already in the corpus. Failed pages never join. The pin starts with its source's name and description, so it records why the evidence is kept; editing one later does not change the other. Deleting that pin stops pinning further captures from its source; editing its days applies to existing and future captures.

Overlapping pins are fine

Multiple pins can cover the same captures. A capture is kept until the latest expiry among them. Within your organization, the same pinned capture-time counts only once toward usage.

Another organization collecting more captures cannot expand your pin or increase your usage. Ten captures pinned for three full days use 30 capture-days.

Keep a capture beyond its first seven daysInteractive example

Collected on day 0. Scheduled for deletion from day 21, unless another pin keeps it longer. Without either pin, deletion would be scheduled from day 7.

Overlapping pins keep the capture until the latest expiry. Within your organization, overlapping time counts once toward pinned capture-days. Deleting a pin does not erase usage already recorded.

Pin a selection from a script

The pin is active as soon as it is created. Supply your own stable UUID as id to recover a create request after losing its response. An optional Markdown description records why the captures matter; HTML is not rendered.

Python

Python
from periplus_sdk import Client

with Client() as client:
    pin = client.pins.create(
        name="Research sources", days=90,
        description="Evidence for the **Q3 pricing review**.",
        sql="SELECT capture_id FROM captures WHERE domain(url) = ?",
        parameters=["example.com"],
    )
    print(pin.id, pin.captures, pin.expires_at)

cURL

Shell
curl --fail-with-body "${PERIPLUS_API_URL:-https://api.periplus.dev}/pins" \
  -H "Authorization: Bearer $PERIPLUS_API_KEY" \
  -X POST \
  -H 'Content-Type: application/json' \
  --data '{"name":"Research sources","days":90,"sql":"SELECT capture_id FROM captures WHERE domain(url) = ?","parameters":["example.com"]}'

Change how long a pin lasts

Set PIN_ID and PIN_VERSION to the pin’s current values. A stale version returns 409; fetch and review the pin before deciding what to do next. The same update changes name or description: omit a field to keep it; a null or blank description clears it.

Python

Python
from periplus_sdk import Client

with Client() as client:
    import os
    pin = client.pins.get(os.environ["PIN_ID"])
    pin = client.pins.update(pin, days=180, description="Kept for the **annual** audit.")
    print(pin.expires_at)

cURL

Shell
curl --fail-with-body "${PERIPLUS_API_URL:-https://api.periplus.dev}/pins/$PIN_ID" \
  -H "Authorization: Bearer $PERIPLUS_API_KEY" \
  -X PATCH \
  -H 'Content-Type: application/json' \
  --data "{\"expected_version\":$PIN_VERSION,\"days\":180}"

Create a pin

Projects

Every pin belongs to exactly one project, and projects are how you organize pins. Your organization starts with a Default Project, and a pin created without a project lands there, so nothing changes for work that never mentions projects. Create projects for separate pieces of work, such as one per product or customer, and keep each pin with the queries, pins and schedules around it. Projects organize your work, not your data: SQL reads every capture your organization can see, whichever project a query is in.

Choose a project with the switcher in the sidebar: Pins lists that project's pins, and new pins go into it. To move a pin, use Move… on its page, or select several and choose Move to project. Moving never changes what a pin does. From the API, pass project_id when creating a pin, or move it later with project_id and its current version as expected_version. Agents find project IDs with projects_list and pass project_id to pin_create, pin_update and pin_list; there is no active project, so name it on every call.

Python
from periplus_sdk import Client

with Client() as client:
    default_project = next(client.projects.iter())    # listed first
    project = client.projects.create(name="Product A")
    client.pins.create(name="Evidence", days=90, sql="SELECT capture_id FROM captures LIMIT 10", project_id=project)
    for pin in client.pins.list(project_id=default_project).items:
        client.pins.move(pin, project_id=project)
    print(client.projects.get(project).item_counts.pins)