Platform
Marketplace partner integration
Any marketplace that rents out agents can provision Robots Center workspaces for its users and give the rented runtimes a way to report what they are doing. The integration is generic: partners are ordinary records with a slug and a scope list, and nothing in this plane is specific to one marketplace.
On this page
Start here
A marketplace can create one managed workspace per customer, open a bounded engagement per job, and receive activity without giving a rented runtime broad workspace access. Partner, workspace, and engagement are separate principals with narrow credentials.
Before you begin
- A marketplace partner record and one-time mpk_ key issued by a platform administrator
- Stable user and job references plus an HTTPS receiver for signed webhooks
Put it into practice
-
Provision the customer workspace
Use workspaces:provision to create or reconcile a workspace from your external reference. Keep the returned identifiers with the marketplace account.
-
Open one engagement per job
Create an engagement for the hiring, mint its short-lived runtime token, and give only that token to the rented agent. The runtime can append allowed events for that engagement only.
-
Reconcile and close
Process signed webhooks idempotently and close the engagement when the job ends. Use an SSO handoff for customer inspection.
The customer enters the correct workspace, one job produces a bounded trace, and the marketplace receives verified activity without reading workspace traces.
01 The shape of the integration
detailsThree principals, not one
A PARTNER is a platform-level machine principal that sits above workspaces and authenticates with an mpk_ key. A WORKSPACE belongs to one of the partner's users and holds their traces. An ENGAGEMENT is one job: one hiring, bound to one trace, for a bounded window. The runtime that actually does the work only ever holds an engagement credential, so it can never read or write outside the single job it was rented for.
Partner scopes
workspaces:provision (create and disconnect workspaces), engagements:manage (open, append to, mint tokens for and close engagements), webhooks:manage (subscribe to activity), sso:handoff (send a user into their workspace), runs:manage (PUT and GET /api/v1/partner/runs/:external_swarm_ref, and POST its /viewer_revocations), runs:viewer_handoff (POST /api/v1/partner/runs/:external_swarm_ref/viewer_handoffs). A partner key is not a workspace key: it can never read a workspace's traces. GET /api/v1/traces/:id and GET /api/v1/traces/:trace_id/events are machine traces:read routes under APIAuth, not PartnerAuth. An mpk_ key never authenticates those paths. Use webhooks for trace activity and the Partner API reads for engagement, commercial-history and own-run reconciliation.
mpk_ keys are issued on /admin/partners
Platform admins issue, scope, rotate, and suspend partner keys on GET /admin/partners. The plaintext mpk_ secret is shown once at issue or rotate.
Sponsored workspaces do not 429 on traces
Marketplace-provisioned workspaces write settings.quota_overrides.traces_per_month = infinity so sponsored ingest never 429s mid-hire. Other quota dimensions stay on the workspace plan (Starter still lists 50,000 traces for self-serve workspaces).
Single-member, no third-party invites
A marketplace-linked workspace refuses invitations with marketplace_managed_workspace and stays single-member. The console has no per-engagement visibility grants for extra operators.
02 Provisioning a workspace
detailsPOST /api/v1/partner/workspaces
Idempotent find-or-link on your external_ref: the first call creates the user, workspace and bridge service agent and answers 201; later calls answer 200 with identical fields. A workspace disconnected earlier is reactivated by the same call. No long-lived credential is issued here at all — runtimes are credentialed per engagement, so there is nothing to lose or leak between jobs.
Why the login address looks synthetic
The Robots Center login identity is <partner-slug>+<external_user_id>@marketplace.invalid, never the human email you send. A partner asserts an address it has not proven, and letting that address claim the global email namespace would let any marketplace take over an existing account by naming it. The human email is stored on the identity account instead. The practical consequence: a provisioned workspace is reached through the SSO handoff below, not by signing in at robotscenter.net directly.
03 One engagement per job
detailsPOST /api/v1/partner/engagements
Idempotent per (partner, external_engagement_ref) — send your own job id and retries collapse onto the same engagement. The response carries engagement_id, trace_id, epoch, expires_at and events_url. Engagements default to 7 days and are clamped to 30.
POST /api/v1/partner/engagements/:id/tokens
Mints the only credential a rented runtime ever holds: append-only, scope engagement:append, default TTL 86400 seconds, clamped to the engagement's own expiry and also hard-capped at 604800 seconds (7 days). A 30-day engagement cannot mint a 30-day token. Hand it to the runtime together with events_url. It authenticates on that URL and nowhere else — presented to any other endpoint it fails signature verification outright.
POST /api/v1/partner/engagements/:id/close
Finalizes the trace with a terminal status and bumps the engagement epoch, which kills every outstanding runtime token for that job at its next request. Close when the job ends rather than waiting for the token to expire. EngagementExpiryWorker also closes still-active engagements 900 seconds after expires_at as final_status partial and bumps the epoch.
04 The runtime ingest route
examplePOST /api/v1/engagements/:engagement_id/events
The runtime's entire authority. Append-only: no read, no list, no finalize, and no trace id in the path — the target trace comes from the authenticated engagement, so the runtime cannot name another job's trace. Up to 50 events per request. Give every event a stable source_event_id of at most 128 bytes: a retried batch is counted as a duplicate rather than appended twice, and the response reports accepted, duplicates and rejected. Runtime ids are stored under an opaque runtime:sha512: prefix and every event records origin runtime, so a runtime can neither collide with nor pre-empt the marketplace's own events. The hiring.* event types and names belong to the marketplace: a runtime event that claims them is counted as rejected.
Stop on 401 and 403, wait on 503
401/403 require diagnosis, including whether the engagement URL names the right job; do not retry blindly. A 503 with error workspace_paused or workspace_frozen is not terminal: an operator reverses both, the token stays valid, so keep the events and retry after the Retry-After seconds (60, or the time until the freeze expires when sooner). For 413 split the batch, for 422 correct invalid input, and for 429 honor Retry-After with backoff. Retry transient network and 5xx failures with stable source_event_id values, so acknowledged events are not appended twice. Authorization is re-checked against the current engagement on every request, so revocation takes effect immediately rather than at token expiry.
curl -X POST https://robotscenter.net/api/v1/engagements/$ENGAGEMENT_ID/events \
-H "Authorization: Bearer $ENGAGEMENT_TOKEN" \
-H "Content-Type: application/json" \
-d '{"events":[{"source_event_id":"run-1:step-3","event_type":"tool.call","name":"read_file","status":"ok","duration_ms":12}]}'
# => {"accepted":1,"duplicates":0,"rejected":0}
05 What a runtime may record
detailsDeny-by-default event fields
A rented runtime writes into a workspace whose data belongs to somebody else, so nothing it sends is stored verbatim. Allowed: source_event_id, event_type, name, status, span_kind, duration_ms, started_at, metadata. Refused outright: input_payload, output_payload and error_payload — their names are recorded under metadata._dropped_payload_keys so an operator can see a runtime attempting to ship bodies.
Fields the server owns
sequence is assigned server-side, so a runtime cannot splice itself into an earlier position in the timeline. A supplied started_at is clamped into the trace's own window. metadata is capped at 32 keys and 32 items per list, depth 3 and 4096 bytes total, and credential-named keys are redacted at every depth. What a runtime's text becomes is the next card; on the marketplace's own partner-key events, name keeps 128 bytes and each metadata string 512 bytes, truncated.
Content survives only as a fingerprint
The server enforces the content posture on the runtime route rather than trusting the runtime to follow it. A string survives as sent only if it is a label: at most 128 bytes of letters, digits and _ . : / @ + -, no whitespace, such as a tool, model or error class. Any other metadata string, at any depth, is stored as {sha256, byte_size} of the whole value as sent. A name that is not a label is replaced by the event type and kept as metadata.reported_name, its fingerprint; an unrecognised event_type is kept as metadata.reported_event_type if it is a label of at most 64 bytes, otherwise as its fingerprint. A metadata key must be an identifier of at most 64 bytes (letters, digits, _ . : -) or it is dropped and _truncated is set. Base64 and hex are made of label characters, so the metadata keys and labels of one event share a 512-byte budget: past it a label is fingerprinted and a key dropped. Numbers, booleans and null pass through within the 4096-byte cap, so an already-fingerprinted {sha256, byte_size} map is kept, its digest spending the budget. source_event_id is the event's identity and is stored under an opaque server-derived runtime:sha512: digest rather than verbatim plaintext. Credential-named keys are still redacted. The marketplace's own partner-key events are not rewritten.
06 Receiving activity back
detailsPOST /api/v1/partner/webhook_subscriptions
Registers a partner-owned subscription on the linked workspace. Send an Idempotency-Key so exact retries return the same subscription and signing secret for 24 hours; reusing the key with another body returns 409. Omitting it preserves create-new behavior. Defaults to trace.event.appended and trace.finalized, and receives only this partner's engagement events. The workspace owner can see it but cannot edit, re-key, disable or delete it (403 partner_managed_subscription), and cannot mark a hook of its own as a partner's (metadata.partner_id is refused); disconnecting the workspace through DELETE /api/v1/partner/workspaces/:external_ref is what disables it. Nothing re-enables a disabled subscription, so the owner may then delete it. Deliveries carry event shape only — ids, event types, names, sequences, statuses, timings and each event's origin (partner or runtime) — never payload bodies, so a subscriber cannot become a back door around the ingest rules above.
Verifying a delivery
Concatenate the x-agentops-timestamp header and the raw body with a period, HMAC-SHA256 it with the subscription secret, hex-encode lowercase, and compare against x-agentops-signature in constant time. Verify against the raw body, not a re-encoded parse. Reject stale timestamps, and take the event type from the signed body rather than the header.
07 Sending a user into their workspace
detailsPOST /api/v1/partner/sso_handoffs
Mints a single-use handoff URL valid for 120 seconds, landing on a return_to path inside the app. Mint one per click and never store it: it is a live credential with a seconds-long life. Fetching the URL renders a CSRF-protected interstitial that names the account and workspace and never logs anyone in on GET; the user clicks Continue to send the POST that consumes the token, and a second visit is refused. return_to must be /app or a path under it: printable ASCII, at most 255 characters, no backslash.
08 Disconnecting
detailsDELETE /api/v1/partner/workspaces/:external_ref
Revokes everything the partner holds: active engagements are revoked and their epochs bumped, the bridge service agent and any legacy credentials are revoked, partner webhook subscriptions are disabled, and the linked user's browser sessions are cut. The workspace, user, membership and traces are deliberately left intact — they are the user's own account and their own evidence. The disconnected link is retained so a later provision call reconnects to the same workspace.
Troubleshooting
- A partner key cannot read traces
- That boundary is intentional. mpk_ authenticates partner routes only; workspace trace reads require machine API authentication and traces:read. Reconcile partner activity through webhooks.
- The runtime receives 401 or 403
- Stop retrying in a tight loop. Check engagement token expiry, closure, permitted event fields, and partner status before minting or distributing another bounded token.
- An invite is refused
- Marketplace-managed workspaces are single-member and reject third-party invitations. Use the handoff flow for the linked customer rather than adding operators.