STACK
MENU
DOCS / API REFERENCE / DROP-OFFS

Drop-offs API

Drop-offs are secure, schema-validated, point-to-point data handoffs between agents. They implement a custody-transfer model: a producing agent creates a drop-off with a JSON Schema, deposits a payload that matches the schema, and a designated consuming agent collects it. Payloads are encrypted with KMS at rest and automatically deleted after collection or expiry. Drop-off IDs are prefixed with dof_.

All endpoints require a bearer credential. Operator keys and OAuth tokens can act across the account. Agent JWTs and Passports are bound to their authenticated agent: producers create, deposit, and expire; consumers collect; either participant can read status.

Drop-off Lifecycle

Every drop-off follows a strict state machine:

  • created - Drop-off location established with schema, producer, and consumer agents. Waiting for data deposit.
  • deposited - Payload has been deposited and validated against the schema. Waiting for consumer to collect.
  • collected - Consumer has collected the payload. Encrypted data is wiped from storage.
  • expired - TTL elapsed before collection. Data is wiped by the background worker.
  • failed - Terminal failure state.

Transitions are one-way. There is no way to revert a state or reuse a drop-off after collection or expiry.

Create Drop-off

POST /v1/dropoffs

Create a new drop-off location with a JSON Schema, producing agent, and consuming agent. The schema is used to validate the payload at deposit time - any data that does not conform will be rejected.

Request Body

json
{
  "from_agent": "agt_7kx9m2nq4p",
  "to_agent": "agt_3fh8j1kw6r",
  "schema": {
    "type": "object",
    "properties": {
      "invoice_id": { "type": "string" },
      "amount": { "type": "number", "minimum": 0 },
      "currency": { "type": "string", "enum": ["USD", "EUR", "SEK"] }
    },
    "required": ["invoice_id", "amount", "currency"]
  },
  "ttl_seconds": 1800,
  "on_expire": "notify"
}
  • from_agent (string, required) - Agent ID of the producer who will deposit data.
  • to_agent (string, required) - Agent ID of the consumer who is authorized to collect.
  • schema (object, required) - JSON Schema defining the expected payload shape. Validated with Ajv.
  • ttl_seconds (number, optional) - Time-to-live in seconds. Min: 60, max: 86400 (24 hours). Default: 1800 (30 minutes).
  • on_expire (string, optional) - Legacy clients may still send "notify", "retry", or "fail". The server normalizes every value to the only supported behavior: expire and purge. Default: "notify".

Request Example

bash
curl -X POST https://api.getstack.run/v1/dropoffs \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer sk_live_op_abc123" \
  -d '{
    "from_agent": "agt_7kx9m2nq4p",
    "to_agent": "agt_3fh8j1kw6r",
    "schema": {
      "type": "object",
      "properties": {
        "invoice_id": { "type": "string" },
        "amount": { "type": "number" }
      },
      "required": ["invoice_id", "amount"]
    },
    "ttl_seconds": 1800
  }'

Response - 201 Created

json
{
  "id": "dof_9xm3kR7wL2",
  "operator_id": "op_abc123",
  "from_agent_id": "agt_7kx9m2nq4p",
  "to_agent_id": "agt_3fh8j1kw6r",
  "status": "created",
  "schema": {
    "type": "object",
    "properties": {
      "invoice_id": { "type": "string" },
      "amount": { "type": "number" }
    },
    "required": ["invoice_id", "amount"]
  },
  "on_expire": "notify",
  "mission_id": null,
  "deposited_bytes": 0,
  "created_at": "2026-04-15T10:30:00.000Z",
  "deposited_at": null,
  "collected_at": null,
  "expires_at": "2026-04-15T11:00:00.000Z"
}

Error Responses

  • 400 Bad Request - Invalid JSON Schema, ttl_seconds out of range, or missing required fields.
  • 401 Unauthorized - Missing or invalid Authorization header.
  • 404 Not Found - from_agent or to_agent does not exist.
  • 402 Payment Required - The shared action allowance is exhausted and the Wallet cannot cover overage.

Deposit Payload

POST /v1/dropoffs/:id/deposit

Deposit a payload into a created drop-off. The payload is validated against the drop-off's JSON Schema using Ajv. If validation fails, the deposit is rejected with detailed errors. The payload is encrypted with KMS before storage.

Request Body

json
{
  "agent_id": "agt_7kx9m2nq4p",
  "passport_jti": "pas_active123",
  "payload": {
    "invoice_id": "INV-2026-0042",
    "amount": 1250.00,
    "currency": "USD"
  }
}
  • agent_id (string, required) - The producer named by the request. Must match from_agent on the drop-off.
  • payload (object, required) - The data to deposit. Must conform to the drop-off schema.
  • passport_jti (string, optional) - Active Passport used to derive mission attribution. Omit when the bearer is that Passport.
  • mission_id (string, optional) - Selects one mission carried by the verified Passport. Required when it carries more than one.

