POST /api/executions
API
One endpoint. You send Python, it runs sealed, you get stdout and what it cost. A run reaches nothing this deployment has not allowed: no filesystem beyond an in-memory /tmp, no process, no environment, and only the network endpoints the deployment permits.
Machine-readable schema: /api/openapi.json
curl https://sucuri.abstra.io/api/executions \
-H "Authorization: Bearer $SUCURI_API_KEY" \
-d '{"code": "print(sum(range(1000)))"}'
Request
-
codestring required - The Python to run.
-
stdinstring - Text fed to the program's stdin, split on newline into a queue of lines.
input()pops one line per call and raises EOFError when the queue is empty, as CPython does at end of file. The optional prompt argument is written to stdout, like CPython. -
files{path: contents} - Files written into /tmp before the run, for inline inputs. They count against the same maxTmpBytes budget the program writes against, so you cannot pre-fill past the limit.
-
mountsarray - Filesystems to attach. See below. Omit it and the run sees only /tmp.
-
maxTmpBytesinteger - Ceiling on /tmp, in bytes. It is clamped to the server maximum, so it can only narrow. The limit is on the total held, not per file.
-
modules{dotted.name: source} - Extra importable modules. This is how you ship code you do not want to inline into code, without deploying anything anywhere.
-
net["host:port"] - Outbound endpoints this run may reach. Matching is exact on both host and port: api.example.com:443 permits neither a subdomain nor port 80. The ceiling is the deployment's and this field can only narrow it; an endpoint outside the ceiling is dropped and refused at connect time. Omit the field and the run gets the whole ceiling. When the deployment's ceiling is empty, no run has any network.
-
limits.timeoutMsinteger - Wall-clock ceiling for this run. Clamped to the deployment's own timeout, so it can only narrow it.
-
limits.maxInstructionsinteger - Instruction ceiling for the run. It can only narrow: the run stops at the smallest of this, the server ceiling, the per-run cap and your remaining balance, and
statuscomes backhalted.
Mounting a filesystem
A run has no filesystem of its own. It gets an in-memory /tmp, dropped when the run ends, plus whatever you mount. A mount points at your bucket or container and uses your credentials, so the data stays where it already is and nothing is stored here. A path under no mount is refused, like every other capability you did not grant.
| provider | Addressing | Credentials | Works with |
|---|---|---|---|
s3 |
bucket, region, endpoint | accessKeyId, secretAccessKey, sessionToken | AWS S3, Cloudflare R2, MinIO, Backblaze B2, DigitalOcean Spaces, Wasabi |
gcs |
bucket | accessKeyId, secretAccessKey | Google Cloud Storage, through its S3-compatible API with an HMAC key |
azure-blob |
account, container | sasToken or accessKey | Azure Blob Storage |
Fields of a mount
-
atstring required - Absolute path the mount appears at, e.g. /data. It may not be /tmp or inside it, may not contain .., and may not nest inside another mount. Overlapping mounts are refused rather than resolved by a rule you would have to guess.
-
providers3 | gcs | azure-blob required - Which service to talk to.
-
bucketstring - Bucket name, for s3 and gcs.
-
account, containerstring - Storage account and container, for azure-blob.
-
prefixstring - Prepended to every key, so a mount can expose one subtree of a bucket. With at: "/data" and prefix: "runs/42/", reading /data/in.csv fetches the key runs/42/in.csv.
-
regionstring - SigV4 region. Defaults to us-east-1, which is what S3-compatible services that do not use regions expect to see.
-
endpointstring - Override the provider's endpoint. Required for R2, MinIO and B2. It must be https and must resolve to a public address; redirects are not followed.
-
readOnlyboolean - Refuse every write, append, delete and rename under this mount. Enforced before any request leaves this service, so a read-only mount never even asks.
-
credentialsobject - Sent over TLS, used for the run, and never stored, logged or echoed back. Your code cannot read them: no syscall exposes them and they are not placed in the environment. Omit them entirely for a public bucket.
Worked examples
Your first run — no storage, no setup
Nothing to configure: /tmp is memory, a relative path lands there because /tmp is the working directory, and the whole filesystem disappears when the run ends. Everything below adds storage to this.
{
"code": "open('out.txt','w').write('hi')\nprint(open('out.txt').read())",
"files": { "/tmp/in.json": "{\"n\": 1}" },
"maxTmpBytes": 1048576
}
Feed it stdin
input() pops one line of stdin per call and raises EOFError when there is nothing left, so a program can read until end of input the way it would in a terminal.
{
"code": "total = 0\nwhile True:\n try:\n total += int(input())\n except EOFError:\n break\nprint(total)",
"stdin": "1\n2\n3"
}
Read from S3, write the result back
Two mounts: inputs read-only, outputs writable. The run never sees your whole bucket, only the prefixes you mounted.
{
"code": "import csv\nrows = list(csv.reader(open('/in/sales.csv')))\nopen('/out/count.txt','w').write(str(len(rows)))",
"mounts": [
{ "at": "/in", "provider": "s3", "bucket": "acme-data", "prefix": "2026-08/",
"region": "us-east-1", "readOnly": true,
"credentials": { "accessKeyId": "AKIA...", "secretAccessKey": "..." } },
{ "at": "/out", "provider": "s3", "bucket": "acme-results", "region": "us-east-1",
"credentials": { "accessKeyId": "AKIA...", "secretAccessKey": "..." } }
]
}
Cloudflare R2, MinIO, Backblaze B2
Anything that speaks S3 works by setting endpoint. Use region "auto" for R2.
{
"code": "print(open('/data/model.json').read()[:80])",
"mounts": [
{ "at": "/data", "provider": "s3", "bucket": "models", "region": "auto",
"endpoint": "https://abc123.r2.cloudflarestorage.com", "readOnly": true,
"credentials": { "accessKeyId": "...", "secretAccessKey": "..." } }
]
}
Google Cloud Storage
Through GCS's S3-compatible API, with an HMAC key from your service account. No OAuth flow to set up.
{
"code": "open('/bucket/out.txt','w').write('done')",
"mounts": [
{ "at": "/bucket", "provider": "gcs", "bucket": "acme-exports",
"credentials": { "accessKeyId": "GOOG1...", "secretAccessKey": "..." } }
]
}
Azure Blob Storage
A container SAS token is the narrowest credential to hand over: scope it to the container and let it expire.
{
"code": "import os\nprint(sorted(os.listdir('/models')))",
"mounts": [
{ "at": "/models", "provider": "azure-blob", "account": "acmestore",
"container": "models", "readOnly": true,
"credentials": { "sasToken": "?sv=2024-11-04&se=..." } }
]
}
Response
-
executionIduuid required - This run.
-
statuscompleted | failed | halted required - halted means a limit stopped the run: the instruction ceiling, the timeout, the heap or recursion depth. Your code cannot catch it.
-
stdoutstring required - Everything the program printed.
-
resultstring - The value of the last expression statement in
code, asrepr()prints it — the rule a notebook cell follows.2 + 2yields"4";x = 2 + 2yields nothing, because an assignment is not an expression;print(x)yields nothing, because it evaluates to None. Absent whenever there is no such value. An object of your own class renders through its__repr__, falling back to<ClassName object>when it defines none. Capped at 64 KiB — seeresultTruncated. -
resultTruncatedbool - Present and true when
resulthit the 64 KiB ceiling and was cut. The cut is the transport's alone: inside the run the value was whole, solen(repr(x))still answers the real length. Absent means nothing was cut. -
resultErrorstring - Present when rendering
resultraised — a__repr__of your own that failed. It does NOT mean the run failed: the program finished,statusiscompleted, and only this one rendering did not. Without it a__repr__that raised would look exactly like a class that defines none. -
errorstring - The Python error, when the run raised.
-
instructionsinteger required - What you are billed. The same program on the same input always bills the same, so you can predict it and check it.
-
cpuMicrosinteger required - CPU microseconds consumed. A diagnostic, not the bill: it varies with the machine and with whoever else is on it.
The Python you get
Every module this sandbox can import, with what each one exports. The list is generated from the interpreter's own registry, so it is what the running engine resolves, not a promise about it. Importing anything else raises ModuleNotFoundError, which your code can catch. The `modules` request field adds your own Python on top. The list names what a module exports, not what each name can do: a method the engine does not implement raises AttributeError, which your code can catch too, so a program can probe before it depends. `collections`, the one most reached for, is complete on its mapping and deque surface — every public method CPython 3.14 gives dict, defaultdict, Counter, OrderedDict and deque, plus their operators. One module needs a warning rather than a list: `random` draws from a real generator, but from a FIXED default seed, so the same program draws the same numbers on every run. That is deliberate — a run whose draws varied would bill differently each time, and the instruction count is meant to be predictable and auditable. `random.seed(n)` chooses the sequence. Use it for simulation and test data; never for a secret, a token or anything that has to be unguessable.
-
__future__ absolute_import,annotations,division,generator_stop,nested_scopes,print_function,unicode_literals,with_statement-
abc ABC,ABCMeta,abstractmethod-
asyncio Event,Lock,Queue,Semaphore,gather,run,sleep,wait_for-
base64 b64decode,b64encode-
bisect bisect,bisect_left,bisect_right,insort,insort_left,insort_right-
collections Counter,OrderedDict,defaultdict,deque,namedtuple-
contextlib - Provided as Python source.
-
contextvars ContextVar-
copy copy,deepcopy-
csv DictReader,DictWriter,reader-
dataclasses MISSING,asdict,astuple,dataclass,field,fields,is_dataclass,replace-
datetime date,datetime,time,timedelta,timezone-
decimal Decimal-
enum Enum,IntEnum,auto-
fractions Fraction-
functools partial,reduce-
hashlib md5,sha256-
heapq heapify,heappop,heappush,nlargest,nsmallest-
http client-
http.client HTTPConnection,HTTPSConnection-
io StringIO-
itertools accumulate,chain,combinations,compress,dropwhile,filterfalse,groupby,islice,pairwise,permutations,product,starmap,takewhile,zip_longest-
json dumps,loads-
logging CRITICAL,DEBUG,ERROR,Formatter,INFO,StreamHandler,WARNING,basicConfig,getLogger-
math ceil,e,factorial,floor,gcd,pi,pow,sqrt-
operator add,attrgetter,itemgetter,methodcaller,mul,sub,truediv-
os getcwd,getenv,listdir,mkdir,path,sep,stat,walk,write_text-
pathlib Path-
pickle dumps,loads-
random choice,choices,getrandbits,randbytes,randint,random,randrange,sample,seed,shuffle,uniform-
re findall,search,split,sub-
shlex join,quote,split-
socket AF_INET,AF_INET6,SOCK_DGRAM,SOCK_STREAM,socket-
statistics mean,median,mode,stdev,variance-
string Template,ascii_letters,ascii_lowercase,ascii_uppercase,digits,punctuation-
struct calcsize,pack,unpack-
subprocess check_output,run-
sys byteorder,getsizeof,maxsize-
tempfile mkdtemp-
textwrap dedent,fill,shorten,wrap-
traceback extract_tb-
typing Any,Dict,FrozenSet,Generic,List,Literal,Optional,Set,Tuple,Type,TypeVar,Union,cast,dataclass_transform,get_args,get_origin,get_type_hints,runtime_checks-
typing_extensions Any,Dict,FrozenSet,Generic,List,Literal,Optional,Set,Tuple,Type,TypeVar,Union,cast,dataclass_transform,get_args,get_origin,get_type_hints,runtime_checks-
urllib parse,request-
urllib.parse quote,unquote,urlencode,urlparse-
urllib.request Request,urlopen-
weakref WeakValueDictionary,ref
What gets raised
-
BaseException - Every builtin exception class of CPython 3.14 exists here under the same name and with the same bases, so naming one in
exceptnever costs you a NameError and a handler written against a base fires:except ArithmeticErrorcatches a ZeroDivisionError,except LookupErrorcatches a KeyError, andexcept Exceptiondoes NOT catch KeyboardInterrupt, SystemExit or GeneratorExit. Which of them the engine itself raises is a separate question — the rest of this section covers that. ExceptionGroup and BaseExceptionGroup are here too, with.exceptions,.subgroup,.splitand.derive, andexcept*splits a group across its clauses and re-raises whatever no clause claimed. -
type(e).__name__ - Catchable. A failing builtin raises the class CPython raises, so the handler you would write for it fires:
[].pop()is an IndexError,[1].index(9)andmin([])are ValueError,next()past the end is StopIteration,ord('ab')andsorted([1,'a'])are TypeError. A failure no rule recognises arrives as RuntimeError — the net, so an unforeseen failure is still caught rather than escaping. -
TypeError: not iterable - Catchable. A generator is an iterable and is DRAINED wherever one is accepted, which covers
map,filter,zipandenumerate, since each of those returns a generator:sorted(map(...)),dict(zip(...))and"".join(map(str, xs))all run the source. Draining consumes it, exactly as in CPython, so a second walk of the same object is empty. Being iterable is not being a sequence:reversed,random.choice,bisectandurlencodemeasure or index their argument, so they refuse a generator or an iterator with a TypeError even though a list of the same items is accepted.inis the exception that does NOT drain: it pulls only as far as the answer, so3 in endless_generator()returns and what it did not pull is still there.io.StringIOis iterable too, LINE by line, moving the buffer's own cursor — reading a line through the iterator and reading it withreadlineare the same read. -
str.encode / bytes.decode - Catchable. Three text codecs are implemented — utf-8, ascii and latin-1 — under the aliases CPython accepts for them, and utf-8 is the default. Any other codec name raises LookupError, which is NOT a ValueError, so a typo'd name does not slip past as a bad value. Text the codec cannot represent raises UnicodeEncodeError and bytes it cannot read raise UnicodeDecodeError, both with CPython's message and both catchable as ValueError. The
errors=argument is not read: a failure is raised, never substituted. -
OSError - Catchable. Every capability the sandbox refuses and every one that fails: a path under no mount, a write past maxTmpBytes, an endpoint outside the ceiling, a subprocess. The message is CPython's [Errno N] form and the class is the matching subclass — PermissionError for 13, FileNotFoundError for 2, plain OSError otherwise.
-
ModuleNotFoundError - Catchable. An import of a module this sandbox does not have. It is a subclass of ImportError, so
except ImportErrorcatches it too, ande.nameis the module that was missing. -
EOFError - Catchable.
input()with nothing left in stdin, exactly where CPython raises at end of file. -
status: failed - In the response. The program raised and nothing caught it:
errorcarries the exception andstdoutcarries what was printed before it. Code that does not compile reports here too, and bills nothing. -
status: halted - Not catchable. A resource limit stopped the run: the instruction ceiling, the wall-clock timeout, the heap or recursion depth. It is the one thing your code can neither catch nor clean up after — there is no exception, the run stops. Everything executed up to that point is billed.
What a call costs
Billing is in instructions. Interpretation charges one per instruction, a builtin that iterates charges per element, and a call that blocks pays the fixed price below. Waiting itself is free by design, so a slow response costs no more than a fast one.
| Call | Instructions | What it covers |
|---|---|---|
socket connect | 50,000 | Opening a connection: a TCP connect, a TLS handshake, or a UDP bind. |
socket send / recv | 10,000 | One send or one receive on an open socket, whatever its size. |
mount read / write | 20,000 | One round trip to a mounted bucket or container: read, write, stat, list. |
/tmp operation | 500 | One operation on the in-memory /tmp, which is RAM: no network and no disk. |
/tmp byte | 1 | Per byte moved through /tmp, on top of the operation itself. |
A call is charged when it is attempted, whether it succeeds, fails or is refused — otherwise a program could probe the sandbox for free. Code that does not compile runs nothing and bills nothing.
No run executes more than 100,000,000 instructions, whatever the balance is. Past that it is halted, and everything it did up to the halt is billed.
HTTP responses
-
400 - Malformed body, or a mount that cannot be accepted: a bad mount point, two mounts that overlap, or an endpoint this service will not connect to.
-
401 - Missing or invalid API key.
-
402 - Your balance is at or below zero. The body carries a topUp URL.
-
503 - We could not meter the run, so it did not execute and nothing was charged. Retry.