POST /api/executions
API
Um endpoint. Você manda Python, ele roda isolado, você recebe o stdout e quanto custou. Um run não alcança nada que este deployment não tenha permitido: nenhum filesystem além de um /tmp em memória, nenhum processo, nenhum ambiente, e só os endpoints de rede que o deployment libera.
Schema para máquinas: /api/openapi.json
curl https://sucuri.abstra.io/api/executions \
-H "Authorization: Bearer $SUCURI_API_KEY" \
-d '{"code": "print(sum(range(1000)))"}'
Requisição
-
codestring obrigatório - O Python a executar.
-
stdinstring - Texto entregue ao stdin do programa, quebrado por newline numa fila de linhas.
input()tira uma linha por chamada e levanta EOFError quando a fila está vazia, como o CPython faz no fim do arquivo. O argumento opcional de prompt é escrito no stdout, como no CPython. -
files{path: contents} - Arquivos escritos no /tmp antes do run, para entradas inline. Contam no mesmo orçamento maxTmpBytes contra o qual o programa escreve, então não é possível pré-encher além do limite.
-
mountsarray - Filesystems a anexar. Veja abaixo. Sem esse campo, o run vê apenas /tmp.
-
maxTmpBytesinteger - Teto do /tmp, em bytes. É limitado ao máximo do servidor, então só consegue estreitar. O limite vale para o total guardado, não por arquivo.
-
modules{dotted.name: source} - Módulos importáveis extras. É assim que você entrega código que não quer embutir em code, sem fazer deploy de nada em lugar nenhum.
-
net["host:port"] - Endpoints de saída que este run pode alcançar. A correspondência é exata em host e porta: api.example.com:443 não libera subdomínio nem a porta 80. O teto é do deployment e este campo só consegue estreitá-lo; um endpoint fora do teto é descartado e recusado na hora de conectar. Sem o campo, o run recebe o teto inteiro. Quando o teto do deployment está vazio, nenhum run tem rede.
-
limits.timeoutMsinteger - Teto de tempo de parede deste run. É limitado ao timeout do próprio deployment, então só consegue estreitá-lo.
-
limits.maxInstructionsinteger - Teto de instruções do run. Só estreita: o run para no menor entre este valor, o teto do servidor, o limite por run e o seu saldo restante, e o
statusvolta comohalted.
Montando um filesystem
Uma execução não tem filesystem próprio. Ela recebe um /tmp em memória, descartado quando termina, mais o que você montar. Um mount aponta para o seu bucket ou container e usa as suas credenciais, então o dado fica onde já está e nada é armazenado aqui. Um caminho sob nenhum mount é recusado, como toda capacidade que você não concedeu.
| provider | Endereçamento | Credenciais | Funciona com |
|---|---|---|---|
s3 |
bucket, region, endpoint | accessKeyId, secretAccessKey, sessionToken | AWS S3, Cloudflare R2, MinIO, Backblaze B2, DigitalOcean Spaces, Wasabi |
gcs |
bucket | accessKeyId, secretAccessKey | Google Cloud Storage, pela API compatível com S3 usando uma HMAC key |
azure-blob |
account, container | sasToken ou accessKey | Azure Blob Storage |
Campos de um mount
-
atstring obrigatório - Caminho absoluto onde o mount aparece, por exemplo /data. Não pode ser /tmp nem estar dentro dele, não pode conter .., e não pode estar dentro de outro mount. Mounts sobrepostos são recusados em vez de resolvidos por uma regra que você teria que adivinhar.
-
providers3 | gcs | azure-blob obrigatório - Com qual serviço falar.
-
bucketstring - Nome do bucket, para s3 e gcs.
-
account, containerstring - Storage account e container, para azure-blob.
-
prefixstring - Prefixado em toda chave, então um mount pode expor só uma subárvore do bucket. Com at: "/data" e prefix: "runs/42/", ler /data/in.csv busca a chave runs/42/in.csv.
-
regionstring - Região do SigV4. Padrão us-east-1, que é o que serviços compatíveis com S3 sem noção de região esperam ver.
-
endpointstring - Sobrescreve o endpoint do provedor. Necessário para R2, MinIO e B2. Tem que ser https e resolver para um endereço público; redirects não são seguidos.
-
readOnlyboolean - Recusa toda escrita, append, remoção e rename sob este mount. Barrado antes de qualquer requisição sair deste serviço, então um mount read-only nem chega a pedir.
-
credentialsobject - Enviadas por TLS, usadas no run, e nunca armazenadas, logadas nem devolvidas. Seu código não consegue lê-las: nenhuma syscall as expõe e elas não vão para o ambiente. Omita por completo para um bucket público.
Exemplos prontos
Seu primeiro run — sem storage, sem setup
Nada para configurar: /tmp é memória, um caminho relativo cai lá porque /tmp é o working directory, e o filesystem inteiro some quando o run termina. Tudo abaixo acrescenta storage a isto.
{
"code": "open('out.txt','w').write('hi')\nprint(open('out.txt').read())",
"files": { "/tmp/in.json": "{\"n\": 1}" },
"maxTmpBytes": 1048576
}
Alimentando o stdin
O input() tira uma linha do stdin por chamada e levanta EOFError quando não sobra nada, então um programa consegue ler até o fim da entrada como faria num terminal.
{
"code": "total = 0\nwhile True:\n try:\n total += int(input())\n except EOFError:\n break\nprint(total)",
"stdin": "1\n2\n3"
}
Ler do S3 e gravar o resultado de volta
Dois mounts: entradas read-only, saídas graváveis. O run nunca vê o seu bucket inteiro, só os prefixos que você montou.
{
"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
Qualquer coisa que fale S3 funciona definindo endpoint. Use region "auto" no 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
Pela API compatível com S3 do GCS, com uma HMAC key da sua service account. Sem fluxo OAuth para configurar.
{
"code": "open('/bucket/out.txt','w').write('done')",
"mounts": [
{ "at": "/bucket", "provider": "gcs", "bucket": "acme-exports",
"credentials": { "accessKeyId": "GOOG1...", "secretAccessKey": "..." } }
]
}
Azure Blob Storage
Um SAS token de container é a credencial mais estreita para entregar: limite ao container e deixe expirar.
{
"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=..." } }
]
}
Resposta
-
executionIduuid obrigatório - Este run.
-
statuscompleted | failed | halted obrigatório - halted significa que um limite parou o run: o teto de instruções, o timeout, o heap ou a profundidade de recursão. Seu código não consegue capturar.
-
stdoutstring obrigatório - Tudo que o programa imprimiu.
-
resultstring - O valor do último statement de expressão do
code, como orepr()imprime — a regra que uma célula de notebook segue.2 + 2devolve"4";x = 2 + 2não devolve nada, porque atribuição não é expressão;print(x)não devolve nada, porque avalia para None. Ausente sempre que não houver esse valor. Um objeto de classe sua aparece pelo__repr__dele, caindo em<ClassName object>quando não define nenhum. Limitado a 64 KiB — vejaresultTruncated. -
resultTruncatedbool - Presente e verdadeiro quando o
resultbateu no teto de 64 KiB e foi cortado. O corte é só do transporte: dentro da execução o valor estava inteiro, entãolen(repr(x))continua respondendo o tamanho real. Ausente significa que nada foi cortado. -
resultErrorstring - Presente quando a renderização do
resultlevantou — um__repr__seu que falhou. NÃO significa que a execução falhou: o programa terminou, ostatusécompleted, e só essa renderização não deu certo. Sem ele, um__repr__que levanta seria idêntico a uma classe que não define nenhum. -
errorstring - O erro de Python, quando o run levantou exceção.
-
instructionsinteger obrigatório - O que é cobrado. O mesmo programa na mesma entrada sempre cobra igual, então você consegue prever e conferir.
-
cpuMicrosinteger obrigatório - Microssegundos de CPU consumidos. Um diagnóstico, não a cobrança: varia com a máquina e com quem mais estiver nela.
O Python que você recebe
Todo módulo que este sandbox consegue importar, com o que cada um exporta. A lista é gerada do próprio registro do interpretador, então ela é o que o engine em execução resolve, e não uma promessa a respeito. Importar qualquer outra coisa levanta ModuleNotFoundError, que o seu código consegue capturar. O campo `modules` da requisição acrescenta o seu próprio Python por cima. A lista nomeia o que um módulo exporta, não o que cada nome faz: um método que o motor não implementa levanta AttributeError, que o seu código também consegue capturar, então dá para sondar antes de depender. O `collections`, o mais procurado, está completo na superfície das mappings e do deque — todo método público que o CPython 3.14 dá a dict, defaultdict, Counter, OrderedDict e deque, mais os operadores deles. Um módulo precisa de aviso, e não de lista: o `random` sorteia de um gerador de verdade, mas a partir de uma semente padrão FIXA, então o mesmo programa sorteia os mesmos números em toda execução. Isso é deliberado — uma execução cujos sorteios variassem cobraria diferente a cada vez, e a contagem de instruções existe para ser previsível e auditável. O `random.seed(n)` escolhe a sequência. Use para simulação e dado de teste; nunca para segredo, token ou qualquer coisa que precise ser imprevisível.
-
__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 - Fornecido como código Python.
-
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
O que é levantado
-
BaseException - Toda classe de exceção builtin do CPython 3.14 existe aqui com o mesmo nome e com as mesmas bases, então nomear uma no
exceptnunca custa um NameError e um handler escrito contra uma base dispara:except ArithmeticErrorpega um ZeroDivisionError,except LookupErrorpega um KeyError, eexcept ExceptionNÃO pega KeyboardInterrupt, SystemExit nem GeneratorExit. Quais delas o próprio motor levanta é outra pergunta — o resto desta seção cobre isso. ExceptionGroup e BaseExceptionGroup também existem, com.exceptions,.subgroup,.splite.derive, e oexcept*divide um grupo entre as clauses e relança o que nenhuma reclamou. -
type(e).__name__ - Capturável. Um builtin que falha levanta a classe que o CPython levanta, então o handler que você escreveria para ele dispara:
[].pop()é IndexError,[1].index(9)emin([])são ValueError,next()além do fim é StopIteration,ord('ab')esorted([1,'a'])são TypeError. Uma falha que nenhuma regra reconhece chega como RuntimeError — a rede, para que uma falha imprevista continue capturada em vez de escapar. -
TypeError: not iterable - Capturável. Um gerador é um iterável e é DRENADO onde um iterável é aceito, o que cobre
map,filter,zipeenumerate, já que cada um deles devolve um gerador:sorted(map(...)),dict(zip(...))e"".join(map(str, xs))rodam a fonte. Drenar consome, exatamente como no CPython, então uma segunda passada pelo mesmo objeto vem vazia. Ser iterável não é ser sequência:reversed,random.choice,bisecteurlencodemedem ou indexam o argumento, então recusam gerador e iterador com TypeError mesmo aceitando uma lista dos mesmos itens. Oiné a exceção que NÃO drena: ele puxa só até a resposta, então3 in gerador_infinito()retorna e o que ele não puxou continua lá. Oio.StringIOtambém é iterável, LINHA a linha, movendo o cursor do próprio buffer — ler uma linha pelo iterador e lê-la comreadlinesão a mesma leitura. -
str.encode / bytes.decode - Capturável. Três codecs de texto estão implementados — utf-8, ascii e latin-1 — sob os apelidos que o CPython aceita para eles, e utf-8 é o padrão. Qualquer outro nome de codec levanta LookupError, que NÃO é ValueError, então um nome com erro de digitação não passa como valor ruim. Texto que o codec não representa levanta UnicodeEncodeError e bytes que ele não lê levantam UnicodeDecodeError, os dois com a mensagem do CPython e os dois capturáveis como ValueError. O argumento
errors=não é lido: a falha é levantada, nunca substituída. -
OSError - Capturável. Toda capacidade que o sandbox recusa e toda que falha: um caminho sob nenhum mount, uma escrita além do maxTmpBytes, um endpoint fora do teto, um subprocesso. A mensagem é a forma [Errno N] do CPython e a classe é a subclasse correspondente — PermissionError para 13, FileNotFoundError para 2, OSError puro nos demais.
-
ModuleNotFoundError - Capturável. Import de um módulo que este sandbox não tem. É subclasse de ImportError, então
except ImportErrortambém pega, ee.nameé o módulo que faltou. -
EOFError - Capturável.
input()sem nada restando no stdin, exatamente onde o CPython levanta no fim do arquivo. -
status: failed - Na resposta. O programa levantou e nada capturou:
errorcarrega a exceção estdoutcarrega o que foi impresso antes dela. Código que não compila também é reportado aqui, e não cobra nada. -
status: halted - Não capturável. Um limite de recurso parou o run: o teto de instruções, o timeout de parede, o heap ou a profundidade de recursão. É a única coisa que o seu código não consegue capturar nem limpar depois — não há exceção, o run para. Tudo que foi executado até ali é cobrado.
Quanto custa uma chamada
A cobrança é em instruções. A interpretação conta uma por instrução, um builtin que itera conta por elemento, e uma chamada que bloqueia paga o preço fixo abaixo. Esperar em si é de graça por desenho, então uma resposta lenta não custa mais que uma rápida.
| Chamada | Instruções | O que cobre |
|---|---|---|
conectar socket | 50,000 | Abrir uma conexão: um connect de TCP, um handshake de TLS, ou um bind de UDP. |
send / recv de socket | 10,000 | Um envio ou um recebimento num socket aberto, de qualquer tamanho. |
leitura / escrita em mount | 20,000 | Uma ida e volta a um bucket ou container montado: ler, escrever, stat, listar. |
operação em /tmp | 500 | Uma operação no /tmp em memória, que é RAM: sem rede e sem disco. |
byte em /tmp | 1 | Por byte movido pelo /tmp, além do custo da operação em si. |
Uma chamada é cobrada quando é tentada, tendo ela sucesso, falhando ou sendo recusada — senão um programa sondaria o sandbox de graça. Código que não compila não executa nada e não cobra nada.
Nenhum run executa mais que 100,000,000 instruções, seja qual for o saldo. Passando disso ele é parado, e tudo que ele fez até a parada é cobrado.
Respostas HTTP
-
400 - Corpo malformado, ou um mount que não pode ser aceito: ponto de mount inválido, dois mounts sobrepostos, ou um endpoint ao qual este serviço não se conecta.
-
401 - API key ausente ou inválida.
-
402 - Seu saldo está zerado ou negativo. O corpo traz uma URL topUp.
-
503 - Não conseguimos medir o run, então ele não executou e nada foi cobrado. Tente de novo.