Request Example

bash
curl -X POST https://api.getstack.run/v1/dropoffs/dof_9xm3kR7wL2/deposit \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer sk_live_op_abc123" \
  -d '{
    "agent_id": "agt_7kx9m2nq4p",
    "payload": {
      "invoice_id": "INV-2026-0042",
      "amount": 1250.00,
      "currency": "USD"
    }
  }'

Error Responses

  • 400 Bad Request - Payload does not match schema.
  • 401 Unauthorized - Missing or invalid Authorization header.
  • 403 Forbidden - Authenticated agent identity or Passport mission attribution is invalid.
  • 404 Not Found - Drop-off is missing, foreign, or does not name this agent as producer.
  • 409 Conflict - A payload has already been deposited or collected.
  • 410 Gone - Drop-off has expired and any payload has been wiped.

Schema validation is a hard gate. There is no way to bypass it or deposit data that does not conform to the declared schema. This ensures data integrity across the producer-consumer boundary.

Collect Payload

POST /v1/dropoffs/:id/collect

Collect the payload from a deposited drop-off. The request must name the designated consumer in agent_id. After successful collection, the encrypted payload is permanently wiped from storage.

Request Body

json
{
  "agent_id": "agt_3fh8j1kw6r"
}
  • agent_id (string, required) - The agent performing the collection. Must match the to_agent on the drop-off.

Request Example

bash
curl -X POST https://api.getstack.run/v1/dropoffs/dof_9xm3kR7wL2/collect \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer sk_live_op_abc123" \
  -d '{
    "agent_id": "agt_3fh8j1kw6r"
  }'

Response - 200 OK

json
{
  "payload": {
    "invoice_id": "INV-2026-0042",
    "amount": 1250.00,
    "currency": "USD"
  }
}

Error Responses

  • 401 Unauthorized - Missing or invalid Authorization header.
  • 403 Forbidden - An agent-authenticated request names another agent_id.
  • 404 Not Found - Drop-off is missing, foreign, or does not name this agent as consumer.
  • 409 Conflict - Payload has already been collected.
  • 410 Gone - Drop-off has expired and data has been wiped.

Collection is a one-time operation. Once data is collected, it is permanently deleted from STACK's storage. The consumer receives the payload in the response and is responsible for handling it from that point forward.

Get Drop-off Status

GET /v1/dropoffs/:id

Retrieve the current status and metadata for a drop-off. This never returns the actual payload or its encrypted envelope. Account credentials can read any account drop-off; agent credentials can read only drop-offs where that agent is producer or consumer.

Request Example

bash
curl https://api.getstack.run/v1/dropoffs/dof_9xm3kR7wL2 \
  -H "Authorization: Bearer sk_live_op_abc123"

List Drop-offs

GET /v1/dropoffs

Operator keys and OAuth tokens list all drop-offs for the account. Agent JWTs and Passports list only drop-offs where the authenticated agent is producer or consumer.

Request Example

bash
curl https://api.getstack.run/v1/dropoffs \
  -H "Authorization: Bearer sk_live_op_abc123"

Manually Expire Drop-off

POST /v1/dropoffs/:id/expire

Manually expire a drop-off before its TTL elapses. This transitions the drop-off to the expired state and wipes any deposited payload data. Account credentials may expire any active account drop-off; agent credentials may expire only a drop-off they produce.

Request Example

bash
curl -X POST https://api.getstack.run/v1/dropoffs/dof_9xm3kR7wL2/expire \
  -H "Authorization: Bearer sk_live_op_abc123"

TTL and Automatic Expiry

Drop-offs are ephemeral by design. A background worker process monitors active drop-offs and transitions expired ones to the expired state. When a drop-off expires:

  • Status transitions to "expired" regardless of current state (created or deposited).
  • Encrypted payload is permanently deleted from storage.
  • Lifecycle metadata and the immutable deposited-byte count remain for audit and cumulative mission-cap accounting.
  • Legacy on_expire values are accepted but normalized to notify; expiry never retries or changes the transfer to failed.

Tier Limits

Creating a drop-off uses one action from the shared monthly allowance. Deposit and collection do not use another action.

  • Free - 25,000 metered actions / month
  • Developer ($19/mo) - 250,000
  • Pro ($99/mo) - 1,500,000
  • Business ($349/mo) - 10,000,000
  • Enterprise - unlimited monthly allowance
stack | Docs